Generar PDFs con JavaScript
Aprende a crear PDFs dinámicos en el navegador: facturas, reportes, certificados y más, sin necesidad de backend.
1. ¿Por qué generar PDFs con JavaScript?
Ventajas
- ✓ Sin backend: Se genera en el navegador del usuario
- ✓ Descarga inmediata: El usuario descarga el PDF al momento
- ✓ Privacidad: Los datos no se envían a un servidor
- ✓ Económico: Sin coste de procesamiento en servidor
Casos de uso
- Facturas y recibos
- Reportes y análisis
- Certificados y diplomas
- Presupuestos y cotizaciones
2. Librerías para generar PDFs
jsPDF
Ligera y fácil de usar - RECOMENDADA para principiantes
Instalación:
npm install jspdf - ✓ Muy ligera (~200KB)
- ✓ Sintaxis simple
- ✓ Soporta texto, imágenes, tablas
- ✗ Menos opciones de estilo que HTML2PDF
html2pdf
Convierte HTML a PDF - MEJOR para layouts complejos
Instalación:
npm install html2pdf.js - ✓ Usa HTML/CSS existente
- ✓ Más flexible con estilos
- ✓ Genera PDFs complejos
- ✗ Más pesada (~700KB)
PDFKit (Node.js)
Para backend con Node.js
Instalación:
npm install pdfkit - ✓ Control total del PDF
- ✓ Se ejecuta en servidor
- ✗ No funciona en el navegador
💡 Recomendación: Usaremos jsPDF para este tutorial por su simplicidad y porque genera PDFs directamente en el navegador sin dependencias de HTML.
2.5. Comparativa: html2pdf.js vs PDFMake
| Característica | html2pdf.js | PDFMake |
|---|---|---|
| Entrada | HTML/CSS existente | Definición JSON |
| Tamaño (minificado) | ~700KB | ~400KB |
| Curva de aprendizaje | Muy fácil (usas HTML) | Media (sintaxis específica) |
| Control sobre layout | Alto (todo CSS) | Muy alto (granular) |
| Tablas | ✓ Soporte completo | ✓ Muy potente |
| Imágenes | ✓ Directo desde HTML | ✓ Base64 o URL |
| Múltiples páginas | ✓ Automático | ✓ Manual con control |
| Estilos avanzados | ⚠ Limitado vs CSS | ✓ Muy flexible |
| Watermarks | ✗ Complicado | ✓ Fácil |
| Encriptación | ✗ No soportado | ✓ Soportado |
| Mejor para... | Convertir HTML → PDF fácilmente | PDFs complejos y profes. |
html2pdf.js - Usa cuando:
- → Ya tienes HTML y CSS diseñados
- → Necesitas convertir rápidamente a PDF
- → El layout es sencillo/estándar
- → Prefieres curva de aprendizaje mínima
PDFMake - Usa cuando:
- → Necesitas control total sobre el PDF
- → Requieres estilos avanzados (watermarks, etc.)
- → Generas PDFs profesionales frecuentemente
- → Necesitas características como encriptación
3. Ejemplo básico: Factura simple
Explicación de argumentos y parámetros
1// Primero instala: npm install jspdf2 3import { jsPDF } from 'jspdf';4 5function generarFactura() {6 // 1. Crear documento (A4, milímetros)7 const doc = new jsPDF();8 9 // 2. Títulos y contenido10 doc.setFontSize(20);11 doc.text('FACTURA', 105, 20, { align: 'center' });12 13 doc.setFontSize(12);14 doc.text('Datos de la empresa:', 10, 40);15 doc.text('Mi Empresa S.A.', 10, 50);16 doc.text('CIF: A12345678', 10, 60);17 doc.text('Teléfono: 666 777 888', 10, 70);18 19 // 3. Datos del cliente20 doc.setFontSize(12);21 doc.text('Cliente:', 10, 90);22 doc.text('Juan Pérez', 10, 100);23 doc.text('Email: juan@ejemplo.com', 10, 110);24 25 // 4. Tabla de items26 doc.setFontSize(11);27 doc.text('Concepto', 10, 130);28 doc.text('Cantidad', 80, 130);29 doc.text('Precio', 120, 130);30 doc.text('Total', 160, 130);31 32 // Línea separadora33 doc.line(10, 135, 200, 135);34 35 // Items36 doc.text('Producto A', 10, 145);37 doc.text('2', 85, 145);38 doc.text('50€', 120, 145);39 doc.text('100€', 160, 145);40 41 doc.text('Producto B', 10, 155);42 doc.text('1', 85, 155);43 doc.text('75€', 120, 155);44 doc.text('75€', 160, 155);45 46 // Total47 doc.setFontSize(14);48 doc.setFont(undefined, 'bold');49 doc.text('TOTAL: 175€', 160, 175);50 51 // 5. Descargar PDF52 doc.save('factura.pdf');53}54 55// Usar en HTML56// <button onclick="generarFactura()">Descargar Factura PDF</button>📝 Nota: Este es un ejemplo básico. Los PDFs generados tendrán un aspecto simple pero funcional.
4. Ejemplo mejorado: Factura con datos dinámicos
Explicación de nuevos conceptos
1import { jsPDF } from 'jspdf';2 3function generarFacturaDinamica(datos) {4 const doc = new jsPDF();5 6 // Estilos7 const MARGIN = 10;8 const PAGE_WIDTH = doc.internal.pageSize.getWidth();9 const PAGE_HEIGHT = doc.internal.pageSize.getHeight();10 let Y_POSITION = 20;11 12 // Función auxiliar para espacios13 const salto = (espacios = 1) => {14 Y_POSITION += espacios * 7;15 };16 17 // Encabezado18 doc.setFontSize(18);19 doc.setTextColor(100, 50, 200); // Morado20 doc.text('FACTURA', PAGE_WIDTH / 2, Y_POSITION, { align: 'center' });21 doc.setTextColor(0);22 23 salto(2);24 doc.setFontSize(10);25 doc.text(`Número: ${datos.numero}`, PAGE_WIDTH - MARGIN, Y_POSITION, { align: 'right' });26 doc.text(`Fecha: ${datos.fecha}`, PAGE_WIDTH - MARGIN, Y_POSITION + 7, { align: 'right' });27 28 salto(3);29 30 // Datos empresa31 doc.setFontSize(11);32 doc.setFont(undefined, 'bold');33 doc.text('De:', MARGIN, Y_POSITION);34 35 doc.setFont(undefined, 'normal');36 doc.text(datos.empresa.nombre, MARGIN, Y_POSITION + 7);37 doc.setFontSize(9);38 doc.text(datos.empresa.direccion, MARGIN, Y_POSITION + 14);39 doc.text(`CIF: ${datos.empresa.cif}`, MARGIN, Y_POSITION + 21);40 41 // Datos cliente42 doc.setFontSize(11);43 doc.setFont(undefined, 'bold');44 doc.text('Para:', PAGE_WIDTH / 2, Y_POSITION);45 46 doc.setFont(undefined, 'normal');47 doc.text(datos.cliente.nombre, PAGE_WIDTH / 2, Y_POSITION + 7);48 doc.setFontSize(9);49 doc.text(datos.cliente.email, PAGE_WIDTH / 2, Y_POSITION + 14);50 51 salto(6);52 53 // Tabla de items54 doc.setFontSize(10);55 doc.setFont(undefined, 'bold');56 57 // Encabezados58 doc.text('Descripción', MARGIN, Y_POSITION);59 doc.text('Cant.', MARGIN + 100, Y_POSITION);60 doc.text('Precio', MARGIN + 125, Y_POSITION);61 doc.text('Total', MARGIN + 155, Y_POSITION);62 63 // Línea64 doc.line(MARGIN, Y_POSITION + 3, PAGE_WIDTH - MARGIN, Y_POSITION + 3);65 66 salto(2);67 doc.setFont(undefined, 'normal');68 69 // Items dinámicos70 let subtotal = 0;71 datos.items.forEach(item => {72 const total = item.cantidad * item.precio;73 subtotal += total;74 75 doc.text(item.descripcion, MARGIN, Y_POSITION);76 doc.text(String(item.cantidad), MARGIN + 105, Y_POSITION);77 doc.text(`${item.precio}€`, MARGIN + 130, Y_POSITION);78 doc.text(`${total}€`, MARGIN + 160, Y_POSITION);79 80 salto(2);81 });82 83 // Línea final84 doc.line(MARGIN, Y_POSITION - 3, PAGE_WIDTH - MARGIN, Y_POSITION - 3);85 86 salto(2);87 88 // Cálculos89 doc.setFont(undefined, 'bold');90 doc.text('Subtotal:', MARGIN + 120, Y_POSITION);91 doc.text(`${subtotal}€`, MARGIN + 160, Y_POSITION);92 93 salto();94 const iva = subtotal * 0.21;95 doc.text('IVA (21%):', MARGIN + 120, Y_POSITION);96 doc.text(`${iva.toFixed(2)}€`, MARGIN + 160, Y_POSITION);97 98 salto();99 const total = subtotal + iva;100 doc.setTextColor(100, 50, 200);101 doc.setFontSize(12);102 doc.text('TOTAL:', MARGIN + 120, Y_POSITION);103 doc.text(`${total.toFixed(2)}€`, MARGIN + 160, Y_POSITION);104 105 // Descargar106 doc.save(`factura-${datos.numero}.pdf`);107}108 109// Usar:110const datosFactura = {111 numero: 'FAC-2024-001',112 fecha: '2024-11-26',113 empresa: {114 nombre: 'Mi Empresa S.A.',115 direccion: 'Calle Principal 123, 28001 Madrid',116 cif: 'A12345678'117 },118 cliente: {119 nombre: 'Juan Pérez García',120 email: 'juan@ejemplo.com'121 },122 items: [123 { descripcion: 'Producto A', cantidad: 2, precio: 50 },124 { descripcion: 'Producto B', cantidad: 1, precio: 75 },125 { descripcion: 'Servicio C', cantidad: 5, precio: 20 }126 ]127};128 129generarFacturaDinamica(datosFactura);5. Usando html2pdf (para layouts complejos)
Explicación de opciones de html2pdf
1// npm install html2pdf.js2 3import html2pdf from 'html2pdf.js';4 5function generarFacturaHTML() {6 // 1. Crear el HTML de la factura7 const element = document.getElementById('factura-template');8 9 // 2. Opciones de configuración10 const opt = {11 margin: 10,12 filename: 'factura.pdf',13 image: { type: 'jpeg', quality: 0.98 },14 html2canvas: { scale: 2 },15 jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }16 };17 18 // 3. Generar y descargar19 html2pdf().set(opt).from(element).save();20}21 22// HTML a usar:23```html24<div id="factura-template" style="padding: 20px; font-family: Arial;">25 <h1 style="text-align: center; color: #6432c8;">FACTURA</h1>26 27 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 30px;">28 <div>29 <h3>De:</h3>30 <p><strong>Mi Empresa S.A.</strong></p>31 <p>Calle Principal 123</p>32 <p>CIF: A12345678</p>33 </div>34 35 <div>36 <h3>Para:</h3>37 <p><strong>Cliente XYZ</strong></p>38 <p>Email: cliente@ejemplo.com</p>39 <p style="color: #666;">Número: FAC-2024-001</p>40 </div>41 </div>42 43 <table style="width: 100%; margin-top: 30px; border-collapse: collapse;">44 <thead>45 <tr style="background: #f0f0f0; font-weight: bold;">46 <th style="padding: 10px; text-align: left;">Concepto</th>47 <th style="padding: 10px; text-align: center;">Cantidad</th>48 <th style="padding: 10px; text-align: right;">Precio</th>49 <th style="padding: 10px; text-align: right;">Total</th>50 </tr>51 </thead>52 <tbody>53 <tr style="border-bottom: 1px solid #ddd;">54 <td style="padding: 10px;">Producto A</td>55 <td style="padding: 10px; text-align: center;">2</td>56 <td style="padding: 10px; text-align: right;">50€</td>57 <td style="padding: 10px; text-align: right;">100€</td>58 </tr>59 <tr style="border-bottom: 1px solid #ddd;">60 <td style="padding: 10px;">Producto B</td>61 <td style="padding: 10px; text-align: center;">1</td>62 <td style="padding: 10px; text-align: right;">75€</td>63 <td style="padding: 10px; text-align: right;">75€</td>64 </tr>65 </tbody>66 </table>67 68 <div style="text-align: right; margin-top: 20px; font-weight: bold;">69 <p>Subtotal: 175€</p>70 <p>IVA (21%): 36,75€</p>71 <p style="color: #6432c8; font-size: 18px; margin-top: 10px;">TOTAL: 211,75€</p>72 </div>73</div>74```75 76// Botón en HTML:77// <button onclick="generarFacturaHTML()">Descargar PDF</button>✓ Ventaja: Con html2pdf puedes reutilizar HTML y CSS que ya tengas en tu proyecto. Perfecta para layouts complejos con tablas, imágenes, etc.
5.5. Usando PDFMake (Control total y profesional)
Explicación de estructura PDFMake
1// npm install pdfmake2 3import pdfMake from 'pdfmake/build/pdfmake';4import pdfFonts from 'pdfmake/build/vfs_fonts';5 6pdfMake.vfs = pdfFonts.pdfMake.vfs;7 8function generarFacturaPDFMake() {9 const docDefinition = {10 pageSize: 'A4',11 pageMargins: [40, 40, 40, 40],12 13 header: {14 text: 'FACTURA',15 alignment: 'center',16 fontSize: 20,17 bold: true,18 color: '#6432c8',19 margin: [0, 0, 0, 20]20 },21 22 content: [23 // Número y fecha24 {25 columns: [26 {27 text: [28 { text: 'Número: ', bold: true },29 'FAC-2024-001'30 ],31 width: '50%'32 },33 {34 text: [35 { text: 'Fecha: ', bold: true },36 new Date().toLocaleDateString('es-ES')37 ],38 width: '50%',39 alignment: 'right'40 }41 ],42 margin: [0, 0, 0, 20]43 },44 45 // Empresa y Cliente46 {47 columns: [48 {49 width: '50%',50 stack: [51 { text: 'De:', bold: true, fontSize: 12 },52 'Mi Empresa S.A.',53 'Calle Principal 123',54 'CIF: A12345678'55 ]56 },57 {58 width: '50%',59 stack: [60 { text: 'Para:', bold: true, fontSize: 12 },61 'Cliente XYZ',62 'Email: cliente@ejemplo.com',63 'Teléfono: 666 777 888'64 ]65 }66 ],67 margin: [0, 0, 0, 30]68 },69 70 // Tabla de items71 {72 table: {73 headerRows: 1,74 widths: ['40%', '15%', '15%', '15%'],75 body: [76 // Headers77 [78 { text: 'Concepto', bold: true, color: 'white', fillColor: '#6432c8' },79 { text: 'Cantidad', bold: true, color: 'white', fillColor: '#6432c8', alignment: 'center' },80 { text: 'Precio', bold: true, color: 'white', fillColor: '#6432c8', alignment: 'right' },81 { text: 'Total', bold: true, color: 'white', fillColor: '#6432c8', alignment: 'right' }82 ],83 // Items84 ['Producto A', { text: '2', alignment: 'center' }, { text: '50€', alignment: 'right' }, { text: '100€', alignment: 'right' }],85 ['Producto B', { text: '1', alignment: 'center' }, { text: '75€', alignment: 'right' }, { text: '75€', alignment: 'right' }],86 ['Servicio C', { text: '5', alignment: 'center' }, { text: '20€', alignment: 'right' }, { text: '100€', alignment: 'right' }]87 ]88 },89 margin: [0, 0, 0, 30]90 },91 92 // Totales93 {94 alignment: 'right',95 columns: [96 {97 width: '40%',98 stack: [99 { text: 'Subtotal:', margin: [0, 5, 0, 5] },100 { text: 'IVA (21%):', margin: [0, 5, 0, 5] },101 { text: 'TOTAL:', bold: true, fontSize: 14, color: '#6432c8', margin: [0, 10, 0, 5] }102 ]103 },104 {105 width: '20%',106 stack: [107 { text: '275€', margin: [0, 5, 0, 5] },108 { text: '57,75€', margin: [0, 5, 0, 5] },109 { text: '332,75€', bold: true, fontSize: 14, color: '#6432c8', margin: [0, 10, 0, 5] }110 ]111 }112 ]113 }114 ],115 116 footer: {117 text: 'Gracias por su compra',118 alignment: 'center',119 fontSize: 10,120 color: '#999',121 margin: [0, 20, 0, 0]122 }123 };124 125 // Generar y descargar126 pdfMake.createPdf(docDefinition).download('factura-2024-001.pdf');127}128 129// Usar:130// <button onclick="generarFacturaPDFMake()">Descargar PDF con PDFMake</button>🎯 Ventajas de este enfoque:
- ✓ Estilos aplicados directamente en el JSON
- ✓ Control pixel-perfect del layout
- ✓ Más ligero que html2pdf
- ✓ Fácil de generar PDFs desde datos dinámicos
- ✓ Soporte para watermarks, encriptación, etc.
6. Caso práctico: Generar factura en Astro
1---2// factura.astro3 4// Datos de ejemplo (podrían venir de una API)5const factura = {6 numero: 'FAC-2024-001',7 fecha: new Date().toLocaleDateString('es-ES'),8 empresa: {9 nombre: 'Mi Tienda Online',10 direccion: 'Calle Principal 123, Madrid',11 cif: 'A12345678'12 },13 cliente: {14 nombre: 'Juan Pérez García',15 email: 'juan@ejemplo.com'16 },17 items: [18 { descripcion: 'Laptop', cantidad: 1, precio: 999 },19 { descripcion: 'Mouse', cantidad: 2, precio: 25 },20 { descripcion: 'Teclado', cantidad: 1, precio: 79 }21 ]22};23 24const subtotal = factura.items.reduce((sum, item) => sum + (item.cantidad * item.precio), 0);25const iva = subtotal * 0.21;26const total = subtotal + iva;27---28 29<html>30<head>31 <title>Factura {factura.numero}</title>32</head>33<body>34 <div id="factura-contenido">35 <h1>FACTURA</h1>36 <p><strong>Número:</strong> {factura.numero}</p>37 <p><strong>Fecha:</strong> {factura.fecha}</p>38 39 <h2>Datos de Empresa</h2>40 <p>{factura.empresa.nombre}</p>41 <p>{factura.empresa.direccion}</p>42 43 <h2>Cliente</h2>44 <p>{factura.cliente.nombre}</p>45 <p>{factura.cliente.email}</p>46 47 <h2>Items</h2>48 <table border="1" cellpadding="10">49 <tr>50 <th>Concepto</th>51 <th>Cantidad</th>52 <th>Precio</th>53 <th>Total</th>54 </tr>55 {factura.items.map(item => (56 <tr>57 <td>{item.descripcion}</td>58 <td>{item.cantidad}</td>59 <td>{item.precio}€</td>60 <td>{item.cantidad * item.precio}€</td>61 </tr>62 ))}63 </table>64 65 <h2>Resumen</h2>66 <p><strong>Subtotal:</strong> {subtotal}€</p>67 <p><strong>IVA (21%):</strong> {iva.toFixed(2)}€</p>68 <p style="font-size: 18px; color: purple;">69 <strong>TOTAL: {total.toFixed(2)}€</strong>70 </p>71 </div>72 73 <button id="descargar-btn"> Descargar PDF</button>74 75 <script>76 import html2pdf from 'html2pdf.js';77 78 document.getElementById('descargar-btn').addEventListener('click', () => {79 const element = document.getElementById('factura-contenido');80 const opt = {81 margin: 10,82 filename: 'factura-001.pdf',83 image: { type: 'jpeg', quality: 0.98 },84 html2canvas: { scale: 2 },85 jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }86 };87 88 html2pdf().set(opt).from(element).save();89 });90 </script>91 92 <style>93 body { font-family: Arial; padding: 20px; }94 table { width: 100%; margin: 20px 0; }95 button {96 padding: 10px 20px;97 background: purple;98 color: white;99 border: none;100 border-radius: 5px;101 cursor: pointer;102 }103 </style>104</body>105</html>8. Consejos y mejores prácticas
📏 Tamaño de página
Usa A4 (210x297mm) por defecto. Es el estándar para facturas y documentos.
🎨 Márgenes
Establece márgenes de 10-15mm en todos los lados para que se vea profesional.
🖼️ Imágenes y logos
Convierte imágenes a base64 o usa URLs públicas. Asegúrate de que se vean bien en el PDF.
📱 Responsividad
El PDF siempre se genera con el mismo tamaño, pero asegúrate de que el contenido quepa en una página A4.
⚙️ Rendimiento
Para PDFs muy complejos, considera generar en el backend. En el navegador puede ser lento con documentos grandes.
✅ Testing
Prueba en diferentes navegadores. Las renderizaciones pueden variar entre Chrome, Firefox y Safari.
Resumen
Para ver como quedaria en la práctica
Aqui veras como queda con cada una de las librerias explicadas.
Ir a Demo Interactiva