Cómo Crear Reportes PDF a Partir de Datos de Base de Datos — TongoRender Blog
Volver al Blog
tutorialsreportsdatabaseautomation

Cómo Crear Reportes PDF a Partir de Datos de Base de Datos

Aprende a conectarte a una base de datos, consultar datos, construir un reporte HTML y convertirlo en un PDF profesional usando una API. Incluye ejemplos de código paso a paso en Node.js.

TongoRender Team18 de marzo de 202611 min

Generar reportes PDF a partir de datos de base de datos es una de las tareas más comunes en aplicaciones empresariales. Ya sea que estés construyendo un dashboard que exporta análisis mensuales, un CRM que produce resúmenes de clientes o un sistema ERP que imprime reportes de inventario, el flujo de trabajo es fundamentalmente el mismo: consultar la base de datos, transformar los resultados en HTML y renderizar ese HTML como PDF.

En este tutorial, recorreremos todo el proceso usando Node.js, PostgreSQL y la API HTML-a-PDF de TongoRender.

La Arquitectura

Antes de sumergirnos en el código, delineemos el flujo de datos:

  1. Consulta — Conéctate a tu base de datos y obtén los datos necesarios para el reporte.
  2. Transformación — Mapea los resultados de la consulta en una plantilla HTML con tablas, gráficos y estadísticas resumidas.
  3. Renderización — Envía el HTML a una API que lo convierte en PDF.
  4. Entrega — Guarda el PDF en disco, súbelo a almacenamiento en la nube o envíalo por correo al destinatario.

Paso 1: Configurando la Conexión a la Base de Datos

Usaremos la biblioteca pg para conectarnos a PostgreSQL:

npm install pg dotenv node-fetch
// db.js
const { Pool } = require('pg');
require('dotenv').config();

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
});

async function query(text, params) {
  const result = await pool.query(text, params);
  return result.rows;
}

module.exports = { query };

Paso 2: Consultando los Datos del Reporte

Supongamos que queremos un reporte mensual de ventas. Necesitamos ingresos totales, cantidad de pedidos y productos más vendidos:

const { query } = require('../db');

async function getMonthlySalesData(year, month) {
  const startDate = new Date(year, month - 1, 1);
  const endDate = new Date(year, month, 0);

  const [summary] = await query(
    `SELECT COUNT(*) as order_count,
            SUM(total) as revenue,
            AVG(total) as avg_order_value
     FROM orders
     WHERE created_at BETWEEN $1 AND $2`,
    [startDate, endDate]
  );

  const topProducts = await query(
    `SELECT p.name, SUM(oi.quantity) as units_sold, SUM(oi.subtotal) as revenue
     FROM order_items oi
     JOIN products p ON p.id = oi.product_id
     JOIN orders o ON o.id = oi.order_id
     WHERE o.created_at BETWEEN $1 AND $2
     GROUP BY p.name
     ORDER BY revenue DESC
     LIMIT 10`,
    [startDate, endDate]
  );

  return { summary, topProducts };
}

Paso 3: Construyendo la Plantilla HTML

function buildReportHTML(data, year, month) {
  const monthName = new Date(year, month - 1).toLocaleString('es', { month: 'long' });

  return `
  <!DOCTYPE html>
  <html lang="es">
  <head>
    <style>
      body { font-family: 'Helvetica Neue', sans-serif; color: #1a1a2e; margin: 40px; }
      h1 { color: #16213e; border-bottom: 3px solid #0f3460; padding-bottom: 10px; }
      .summary-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 20px 0; }
      .summary-card { background: #f0f4ff; border-radius: 8px; padding: 20px; text-align: center; }
      .summary-card .value { font-size: 2em; font-weight: 700; color: #0f3460; }
      table { width: 100%; border-collapse: collapse; margin: 20px 0; }
      th { background: #0f3460; color: white; padding: 10px; text-align: left; }
      td { padding: 10px; border-bottom: 1px solid #e0e0e0; }
    </style>
  </head>
  <body>
    <h1>Reporte de Ventas — ${monthName} ${year}</h1>
    <div class="summary-grid">
      <div class="summary-card">
        <div class="value">${data.summary.order_count}</div>
        <div>Total de Pedidos</div>
      </div>
      <div class="summary-card">
        <div class="value">${Number(data.summary.revenue).toLocaleString()}</div>
        <div>Ingresos</div>
      </div>
      <div class="summary-card">
        <div class="value">${Number(data.summary.avg_order_value).toFixed(2)}</div>
        <div>Valor Promedio del Pedido</div>
      </div>
    </div>

    <h2>Productos Más Vendidos</h2>
    <table>
      <thead><tr><th>Producto</th><th>Unidades</th><th>Ingresos</th></tr></thead>
      <tbody>
        ${data.topProducts.map(p => \`
          <tr><td>${p.name}</td><td>${p.units_sold}</td><td>${Number(p.revenue).toLocaleString()}</td></tr>
        \`).join('')}
      </tbody>
    </table>
  </body>
  </html>`;
}

Paso 4: Convirtiendo a PDF con TongoRender

async function generateReport(year, month) {
  const data = await getMonthlySalesData(year, month);
  const html = buildReportHTML(data, year, month);

  const response = await fetch('https://api.tongorender.io/v1/pdf', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.TONGORENDER_API_KEY,
    },
    body: JSON.stringify({
      html,
      format: 'A4',
      margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
    }),
  });

  if (!response.ok) throw new Error(`Error de API: ${response.statusText}`);
  return Buffer.from(await response.arrayBuffer());
}

Programando Reportes con Cron

const cron = require('node-cron');

cron.schedule('0 8 1 * *', async () => {
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth();
  const pdf = await generateReport(year, month);
  await uploadToS3(pdf, `reports/${year}-${String(month).padStart(2, '0')}.pdf`);
  await sendEmail('finanzas@empresa.com', 'Reporte Mensual de Ventas', pdf);
});

Consejos para Reportes de Calidad

  • Maneja datos vacíos con elegancia — Muestra mensajes "Sin datos disponibles" en lugar de tablas vacías.
  • Formatea números consistentemente — Usa Intl.NumberFormat para moneda y números grandes.
  • Agrega timestamps — Incluye la fecha de generación y el rango de tiempo en el encabezado del reporte.
  • Usa pool de conexionespg.Pool gestiona conexiones eficientemente para solicitudes concurrentes.

TongoRender hace que la etapa de renderización de PDF sea sin esfuerzo. Concéntrate en consultar los datos correctos y diseñar una plantilla clara — la API se encarga del resto.

Comienza a generar reportes con TongoRender — 100 renderizaciones gratuitas al mes, sin necesidad de tarjeta de crédito.

Comparte este artículoCompartir en Twitter