Appearance
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ónEstructura 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 renderArchivo -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
| Formato | Archivo datos | Archivo render |
|---|---|---|
| ✓ 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 clientesComprobantes
reports/mod-ventas/comprobantes/
├── comprobantes-datos.php # Obtención de datos
└── comprobantes-render.php # Renderizado HTMLComisiones
reports/mod-ventas/comisiones/
├── comisiones-datos.php # Obtención de datos
└── comisiones-render.php # Renderizado HTMLConvenciones de nomenclatura
| Tipo de archivo | Sufijo | Ejemplo |
|---|---|---|
| Datos | -datos.php | ventas-datos.php |
| Render HTML | -render.php | ventas-render.php |
| Render alternativo | Descriptivo | ventas-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
- Exportación a Excel - Uso de funciones helper para Excel
- Arquitectura Backend - Contexto general del sistema
Historial de cambios
| Fecha | Versión | Autor | Descripción |
|---|---|---|---|
| 2025-12-12 | 1.0 | Sistema | Documentación del patrón existente |