Skip to content

Arquitectura de Informes: Patrón Datos/Render

Módulo: Shared/General Tipo: Architecture Pattern Estado: Implementado Fecha: 2025-12-12


Descripción

Los informes del sistema siguen un patrón de arquitectura que separa la obtención de datos del renderizado visual. Esta separación permite reutilizar la lógica de datos para diferentes formatos de salida (PDF, Excel) sin duplicar código.


Problema que resuelve

Sin separación (antipatrón)

php
// informe-ventas.php - Todo mezclado
<?php
// Obtención de datos
$sql = "SELECT * FROM ventas...";
$datos = $conn->query($sql)->fetchAll();

// Procesamiento
foreach ($datos as &$dato) {
    $dato['total'] = $dato['cantidad'] * $dato['precio'];
}
?>
<!-- HTML mezclado con lógica -->
<table>
    <?php foreach ($datos as $fila): ?>
        <tr>
            <td><?= $fila['fecha'] ?></td>
            <td><?= $fila['total'] ?></td>
        </tr>
    <?php endforeach; ?>
</table>

Problemas:

  • Para Excel, hay que duplicar toda la obtención de datos
  • Difícil de mantener y testear
  • Lógica de negocio mezclada con presentación

Con separación (patrón recomendado)

reports/mi-modulo/
├── mi-informe-datos.php    # Solo obtención y procesamiento de datos
└── mi-informe-render.php   # Solo HTML/presentación

Estructura del patrón

Archivo -datos.php

Contiene:

  • Consultas SQL
  • Procesamiento de datos
  • Cálculos y transformaciones
  • Preparación de headers/footers
  • Variables que serán usadas por el render

NO contiene:

  • HTML
  • Lógica de presentación
  • Estilos CSS
php
// mi-informe-datos.php
<?php

// Consultas SQL
$sql = "SELECT fecha, cliente, monto FROM ventas WHERE fecha >= :desde";
$stmt = $conn->prepare($sql);
$stmt->execute([':desde' => $fechaDesde]);
$ventas = $stmt->fetchAll(PDO::FETCH_ASSOC);

// Procesamiento de datos
$totalGeneral = 0;
foreach ($ventas as &$venta) {
    $venta['monto_formateado'] = number_format($venta['monto'], 2, ',', '.');
    $totalGeneral += $venta['monto'];
}

// Preparación de encabezados para el informe
$HEADER = createHeader($empresa, null, null, null, [
    ['texto' => 'Informe de Ventas', 'pos' => 'center', 'tipo' => 'titulo'],
    ['texto' => "Desde: $fechaDesde", 'pos' => 'start'],
]);

$FOOTER = createFooter($pieEmpresa);

// $ventas y $totalGeneral quedan disponibles para el render

Archivo -render.php

Contiene:

  • Estructura HTML
  • Tablas y elementos visuales
  • Referencias a las variables preparadas en datos
  • CSS inline si es necesario

NO contiene:

  • Consultas SQL
  • Lógica de negocio compleja
  • Conexiones a base de datos
php
// mi-informe-render.php
<?php // Solo HTML ?>
<?= $HEADER ?>

<table class="tabla-datos">
    <thead>
        <tr>
            <th>Fecha</th>
            <th>Cliente</th>
            <th>Monto</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($ventas as $venta): ?>
        <tr>
            <td><?= $venta['fecha'] ?></td>
            <td><?= $venta['cliente'] ?></td>
            <td class="text-right"><?= $venta['monto_formateado'] ?></td>
        </tr>
        <?php endforeach; ?>
    </tbody>
    <tfoot>
        <tr>
            <td colspan="2">Total</td>
            <td class="text-right"><?= number_format($totalGeneral, 2, ',', '.') ?></td>
        </tr>
    </tfoot>
</table>

<?= $FOOTER ?>

Integración en index.php

Solo PDF

php
case 'mi-informe':
    // Obtención de datos
    include 'reports/mi-modulo/mi-informe-datos.php';

    // Renderizado HTML
    ob_start();
    include 'reports/mi-modulo/mi-informe-render.php';
    $html = ob_get_clean();

    // Generación PDF
    $html = formatearPagina($html, TipoPagina::A4);
    $pdfGenerator = new PdfGeneratorService();
    $result = $pdfGenerator->generatePdf($html);

    header('Content-Type: application/pdf');
    header(formatNameFile('mi-informe'));
    echo $result;
    break;

PDF + Excel

php
case 'mi-informe':
    // Obtención de datos (siempre se ejecuta)
    include 'reports/mi-modulo/mi-informe-datos.php';

    if (debeGenerarExcel($array)) {
        // Excel: usa directamente los datos sin renderizar HTML
        $encabezado = [
            ['nombre' => 'Fecha', 'tipo_filtro' => 'fecha'],
            ['nombre' => 'Cliente', 'tipo_filtro' => null],
            ['nombre' => 'Monto', 'tipo_filtro' => null],
        ];

        // $ventas viene del archivo -datos.php
        $cuerpo = array_map(fn($v) => [
            $v['fecha'],
            $v['cliente'],
            $v['monto']
        ], $ventas);

        $content = crearExcel($encabezado, $cuerpo);
        enviarExcel($content, 'mi-informe');
    } else {
        // PDF: necesita el render HTML
        ob_start();
        include 'reports/mi-modulo/mi-informe-render.php';
        $html = ob_get_clean();

        $html = formatearPagina($html, TipoPagina::A4);
        $pdfGenerator = new PdfGeneratorService();
        $result = $pdfGenerator->generatePdf($html);

        header('Content-Type: application/pdf');
        header(formatNameFile('mi-informe'));
        echo $result;
    }
    break;

Beneficios del patrón

1. Reutilización de código

FormatoArchivo datosArchivo render
PDF✓ Usa✓ Usa
Excel✓ Usa✗ No necesita

2. Mantenibilidad

  • Cambios en la lógica de datos se aplican a ambos formatos
  • Cambios visuales solo afectan al render
  • Más fácil de debuggear

3. Performance

  • Excel no ejecuta código de renderizado HTML innecesario
  • Menos procesamiento para exportaciones de datos

4. Testabilidad

  • Se puede testear la obtención de datos independientemente
  • Se puede validar el HTML generado por separado

Ejemplos existentes en el proyecto

Histórico de Ventas

reports/mod-ventas/historico-ventas/
├── historico-ventas-datos.php  # Obtención y procesamiento
├── historico-articulos.php     # Render por artículos
└── historico-clientes.php      # Render por clientes

Comprobantes

reports/mod-ventas/comprobantes/
├── comprobantes-datos.php      # Obtención de datos
└── comprobantes-render.php     # Renderizado HTML

Comisiones

reports/mod-ventas/comisiones/
├── comisiones-datos.php        # Obtención de datos
└── comisiones-render.php       # Renderizado HTML

Convenciones de nomenclatura

Tipo de archivoSufijoEjemplo
Datos-datos.phpventas-datos.php
Render HTML-render.phpventas-render.php
Render alternativoDescriptivoventas-resumen.php

Cuándo aplicar este patrón

Aplicar cuando:

  • El informe soportará múltiples formatos (PDF + Excel)
  • El informe tiene lógica de datos compleja
  • Se prevé reutilización de la lógica de datos
  • El informe tiene múltiples variantes visuales

No necesario cuando:

  • Informe muy simple (pocas líneas de código)
  • Solo formato PDF sin planes de Excel
  • Comprobantes con formato fijo (facturas, recibos)

Diagrama de flujo

                    Request


                ┌─────────────┐
                │  index.php  │
                │   switch    │
                └──────┬──────┘


            ┌──────────────────────┐
            │  include             │
            │  {informe}-datos.php │
            └──────────┬───────────┘


              ┌────────────────┐
              │ debeGenerarExcel? │
              └────────┬───────┘

           ┌───────────┴───────────┐
           │ No                    │ Si
           ▼                       ▼
   ┌───────────────┐      ┌───────────────┐
   │ include       │      │ crearExcel()  │
   │ {}-render.php │      │               │
   └───────┬───────┘      └───────┬───────┘
           │                      │
           ▼                      ▼
   ┌───────────────┐      ┌───────────────┐
   │ formatearPagina│     │ enviarExcel() │
   │ generatePdf() │      │               │
   └───────┬───────┘      └───────┬───────┘
           │                      │
           ▼                      ▼
   ┌───────────────┐      ┌───────────────┐
   │ Response PDF  │      │ Response XLSX │
   └───────────────┘      └───────────────┘

Relación con otras funcionalidades


Historial de cambios

FechaVersiónAutorDescripción
2025-12-121.0SistemaDocumentación del patrón existente