javascript

Factura PDF con Node.js

En este post de programación con JavaScript del lado del servidor con Node.js y un poco de express te mostraré cómo crear una factura, ticket o recibo PDF usando estas tecnologías.

Voy a enseñarte dos ejemplos. En el primero verás cómo crear un recibo PDF y guardarlo en el almacenamiento, mientras que en el segundo ejemplo te enseñaré mostrar el ticket PDF con Express.

Esto solo es una continuación a mi post de cómo crear un PDF con Node. Recuerda que tú eres libre de cambiar los estilos y modificar la plantilla, lo que aquí te muestro es mi ejemplo.

Diseño de la plantilla

Diseño de plantilla de factura HTML para convertir a PDF con Node

Primero vamos a diseñar la plantilla HTML y después le vamos a pasar los datos desde Node, específicamente le pasaremos la lista de productos y los totales.

Al inicio se ve así:

<!DOCTYPE html>
<html lang="es">

<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
        integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Factura</title>
</head>

<body>
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-10 ">
                <h1>Factura</h1>
            </div>
            <div class="col-xs-2">
                <img class="img img-responsive" src="https://github.com/parzibyte.png" alt="Logotipo">
            </div>
        </div>
        <hr>
        <div class="row">
            <div class="col-xs-10">
                <h1 class="h6">Luis Cabrera Benito</h1>
                <h1 class="h6">https://parzibyte.me/blog</h1>
            </div>
            <div class="col-xs-2 text-center">
                <strong>Fecha</strong>
                <br>
                2021-05-03 <br>
                <strong>Factura No.</strong>
                <br>
                1
            </div>
        </div>
        <hr>
        <div class="row text-center" style="margin-bottom: 2rem;">
            <div class="col-xs-6">
                <h1 class="h2">Cliente</h1>
                <strong>Luis Cabrera Benito</strong>
            </div>
            <div class="col-xs-6">
                <h1 class="h2">Remitente</h1>
                <strong>Luis Cabrera Benito</strong>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12">
                <table class="table table-condensed table-bordered table-striped">
                    <thead>
                        <tr>
                            <th>Descripción</th>
                            <th>Cantidad</th>
                            <th>Precio unitario</th>
                            <th>Total</th>
                        </tr>
                    </thead>
                    <tbody>
                        {{tablaProductos}}
                    </tbody>
                    <tfoot>
                        <tr>
                            <td colspan="3" class="text-right">Subtotal</td>
                            <td>{{subtotal}}</td>
                        </tr>
                        <tr>
                            <td colspan="3" class="text-right">Descuento</td>
                            <td>{{descuento}}</td>
                        </tr>
                        <tr>
                            <td colspan="3" class="text-right">Subtotal con descuento</td>
                            <td>{{subtotalConDescuento}}</td>
                        </tr>
                        <tr>
                            <td colspan="3" class="text-right">Impuestos</td>
                            <td>{{impuestos}}</td>
                        </tr>
                        <tr>
                            <td colspan="3" class="text-right">
                                <h4>Total</h4>
                            </td>
                            <td>
                                <h4>{{total}}</h4>
                            </td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12 text-center">
                <p class="h5">Gracias por su compra</p>
            </div>
        </div>
    </div>
</body>

</html>

Si te fijas estoy usando el framework Bootstrap 3 para demostrar que podemos usar cualquier hoja de estilos. Obviamente puedes usar cualquier hoja de estilos  o no usar estilos.

También fíjate en que estoy colocando variables como {{total}} o {{tablaProductos}}, mismas que vamos a pasar desde JavaScript del lado del servidor más adelante.

Creando factura con Node

Factura PDF creada con Node.js, JavaScript y html-pdf

Ahora simplemente vamos a leer el HTML de la plantilla, pasarle los valores (que al final será HTML) y luego convertirla a PDF. Te repito que aquí estoy suponiendo que ya leíste mi otro post sobre la creación de PDFs con JS.

/**
 * https://parzibyte.me/blog
 */const pdf = require("html-pdf");
const fs = require("fs");
const ubicacionPlantilla = require.resolve("./factura.html");
let contenidoHtml = fs.readFileSync(ubicacionPlantilla, 'utf8')
// Estos productos podrían venir de cualquier lugar
const productos = [
    {
        descripcion: "Nintendo Switch",
        cantidad: 2,
        precio: 9000,
    },
    {
        descripcion: "Videojuego: Hollow Knight",
        cantidad: 1,
        precio: 150,
    },
    {
        descripcion: "Audífonos HyperX",
        cantidad: 5,
        precio: 1500,
    },
];

// Nota: el formateador solo es para, valga la redundancia, formatear el dinero. No es requerido, solo que quiero que se vea bonito
// https://parzibyte.me/blog/2021/05/03/formatear-dinero-javascript/
const formateador = new Intl.NumberFormat("en", { style: "currency", "currency": "MXN" });
// Generar el HTML de la tabla
let tabla = "";
let subtotal = 0;
for (const producto of productos) {
    // Aumentar el total
    const totalProducto = producto.cantidad * producto.precio;
    subtotal += totalProducto;
    // Y concatenar los productos
    tabla += `<tr>
    <td>${producto.descripcion}</td>
    <td>${producto.cantidad}</td>
    <td>${formateador.format(producto.precio)}</td>
    <td>${formateador.format(totalProducto)}</td>
    </tr>`;
}
const descuento = 0;
const subtotalConDescuento = subtotal - descuento;
const impuestos = subtotalConDescuento * 0.16
const total = subtotalConDescuento + impuestos;
// Remplazar el valor {{tablaProductos}} por el verdadero valor
contenidoHtml = contenidoHtml.replace("{{tablaProductos}}", tabla);

// Y también los otros valores

contenidoHtml = contenidoHtml.replace("{{subtotal}}", formateador.format(subtotal));
contenidoHtml = contenidoHtml.replace("{{descuento}}", formateador.format(descuento));
contenidoHtml = contenidoHtml.replace("{{subtotalConDescuento}}", formateador.format(subtotalConDescuento));
contenidoHtml = contenidoHtml.replace("{{impuestos}}", formateador.format(impuestos));
contenidoHtml = contenidoHtml.replace("{{total}}", formateador.format(total));
pdf.create(contenidoHtml).toFile("salida.pdf", (error) => {
    if (error) {
        console.log("Error creando PDF: " + error)
    } else {
        console.log("PDF creado correctamente");
    }
});

Los comentarios y el código en sí ayudan a explicar toda esta generación del recibo de compra creado con Node.

Primero leemos el contenido del HTML y creamos el HTML necesario de la tabla de productos, mismo que será incluido en la cadena HTML usando replace.

Luego calculamos el total, impuestos, descuentos, etcétera y los formateamos como moneda. Finalmente convertimos el PDF a un archivo e imprimimos si la operación fue correcta.

Aquí puedes ver el PDF que resulta: Factura de ejemplo creada con Node.js

Sirviendo factura con Express

Para terminar este tutorial te voy a enseñar cómo mostrar esta factura PDF creada con Node.js directamente en el navegador usando Express. En lugar de invocar a toFile, invocamos a toStream así:

/**
 * Tomado de https://parzibyte.me/blog/2019/05/27/node-js-express-ejemplo-creacion-proyecto/
 * By Parzibyte
 */// Importar dependencias
const express = require("express"),
    app = express(),
    pdf = require("html-pdf"),
    fs = require("fs");

// Constantes propias del programa
const ubicacionPlantilla = require.resolve("./factura.html"),
    puerto = 3000;
let contenidoHtml = fs.readFileSync(ubicacionPlantilla, 'utf8')
// Estos productos podrían venir de cualquier lugar
const productos = [
    {
        descripcion: "Nintendo Switch",
        cantidad: 2,
        precio: 9000,
    },
    {
        descripcion: "Videojuego: Hollow Knight",
        cantidad: 1,
        precio: 150,
    },
    {
        descripcion: "Audífonos HyperX",
        cantidad: 5,
        precio: 1500,
    },
];

// Nota: el formateador solo es para, valga la redundancia, formatear el dinero. No es requerido, solo que quiero que se vea bonito
// https://parzibyte.me/blog/2021/05/03/formatear-dinero-javascript/
const formateador = new Intl.NumberFormat("en", { style: "currency", "currency": "MXN" });
// Definir rutas
app.get('/', (peticion, respuesta) => {
    // Generar el HTML de la tabla
    let tabla = "";
    let subtotal = 0;
    for (const producto of productos) {
        // Aumentar el total
        const totalProducto = producto.cantidad * producto.precio;
        subtotal += totalProducto;
        // Y concatenar los productos
        tabla += `<tr>
    <td>${producto.descripcion}</td>
    <td>${producto.cantidad}</td>
    <td>${formateador.format(producto.precio)}</td>
    <td>${formateador.format(totalProducto)}</td>
    </tr>`;
    }
    const descuento = 0;
    const subtotalConDescuento = subtotal - descuento;
    const impuestos = subtotalConDescuento * 0.16
    const total = subtotalConDescuento + impuestos;
    // Remplazar el valor {{tablaProductos}} por el verdadero valor
    contenidoHtml = contenidoHtml.replace("{{tablaProductos}}", tabla);

    // Y también los otros valores

    contenidoHtml = contenidoHtml.replace("{{subtotal}}", formateador.format(subtotal));
    contenidoHtml = contenidoHtml.replace("{{descuento}}", formateador.format(descuento));
    contenidoHtml = contenidoHtml.replace("{{subtotalConDescuento}}", formateador.format(subtotalConDescuento));
    contenidoHtml = contenidoHtml.replace("{{impuestos}}", formateador.format(impuestos));
    contenidoHtml = contenidoHtml.replace("{{total}}", formateador.format(total));
    pdf.create(contenidoHtml).toStream((error, stream) => {
        if (error) {
            respuesta.end("Error creando PDF: " + error)
        } else {
            respuesta.setHeader("Content-Type", "application/pdf");
            stream.pipe(respuesta);
        }
    });
});

// Una vez definidas nuestras rutas podemos iniciar el servidor
app.listen(puerto, err => {
    if (err) {
        // Aquí manejar el error
        console.error("Error escuchando: ", err);
        return;
    }
    // Si no se detuvo arriba con el return, entonces todo va bien ;)
    console.log(`Escuchando en el puerto :${puerto}`);
});

Ahora al ejecutar el script y visitar localhost:3000 tenemos el PDF mostrado:

Mostrar factura PDF en navegador usando Node.js y Express

Poniendo todo junto

Ya te mostré el código, pero si quieres tener el código completo incluyendo al package.json para que puedas instalar las dependencias con un npm install te lo dejo en GitHub.

Igualmente te dejo con más posts de JavaScript.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.