Plataforma técnica · Orchestrator
Pdf Service
Orchestrator Common Pdf
PdfService es un wrapper delgado sobre Puppeteer que produce Buffer PDF a partir de una URL o de HTML inline. Métodos estáticos — no se instancia.
Cuándo usarlo
Section titled “Cuándo usarlo”- Generar PDFs de liquidaciones de sueldo (lo más común).
- Exportar reportes contables.
- Producir contratos finiquitos.
Para generación masiva o templating complejo, los servicios consumidores combinan docxtemplater/pizzip con PdfService.generateFromHtml. El service solo se encarga del paso “HTML/URL → PDF”.
PdfService.generateFromUrl(url, options?)
Section titled “PdfService.generateFromUrl(url, options?)”static async generateFromUrl(url: string, options?: PdfServiceOptions): Promise<Buffer>Lanza un browser headless, navega a url con waitUntil: 'networkidle0' (todo lo HTTP terminado por al menos 500ms), emula screen y exporta a PDF.
Útil cuando hay un endpoint que renderiza el documento (e.g. una página de previsualización servida por Sevastopol o el propio Orchestrator).
PdfService.generateFromHtml(html, options?, baseUrl?)
Section titled “PdfService.generateFromHtml(html, options?, baseUrl?)”static async generateFromHtml( html: string, options?: PdfServiceOptions, baseUrl?: string,): Promise<Buffer>Setea el HTML con setContent(... { waitUntil: 'networkidle0' }). Si se pasa baseUrl y el HTML no contiene <base ...>, inyecta uno dentro de <head> para resolver links/imgs relativos.
Útil cuando ya tienes el HTML armado (Mustache, plantilla, string concat) y no necesitas una URL accesible.
Opciones
Section titled “Opciones”PdfServiceOptions extiende un subset de PDFOptions de Puppeteer:
| Campo | Default | Notas |
|---|---|---|
format | 'Letter' | 'A4', 'Legal', etc. |
printBackground | true | Imprime backgrounds CSS. |
preferCSSPageSize | true | @page del CSS gana sobre format. |
margin | { top, right, bottom, left: '1cm' } | Override individual. |
scale | — (Puppeteer default 1) | Útil para encoger reportes anchos. |
landscape | — (Puppeteer default false) |
Patrón típico
Section titled “Patrón típico”import { PdfService } from '@/domain/common/PdfService';
router.get('/api/remuneraciones/liquidaciones/:id/pdf', authenticateToken, asyncHandler(async (req, res) => {const html = await renderLiquidacionHtml(req.params.id);const buffer = await PdfService.generateFromHtml(html, { format: 'Letter', margin: { top: '0.8cm', right: '0.8cm', bottom: '0.8cm', left: '0.8cm' },});
res.setHeader('Content-Type', 'application/pdf');res.setHeader('Content-Disposition', `attachment; filename="liquidacion-${req.params.id}.pdf"`);res.send(buffer);}));Comportamiento del browser
Section titled “Comportamiento del browser”Cada llamada:
puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }).browser.newPage().- Navegación / setContent +
emulateMediaType('screen'). page.pdf(...).browser.close()enfinally.
--no-sandbox es necesario para correr Chromium en Docker / Cloud Run sin permisos de privilegio. Si se ejecuta en un host hardened, considerar quitarlo y configurar el sandbox de Chromium correctamente.
Browser por llamada (no reusado) — cada generateFromX arranca y cierra Chromium. Esto es caro (~500ms-1s de arranque). Para alto throughput, considerar:
- Browser pool reusado (
puppeteer-clustero equivalente custom). - Worker pool dedicado para PDFs.
- Pre-warmed browser singleton.
Ninguna optimización está implementada — la frecuencia actual (liquidaciones bajo demanda) no la justifica.
emulateMediaType(‘screen’)
Section titled “emulateMediaType(‘screen’)”Por default Puppeteer usa print que aplica @media print CSS. El service fuerza screen para que el PDF se vea como el browser lo muestra. Si tu plantilla quiere un look específico para print, configurar @media print y NO llamar emulateMediaType — pero eso requeriría modificar PdfService (no hay opción para suprimirlo).
networkidle0
Section titled “networkidle0”Ambos métodos esperan networkidle0 (no requests en flight por 500ms). Para páginas con polling continuo (WebSocket abierto, telemetría, beacons), esto puede colgarse. Si encuentras timeout en una página específica, considerar:
- Servir una versión print-only sin polling.
- Cambiar a
networkidle2(≤2 conexiones activas) modificando el service.
Almacenamiento de PDFs
Section titled “Almacenamiento de PDFs”PdfService solo retorna el Buffer. La persistencia (e.g. guardar en storage/liquidaciones/) la decide el caller. La convención actual:
import path from 'path';import fs from 'fs/promises';
const dir = process.env.LIQUIDACIONES_DIR ?? './storage/liquidaciones';const filePath = path.join(dir, `${liquidacionId}.pdf`);await fs.writeFile(filePath, buffer);Servido luego por /files/liquidaciones/<id>.pdf con Cache-Control: public, max-age=31536000, immutable (ver Orchestrator › Static files).