Mostrar e imprimir el documento
Qué hacer con el HTML que devuelve un documento dinámico: incrustarlo en tu web dentro de un iframe o convertirlo en PDF con un navegador headless, sin que se queden las variables sin sustituir.
Ya tienes el documento generado: llamaste a POST /api/dynamicdocuments, lo exportaste desde Dinaup o lo recibiste por email. Lo que tienes en la mano es una página HTML completa. Esta página explica qué hacer con ella para verla en tu web o convertirla en PDF.
curl -X POST "https://webhook.dinaup.com/api/dynamicdocuments?id=<id-del-documento>" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "factura": "<id-de-la-factura>" }' \
-o factura.htmlLo único importante: el HTML llega vivo
Una plantilla de impresión no es HTML plano. Como cuenta Tecnologías compatibles, las plantillas se apoyan en Bootstrap, Vue y Google Charts, que se cargan desde internet y se ejecutan en el navegador. La plantilla de facturas es justo así: el fichero que recibes trae los datos y las instrucciones para pintarlos, pero el dibujo lo hace el navegador.
La consecuencia práctica es una sola, y de ella salen todos los errores típicos:
Hasta que un navegador ejecute el JavaScript, no hay factura
Si abres el HTML en algo que no ejecuta JavaScript, verás la plantilla en crudo: los huecos de datos aparecen como {{ ... }} sin sustituir, o directamente no aparece nada. No es que el documento se haya generado mal — es que todavía no ha terminado de montarse.
Así que solo hay dos destinos válidos, y los dos pasan por un navegador de verdad: incrustarlo en un iframe o pasarlo por un navegador headless para sacar el PDF.
Mostrarlo en tu web: siempre en un iframe
No lo metas con innerHTML
elemento.innerHTML = html nunca va a funcionar. Los navegadores no ejecutan las etiquetas <script> insertadas de esa forma, así que Vue no arranca y te quedas con la plantilla cruda. Además el documento trae su propio <style>, que se te mezclaría con el CSS de tu web.
Un documento es una página entera, así que necesita su propio marco. Tienes dos formas de dárselo, y la diferencia entre ellas no es si funciona — las dos pintan el documento igual de bien — sino cuánto acceso le das a tu página.
Aislado (srcdoc + sandbox) | Mismo origen (blob:) | |
|---|---|---|
| El documento se pinta | ✅ | ✅ |
| Puedes medirlo o imprimirlo desde fuera | ❌ | ✅ |
| El documento puede leer tu página (sesión, cookies) | ❌ | ⚠️ sí |
| Cuándo usarlo | El documento viene de otra empresa, otro tenant o de datos que no controlas | Es tu Dinaup dentro de tu propia aplicación, y necesitas imprimir o ajustar el alto |
Opción A · Aislado
La opción por defecto. allow-scripts es lo que deja arrancar a Vue; al no incluir allow-same-origin, el documento vive en un origen aparte y no puede tocar tu página.
<iframe id="doc" sandbox="allow-scripts" style="width:100%;height:100vh;border:0"></iframe>
<script>
const respuesta = await fetch('/mi-backend/factura/123'); // tu backend llama a Dinaup
document.getElementById('doc').srcdoc = await respuesta.text();
</script>El sandbox vacío no vale
sandbox="" no significa «protección normal»: significa todos los permisos apagados, empezando por ejecutar JavaScript. Es la causa número uno de que un documento aparezca con los {{ }} a la vista. Y añadir allow-same-origin junto a allow-scripts anula el aislamiento por completo: si vas a hacer eso, usa directamente la opción B.
Opción B · Mismo origen
Conviertes el HTML en un fichero temporal del navegador y se lo das al iframe por su src. El documento queda en tu mismo origen, así que sí puedes medir su altura o lanzar la impresión desde fuera. Llama a la clave API solo desde tu backend.
const marco = document.getElementById('doc');
const url = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
marco.onload = () => URL.revokeObjectURL(url); // libera la memoria del fichero temporal
marco.src = url;Y a partir de ahí, con el documento ya cargado:
marco.contentWindow.print(); // abrir el diálogo de impresión
marco.style.height = marco.contentDocument.body.scrollHeight + 'px'; // ajustar el altoConvertirlo en PDF
Para archivar la factura o adjuntarla a un email necesitas un PDF. Vale cualquier navegador headless — Puppeteer o Playwright en tu servidor, o un servicio de render como Cloudflare Browser Rendering, Browserless o similar — pero en todos hay que cumplir tres condiciones, o el PDF sale mal:
Las tres condiciones
- Esperar a que el JavaScript termine. El PDF se captura cuando tú digas; si lo capturas al instante, congelas la página a medio montar y salen los
{{ }}. - Imprimir los fondos. Las plantillas usan colores de fondo en cabeceras y totales; sin esta opción salen en blanco.
- Tamaño A4. Las plantillas ya declaran
@page { size: A4 }; dale al navegador un lienzo acorde (794 × 1123 px a 96 ppp).
Con Puppeteer son cuatro líneas:
const pagina = await navegador.newPage();
await pagina.setViewport({ width: 794, height: 1123 });
await pagina.setContent(html, { waitUntil: 'networkidle0' }); // condición 1
const pdf = await pagina.pdf({ format: 'A4', printBackground: true }); // condiciones 2 y 3networkidle0 espera a que no queden descargas en curso, que es justo lo que necesitas: Bootstrap y Vue vienen de internet, y hasta que llegan y se ejecutan no hay factura. Si tu servicio de render no ofrece esa espera, casi todos aceptan una espera fija (uno o dos segundos) o esperar a que aparezca un elemento de la plantilla; cualquiera de las dos sirve.
Enviarlo por email
No hace falta que montes tú el correo: la propia plantilla puede fijar asunto, cuerpo, destinatario y nombre del adjunto. Está en Envío de emails.
Errores típicos
→ Crear documento dinámico → Plantilla de impresión de facturas → Envío de emails → Referencia del endpoint