Tecnico: Arquitectura de informes

Este documento describe la arquitectura interna del plugin y cómo extenderlo o añadir nuevos informes. Para entender el motor de informes que hay debajo, consulta primero la documentación de ExtendedReport:

Visión general · Estructura del XML · Widgets · Grupos y rupturas

Las tres capas

InformesEstadisticos separa claramente qué pide el usuario, de dónde salen los datos y con qué diseño se imprimen:

 ┌──────────────────────────────────────────────────────────────┐
 │ 1. CONFIGURACIÓN (qué pide el usuario)                        │
 │    StatisticCustomer / StatisticAgent / StatisticSupplier /  │
 │    StatisticProduct  ── (base: ModelStatistic)               │
 │    Guarda filtros: empresa, fechas, doctype, orden, límite…  │
 │    Tablas: statistics_*  ·  Edición: EditStatistic*          │
 └──────────────────────────────────────────────────────────────┘
                          │ al exportar
                          ▼
 ┌──────────────────────────────────────────────────────────────┐
 │ 2. CÁLCULO (de dónde salen los datos)                         │
 │    Model/Report/*  ── (base: ModelReport, que hereda del     │
 │    ModelReport de ExtendedReport)                            │
 │    Ejecuta SQL (periodo actual vs anterior) y rellena        │
 │    objetos de datos (ComparativeData, PurchaseSaleData…)     │
 └──────────────────────────────────────────────────────────────┘
                          │ addDataset('main', …)
                          ▼
 ┌──────────────────────────────────────────────────────────────┐
 │ 3. DISEÑO + RENDER (con qué se imprime)                       │
 │    StatisticReport (tabla statistics_reports) vincula:       │
 │      format (XML de XMLView/Report) ↔ model ↔ grouptype      │
 │    PDFTemplate / CSVTemplate / HtmlTemplate de ExtendedReport│
 └──────────────────────────────────────────────────────────────┘
                          │
                          ▼
              PDF / CSV / HTML en nueva pestaña 

Capa 1 — Configuración del informe

Los modelos StatisticCustomer, StatisticAgent, StatisticSupplier y StatisticProduct extienden la base abstracta Lib/InformesEstadisticos/ModelStatistic y representan una configuración guardada de informe. Campos comunes:

  • idcompany — empresa sobre la que se calcula.
  • periodtype — clave de periodo relativo (this-year, last-month, current-quarter…). Si está vacío, las fechas se usan tal cual (modo manual). Ver Core/Lib/ListFilter/PeriodTools para la lista completa de claves.
  • startdate / enddate — rango de fechas del periodo actual. Se recalculan desde periodtype al guardar (test()) y al generar el informe (ModelReport::getWhere()). En modo manual el usuario las introduce directamente.
  • doctype — tipo de documento (factura, albarán, pedido, presupuesto).
  • orderfield / orderdesc / orderlimit — ordenación y límite de filas.
  • withdata — si se incluyen o no filas sin datos en el periodo.
  • pagebreak — salto de página por grupo de ruptura.
  • idreport — formato elegido (FK a statistics_reports).

El método applyPeriodToDates() de ModelStatistic recalcula startdate/enddate en memoria a partir de periodtype. Se llama en dos momentos: al guardar (en test()) para mantener la BD coherente, y al generar el informe (en ModelReport::getWhere()) para que las fechas sean siempre relativas al día de impresión.

Cada subclase añade sus propios filtros específicos del grupo. Se editan con los controladores EditStatistic* y sus vistas en XMLView/EditStatistic*.xml.

Filtros aplicados en la cabecera (reportFilters())

Para que la cabecera del informe muestre qué filtros usó el usuario, ModelStatistic expone:

  • reportFilters(): array — devuelve un mapa ordenado clave-de-traducción => valor con los filtros propios de los datos. La base solo incluye el periodo (period); cada subclase lo amplía con array_merge añadiendo sus campos (agente, grupo, familia, rangos desde/hasta, etc.). Quedan fuera las opciones de listado/configuración (origen doctype, withdata, orden, límite, formato…).
  • reportFiltersText(array $params = []): string — monta una línea Etiqueta: valor por filtro activo (traduce la etiqueta, omite los vacíos), unidas con \n. Sin parámetros devuelve todos; con ('columna','total') devuelve solo la parte equilibrada de esa columna, para repartir los filtros en dos bloques de cabecera (cubre 6-8 filtros sin crecer en vertical).

Helpers protegidos que usan las subclases:

  • describe($modelClass, $code, $descField = null) — resuelve una clave foránea a su descripción legible cargando el modelo relacionado (load()); por defecto usa su primaryDescriptionColumn(). El código ($code) es mixed (PK entera o varchar); si no resuelve, devuelve el propio código.
  • range($from, $to) — combina un par desde/hasta en "desde / hasta" (si solo hay un lado, muestra ese).

El XML pinta este texto con una sola columna area="meta" y type="default":

<!-- dos columnas equilibradas: 1ª mitad y 2ª mitad de los filtros -->
<column posx="20" posy="40" width="260" height="50" area="meta">
    <widget type="default" fieldname="filters.reportFiltersText('1','2')" prewrap="true" size="10" />
</column>
<column posx="290" posy="40" width="260" height="50" area="meta">
    <widget type="default" fieldname="filters.reportFiltersText('2','2')" prewrap="true" size="10" />
</column>

El motor resuelve filters.reportFiltersText(...) invocando el método sobre el dato adicional filters (el modelo de configuración), pasándole los argumentos como array; prewrap="true" apila las líneas en la vista HTML. Ver Widgets de ExtendedReport.

Capa 2 — Cálculo de los datos

Los modelos de Model/Report/ extienden Lib/InformesEstadisticos/ModelReport, que a su vez hereda del ModelReport de ExtendedReport. Cada clase implementa getSQL() y vuelca los resultados en su propiedad data.

Modelos comparativos por periodo: CustomerReport, AgentReport y SupplierReport producen arrays de objetos ComparativeData.

Modelos especiales: PurchaseSaleReport (compras vs ventas), ProductReport, ProductFamilyReport, ProductFamilyCountryReport y BestSellersNotBought, cada uno con su clase de datos propia en Model/Report/Data/.

El SQL calcula simultáneamente el periodo solicitado y el mismo periodo del año anterior usando periodBetween() con un desplazamiento de un año, de modo que cada fila trae ambos valores listos para comparar.

ComparativeData: el dato comparativo

La clase Model/Report/Data/ComparativeData es el corazón de los informes comparativos. Para cada fila guarda arrays indexados de 0 a 12, donde el índice 0 es el total anual y 1–12 son los meses:

  • current_value[] — valores del periodo solicitado.
  • previous_value[] — valores del mismo periodo del año anterior.
  • difference[] — diferencia (actual − anterior).
  • percentage[] — porcentaje de variación.

Además expone métodos que el XML puede invocar directamente como fieldname, aprovechando la resolución por método y por índice de array de ExtendedReport (ver Widgets):

  • current_value[0] / current_value[3] — total anual / valor de marzo del periodo actual.
  • quarter('current',1) — total del primer trimestre del periodo actual.
  • quarter('diff',2) — diferencia del segundo trimestre.
  • semester('previous',2) — total del segundo semestre del año anterior.
  • percentageSemesterFirst() — % de variación del primer semestre.
  • monthName([n,'short']) — nombre del mes en formato largo, corto o abreviado.

Así, un mismo ComparativeData alimenta los formatos Anual, Semestral, Trimestral y Mensual sin cambiar el cálculo: solo cambia el XML que decide qué celdas mostrar.

Capa 3 — Diseño y render

El modelo StatisticReport (tabla statistics_reports) es el catálogo de formatos: vincula el nombre del XML de diseño (format, en XMLView/Report/) con la clase de cálculo (model) y el grupo (grouptype).

Al exportar, Lib/InformesEstadisticos/EditStatisticController::exportAction() instancia el template de ExtendedReport correspondiente según la opción solicitada (PDF, CSV o HTML):

// Ejemplo para PDF
$pdfTemplate = new PDFTemplate($this->user, $this->company, ['filters' => $model]);
$pdfTemplate->loadTemplate($statisticReport->format);
$pdfTemplate->addDataset('main', $modelReport);
$pdfTemplate->setRenderCfgValue('pageBreakOnRupture', (bool) $model->pagebreak);
$pdf = $pdfTemplate->render();

El modelo de configuración se pasa como dato adicional filters, por eso los XML pueden mostrar el título del informe con fieldname="filters.name".

Para la salida HTML se instancia HtmlTemplate en lugar de PDFTemplate. El resultado se abre en una nueva pestaña sin descargar ningún archivo.

Convenciones de diseño XML

Los diseños de XMLView/Report/ siguen las convenciones de ExtendedReport con estas particularidades propias del plugin:

  • Las columnas de metadatos de cabecera (nombre de empresa, título, fecha, hora, filtros) llevan area="meta". El visor HTML las muestra en un bloque informativo separado de la tabla; el PDF las pinta en su posición normal.
  • La primera línea unifica empresa, fecha, hora y número de página (todas en posy="25", sobre la barra sombreada y con texto blanco). Debajo, antes del título, va la zona de filtros (posy="40", fieldname="filters.reportFiltersText()", prewrap="true"). El título y las etiquetas de columna van desplazados hacia abajo para dejar ese hueco.
  • El título del informe (fieldname="filters.name") usa class="h2" para que aparezca con tamaño de encabezado en la vista HTML.
  • Las columnas de número de página llevan hideonview="true" para que no aparezcan en la vista HTML (y color="white" para verse sobre la barra de la primera línea en PDF).
  • Los widgets type="number" del detalle y type="calculated" del pie llevan negative="red", de modo que los valores negativos se muestran en rojo en PDF y en HTML.
  • Los widgets de fecha y hora en la cabecera llevan bgcolor para que su fondo de color sea visible también en la vista HTML (donde no existe el fondo de banda del PDF).

Para la referencia completa de atributos consulta Estructura del XML y Widgets.

Catálogo de formatos y siembra por CSV

Los formatos disponibles se siembran desde archivos CSV en Data/Lang/ES|EN/statistics_*.csv. Al instalar o actualizar el plugin, Init::update() llama a Lib/InformesEstadisticos/UpdateReport::checkReports(), que lee statistics_reports.csv y crea en statistics_reports cada formato que falte.

Cada fila del CSV define un formato con estos campos:

  • format — archivo XML de diseño en XMLView/Report/.
  • grouptype — grupo: 0 clientes, 1 agentes, 2 proveedores, 3 productos.
  • model — clase de Model/Report/ que calcula los datos.
  • name — nombre legible que ve el usuario.

Los administradores pueden gestionar estos formatos desde Ajustes (pestaña añadida por Extension/Controller/EditSettings.php, editada con EditStatisticReport).

Cómo añadir un informe nuevo

1. Crear el diseño XML en XMLView/Report/MiInforme.xml
   (usando bandas, widgets, calculated y rupturas de ExtendedReport)
         ↓
2. (Opcional) Crear un ModelReport en Model/Report/ si el cálculo
   no encaja con los existentes (CustomerReport, PurchaseSaleReport…)
         ↓
3. Registrar el formato como una fila en Data/Lang/*/statistics_reports.csv
   (format ↔ grouptype ↔ model ↔ name)
         ↓
4. Actualizar el plugin → UpdateReport lo da de alta en statistics_reports

Widget WidgetStatisticperiod

El plugin incluye un widget propio (Lib/Widget/WidgetStatisticperiod.php, type="statisticperiod") que combina en una sola columna XML el selector de periodo y los dos campos de fecha. El PluginsDeploy del Core lo despliega en Dinamic/Lib/Widget/ igual que cualquier widget del Core, siguiendo el mismo patrón que usan otros plugins (WidgetWeekdays, WidgetSelectTraza, etc.).

El widget está ligado al fieldname="periodtype" pero en processFormData también lee y escribe startdate/enddate del mismo formulario. En setValue($model) guarda una referencia al modelo completo para poder renderizar las dos fechas en inputHtml().

Para reutilizarlo en otra vista con campos de fecha distintos puedes cambiar sus nombres desde el XML:

<widget type="statisticperiod" fieldname="periodtype"
        startfield="mifecha_inicio" endfield="mifecha_fin" />

Extensibilidad

Varios modelos de cálculo (CustomerReport, PurchaseSaleReport…) usan ExtensionsTrait y exponen un punto de extensión mediante $this->pipe('getWhere'). Esto permite que otros plugins añadan condiciones SQL a los informes sin modificar el código de InformesEstadisticos.

Cookies
Usamos cookies en nuestro sitio web para brindarte la experiencia más relevante recordando tus preferencias y visitas repetidas. Al hacer clic en "Aceptar", aceptas el uso de TODAS las cookies necesarias.
Copyright (c) 2013-2026 FacturaScripts
0.05585s

Soporte