Genera el PDF del presupuesto con galería antes/después por zona
- build-presupuesto.ts: construirPresupuestoPdf(leadId) agrupa fotos y notas por zona (fallback al tipoReforma del lead), convierte las imágenes con resolverImagenPdf y arma el PDF. Carga el lead por id sin scoping (uso interno desde el route del panel y desde la finalización pública). - tenant-queries: getTenantPerfilById(tenantId) sin auth; getTenantPerfil lo reutiliza con el tenant de la sesión. - PresupuestoDoc: prop zonas + sección "Imágenes de tu reforma" (antes/después lado a lado + notas por zona). - route del panel: refactor para reutilizar construirPresupuestoPdf (DRY), manteniendo getLead como guardia de auth/404. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,6 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { renderToBuffer } from '@react-pdf/renderer';
|
||||
import { getLead } from '@/db/queries';
|
||||
import { getTenantPerfil } from '@/db/tenant-queries';
|
||||
import { TIPO_LABEL } from '@/lib/funnel';
|
||||
import { PresupuestoDoc } from '@/lib/pdf/PresupuestoDoc';
|
||||
import { construirDescripcionRender, resolverImagenPdf } from '@/lib/pdf/render-info';
|
||||
import type { BudgetResult } from '@/budget/types';
|
||||
import type { AbstractedPreferences } from '@/lib/voice/preferences';
|
||||
import { construirPresupuestoPdf } from '@/lib/pdf/build-presupuesto';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
@@ -16,52 +10,18 @@ export async function GET(
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id } = await params;
|
||||
// getLead aplica el scoping por tenant del panel: sirve de guardia de auth/404.
|
||||
const data = await getLead(id);
|
||||
if (!data) notFound();
|
||||
|
||||
const pdf = await construirPresupuestoPdf(id);
|
||||
if (!pdf) notFound();
|
||||
|
||||
const descargar = new URL(req.url).searchParams.get('download') === '1';
|
||||
|
||||
const { lead } = data;
|
||||
const empresa = await getTenantPerfil();
|
||||
|
||||
const snapshot = lead.desgloseSnapshot as { result: BudgetResult } | null;
|
||||
const desglose = snapshot?.result ?? null;
|
||||
|
||||
const [logoSrc, imagenSrc] = await Promise.all([
|
||||
resolverImagenPdf(empresa.logoUrl, { formato: 'png', maxAncho: 400 }),
|
||||
resolverImagenPdf(lead.renderUrl, { formato: 'jpeg', maxAncho: 1400 }),
|
||||
]);
|
||||
const prefs = lead.preferencesSnapshot as AbstractedPreferences | null;
|
||||
const render = imagenSrc
|
||||
? {
|
||||
imagenSrc,
|
||||
descripcion: construirDescripcionRender({
|
||||
calidad: lead.calidadGlobal,
|
||||
materiales: desglose?.materialesRender ?? [],
|
||||
estilo: prefs?.estiloRender ?? [],
|
||||
}),
|
||||
}
|
||||
: null;
|
||||
|
||||
const buffer = await renderToBuffer(
|
||||
PresupuestoDoc({
|
||||
empresa,
|
||||
cliente: { nombre: lead.nombre, telefono: lead.telefono, provincia: lead.provincia },
|
||||
reforma: {
|
||||
tipoLabel: lead.tipoReforma ? TIPO_LABEL[lead.tipoReforma] : 'Reforma',
|
||||
fecha: lead.createdAt,
|
||||
},
|
||||
desglose,
|
||||
logoSrc,
|
||||
render,
|
||||
})
|
||||
);
|
||||
|
||||
const slug = lead.nombre.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
||||
return new Response(new Uint8Array(buffer), {
|
||||
return new Response(new Uint8Array(pdf.buffer), {
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `${descargar ? 'attachment' : 'inline'}; filename="presupuesto-${slug || lead.id}.pdf"`,
|
||||
'Content-Disposition': `${descargar ? 'attachment' : 'inline'}; filename="${pdf.filename}"`,
|
||||
'Cache-Control': 'no-store',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user