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

Servidor HTTP en Android con Flutter

El día de hoy te mostraré cómo crear un servidor HTTP (servidor web) en Android…

3 días hace

Imprimir automáticamente todos los PDF de una carpeta

En este post te voy a enseñar a designar una carpeta para imprimir todos los…

4 días hace

Guía para imprimir en plugin versión 1 desde Android

En este artículo te voy a enseñar la guía para imprimir en una impresora térmica…

1 semana hace

Añadir tasa de cambio en sistema de información

Hoy te voy a mostrar un ejemplo de programación para agregar un módulo de tasa…

2 semanas hace

Comprobar validez de licencia de plugin ESC POS

Los usuarios del plugin para impresoras térmicas pueden contratar licencias, y en ocasiones me han…

2 semanas hace

Imprimir euro € en impresora térmica

Hoy voy a enseñarte cómo imprimir el € en una impresora térmica. Vamos a ver…

3 semanas hace

Esta web usa cookies.