Report Studio
This content is not available in your language yet.
Report Studio — Guia para Desarrolladores
Sección titulada «Report Studio — Guia para Desarrolladores»Report Studio es el motor de reportes propio de Zentto, diseñado como reemplazo de Crystal Reports. Permite visualizar, editar y crear reportes personalizados directamente desde el ERP web, sin dependencias externas de terceros.
1. Arquitectura
Sección titulada «1. Arquitectura»El sistema se compone de paquetes npm independientes que se conectan en cascada:
@zentto/report-core Motor puro: tipos, engine 3-pass, expresiones, charts SVG | +-- @zentto/report-viewer Web component <zentto-report-viewer> +-- @zentto/report-designer Web component <zentto-report-designer> | v @zentto/shared-reports React wrappers: ReportViewer, ReportDesigner, | PrintButton, layouts centralizados v @zentto/shared-api Hooks y servicios API (fetch, auth) | v zentto-cache (Redis) Persistencia de layouts guardadosFlujo de renderizado
Sección titulada «Flujo de renderizado»- Se carga un Layout JSON (desde codigo o desde BD via API).
- Si el layout define
endpointen susdataSources, elDataFetchProviderobtiene datos reales de la API. - El Band Engine ejecuta un renderizado de 3 pasadas:
- Pass 1: Calcula dimensiones, resuelve grupos y running totals.
- Pass 2: Pagina el contenido, calcula
totalPages. - Pass 3: Renderiza el HTML final con page numbers correctos.
- El resultado (
RenderResult) contiene un arreglo de paginas HTML. - El
ReportViewermuestra las paginas con toolbar de navegacion, zoom, impresion y descarga.
2. Estructura de un Layout
Sección titulada «2. Estructura de un Layout»Un layout es un objeto JSON compatible con la interfaz ReportLayout de @zentto/report-core.
interface ReportLayout { version: string; // "1.0" name: string; // "Listado de Asientos Contables" description?: string; // Descripcion del reporte pageSize: PageSize; // { width: 210, height: 297, unit: "mm" } margins: Margins; // { top: 12, right: 12, bottom: 12, left: 12 } orientation?: "portrait" | "landscape";
dataSources: DataSourceDef[]; // Fuentes de datos relations?: RelationDef[]; // JOINs entre fuentes bands: Band[]; // Bandas del reporte groups?: GroupDef[]; // Agrupaciones sorting?: SortDef[]; // Ordenamiento defaultStyle?: ElementStyle; // Estilos por defecto styles?: Record<string, ElementStyle>; // Estilos nombrados parameters?: ParameterDef[]; // Parametros de usuario runningTotals?: RunningTotalDef[]; // Totales acumulados conditionalFormats?: ConditionalFormatRule[]; subreports?: SubreportDef[]; // Sub-reportes crossTabs?: CrossTabDef[]; // Tablas dinamicas recordSelectionFormula?: string; // Filtro WHERE-style metadata?: Record<string, unknown>;}DataSources
Sección titulada «DataSources»Cada fuente de datos define de donde vienen los datos del reporte:
interface DataSourceDef { id: string; // Identificador unico, ej: "header", "items" name: string; // Nombre legible type: "object" | "array"; // object = registro unico, array = lista fields?: FieldDef[]; // Metadatos de campos endpoint?: string; // Endpoint API para fetch automatico endpointParams?: Record<string, string>; // Params para URLs con :id}Bands (Bandas)
Sección titulada «Bands (Bandas)»Las bandas definen las secciones del reporte. Tipos disponibles:
| Banda | Descripcion |
|---|---|
reportHeader | Encabezado del reporte (una vez al inicio) |
pageHeader | Encabezado de cada pagina |
groupHeader | Encabezado de grupo (al cambiar el campo agrupado) |
columnHeader | Titulos de columnas |
detail | Cuerpo: se repite por cada registro del dataSource |
columnFooter | Pie de columnas |
groupFooter | Pie de grupo (totales parciales) |
pageFooter | Pie de cada pagina |
reportFooter | Pie del reporte (totales generales, una vez al final) |
Elements (Elementos)
Sección titulada «Elements (Elementos)»Cada banda contiene elementos posicionados con coordenadas x/y/width/height:
| Tipo | Descripcion | Props clave |
|---|---|---|
text | Texto estatico | content |
field | Campo vinculado a datos | dataSource, field, format, expression, aggregate |
image | Imagen (URL o expresion) | src, fit |
line | Linea decorativa | x2, y2, lineStyle |
rect | Rectangulo | lineStyle, fill, cornerRadius |
barcode | Codigo de barras/QR | barcodeType (qr, code128, ean13, code39), value |
chart | Grafico SVG | chartType, dataSource, labelField, valueFields |
pageNumber | Numero de pagina | format (ej: "Pagina {page} de {pages}") |
currentDate | Fecha/hora actual | format (ej: "dd/MM/yyyy HH:mm") |
subreport | Sub-reporte embebido | subreportId |
crossTab | Tabla dinamica (pivot) | crossTabId |
Estilos
Sección titulada «Estilos»Todos los elementos aceptan un objeto style opcional:
interface ElementStyle { fontFamily?: string; fontSize?: number; // en puntos fontWeight?: "normal" | "bold" | number; fontStyle?: "normal" | "italic"; textAlign?: "left" | "center" | "right" | "justify"; color?: string; // "#1a1a1a" backgroundColor?: string; borderTop?: string; // "1px solid #ccc" borderBottom?: string; padding?: number | string; opacity?: number; wordWrap?: boolean;}Formatos de campo
Sección titulada «Formatos de campo»El campo format en elementos field acepta patrones:
| Patron | Ejemplo | Resultado |
|---|---|---|
#,##0.00 | 1234.5 | 1,234.50 |
$#,##0.00 | 1234.5 | $1,234.50 |
dd/MM/yyyy | 2026-03-30 | 30/03/2026 |
dd/MM/yyyy HH:mm | 2026-03-30T14:30 | 30/03/2026 14:30 |
0.00% | 0.185 | 18.50% |
3. Layouts Centralizados
Sección titulada «3. Layouts Centralizados»Los layouts del sistema se definen como constantes TypeScript en shared-reports:
web/modular-frontend/packages/shared-reports/src/layouts/ index.ts # Barrel: re-exporta todos los layouts contabilidad/ asientos-list.ts # ASIENTOS_LIST_LAYOUT libro-mayor.ts # LIBRO_MAYOR_LAYOUT balance-comprobacion.ts # BALANCE_COMPROBACION_LAYOUT libro-diario.ts # LIBRO_DIARIO_LAYOUT plan-cuentas.ts # PLAN_CUENTAS_LAYOUT inventario/ articulos.ts # ARTICULOS_LAYOUT movimientos.ts # MOVIMIENTOS_INVENTARIO_LAYOUT bancos/ bancos-list.ts # BANCOS_LIST_LAYOUT cuentas-bancarias.ts # CUENTAS_BANCARIAS_LAYOUT movimientos-bancarios.ts # MOVIMIENTOS_BANCARIOS_LAYOUT caja-chica.ts # CAJA_CHICA_LAYOUT ventas/ documentos-venta.ts # DOCUMENTOS_VENTA_LAYOUT clientes.ts # CLIENTES_LAYOUT cxc.ts # CXC_DOCUMENTOS_LAYOUT compras/ documentos-compra.ts # DOCUMENTOS_COMPRA_LAYOUT proveedores.ts # PROVEEDORES_LAYOUT cxp.ts # CXP_DOCUMENTOS_LAYOUT nomina/ empleados.ts # EMPLEADOS_LAYOUT nominas.ts # NOMINAS_LAYOUT conceptos.ts # CONCEPTOS_NOMINA_LAYOUT vacaciones.ts # VACACIONES_LAYOUT crm/ leads.ts # LEADS_LAYOUT actividades.ts # ACTIVIDADES_CRM_LAYOUT maestros/ categorias.ts # CATEGORIAS_LAYOUT vendedores.ts # VENDEDORES_LAYOUT almacenes.ts # ALMACENES_LAYOUT centro-costo.ts # CENTRO_COSTO_LAYOUTConvencion de nombres
Sección titulada «Convencion de nombres»- Archivo:
kebab-case.ts— ej:asientos-list.ts - Export:
UPPER_SNAKE_CASE— ej:ASIENTOS_LIST_LAYOUT - Cada archivo exporta exactamente 1 constante
const
Como crear un nuevo layout
Sección titulada «Como crear un nuevo layout»- Crear archivo en
shared-reports/src/layouts/{modulo}/{nombre}.ts - Definir la constante con la estructura
ReportLayout - Re-exportar desde
shared-reports/src/layouts/index.ts - Ejecutar
npm run seed:reportsenweb/api/para sincronizar con la BD
Ejemplo minimo:
export const PEDIDOS_LAYOUT = { version: "1.0", name: "Listado de Pedidos", description: "Pedidos pendientes con totales", pageSize: { width: 210, height: 297, unit: "mm" }, margins: { top: 12, right: 12, bottom: 12, left: 12 }, orientation: "portrait" as const, dataSources: [ { id: "header", name: "Encabezado", type: "object" as const, endpoint: "/v1/config/empresa", fields: [ { name: "empresa", label: "Empresa", type: "string" }, ], }, { id: "pedidos", name: "Pedidos", type: "array" as const, endpoint: "/v1/ventas/pedidos", fields: [ { name: "id", label: "ID", type: "number" }, { name: "fecha", label: "Fecha", type: "date" }, { name: "cliente", label: "Cliente", type: "string" }, { name: "total", label: "Total", type: "currency" }, ], }, ], bands: [ { id: "rh", type: "reportHeader", height: 14, elements: [ { id: "rh-title", type: "text", content: "LISTADO DE PEDIDOS", x: 0, y: 0, width: 186, height: 9, style: { fontSize: 14, fontWeight: "bold", textAlign: "center" }, }, ], }, { id: "ch", type: "columnHeader", height: 7, elements: [ { id: "ch-id", type: "text", content: "ID", x: 0, y: 0, width: 20, height: 5, style: { fontSize: 8, fontWeight: "bold" } }, { id: "ch-fecha", type: "text", content: "Fecha", x: 22, y: 0, width: 30, height: 5, style: { fontSize: 8, fontWeight: "bold" } }, { id: "ch-cliente", type: "text", content: "Cliente", x: 54, y: 0, width: 90, height: 5, style: { fontSize: 8, fontWeight: "bold" } }, { id: "ch-total", type: "text", content: "Total", x: 146, y: 0, width: 40, height: 5, style: { fontSize: 8, fontWeight: "bold", textAlign: "right" } }, ], }, { id: "dt", type: "detail", height: 6, dataSource: "pedidos", elements: [ { id: "dt-id", type: "field", dataSource: "pedidos", field: "id", x: 0, y: 0, width: 20, height: 5, style: { fontSize: 8 } }, { id: "dt-fecha", type: "field", dataSource: "pedidos", field: "fecha", format: "dd/MM/yyyy", x: 22, y: 0, width: 30, height: 5, style: { fontSize: 8 } }, { id: "dt-cliente", type: "field", dataSource: "pedidos", field: "cliente", x: 54, y: 0, width: 90, height: 5, style: { fontSize: 8 } }, { id: "dt-total", type: "field", dataSource: "pedidos", field: "total", format: "$#,##0.00", x: 146, y: 0, width: 40, height: 5, style: { fontSize: 8, textAlign: "right" } }, ], }, { id: "pf", type: "pageFooter", height: 8, elements: [ { id: "pf-page", type: "pageNumber", format: "Pagina {page} de {pages}", x: 146, y: 0, width: 40, height: 5, style: { fontSize: 7, textAlign: "right" } }, ], }, ],};4. Seed Script
Sección titulada «4. Seed Script»El script seed:reports sincroniza los layouts de codigo hacia la base de datos (zentto-cache / Redis).
# Desarrollo localcd web/apinpm run seed:reports
# Produccion (via Docker)docker exec zentto-api npm run seed:reportsLos layouts en codigo (shared-reports/src/layouts/) son el respaldo canonico.
En runtime, el sistema lee layouts desde la BD. Si un usuario modifica un layout
via el Designer, los cambios se persisten en BD sin afectar el codigo fuente.
5. Integrar un Reporte en una Pagina
Sección titulada «5. Integrar un Reporte en una Pagina»Ejemplo completo basado en la pagina de Asientos Contables:
"use client";import { useMemo, useState } from "react";import { Button, Dialog, DialogContent } from "@mui/material";import { ReportViewer, ASIENTOS_LIST_LAYOUT } from "@zentto/shared-reports";import type { ReportLayout, DataSet } from "@zentto/report-core";
export default function AsientosListPage() { const [reportOpen, setReportOpen] = useState(false);
// Supongamos que `rows` viene de un hook useAsientos() const rows = useAsientos();
// 1. Mapear datos al DataSet esperado por el layout const reportData = useMemo((): DataSet => ({ header: { empresa: "Mi Empresa S.A.", fechaDesde: "01/03/2026", fechaHasta: "31/03/2026", totalDebe: rows.reduce((s, r) => s + r.totalDebe, 0), totalHaber: rows.reduce((s, r) => s + r.totalHaber, 0), totalRegistros: rows.length, }, asientos: rows.map((r, i) => ({ num: i + 1, id: r.id, fecha: r.fecha, tipoAsiento: r.tipo, concepto: r.concepto, referencia: r.referencia, totalDebe: r.totalDebe, totalHaber: r.totalHaber, estado: r.estado, })), }), [rows]);
return ( <> {/* 2. Boton para abrir el reporte */} <Button variant="outlined" startIcon={<PrintIcon />} onClick={() => setReportOpen(true)} > Reporte </Button>
{/* 3. Dialog con el ReportViewer */} <Dialog open={reportOpen} onClose={() => setReportOpen(false)} maxWidth={false} fullWidth > <DialogContent sx={{ height: "85vh", p: 0 }}> <ReportViewer layout={ASIENTOS_LIST_LAYOUT as unknown as ReportLayout} data={reportData} showToolbar viewMode="all" /> </DialogContent> </Dialog> </> );}Alternativa: PrintButton (atajo)
Sección titulada «Alternativa: PrintButton (atajo)»El componente PrintButton encapsula todo el flujo anterior:
import { PrintButton } from "@zentto/shared-reports";
<PrintButton layout={ASIENTOS_LIST_LAYOUT} data={reportData} label="Imprimir"/>6. Usar el Designer
Sección titulada «6. Usar el Designer»El ReportDesigner es un editor visual WYSIWYG tipo Figma para crear y modificar layouts.
Props del ReportDesigner
Sección titulada «Props del ReportDesigner»| Prop | Tipo | Default | Descripcion |
|---|---|---|---|
layout | ReportLayout | null | — | Layout inicial a editar |
sampleData | DataSet | null | null | Datos de ejemplo para preview |
dataSources | DataSourceDef[] | [] | Fuentes de datos disponibles en el toolbox |
showPreview | boolean | true | Mostrar panel de preview en tiempo real |
gridSnap | number | 1 | Ajuste a cuadricula en mm |
autoSaveMs | number | 3000 | Intervalo de auto-guardado (ms) |
onLayoutChange | (layout: ReportLayout) => void | — | Callback al modificar el layout |
style | CSSProperties | — | Estilos CSS del contenedor |
className | string | — | Clase CSS |
Ejemplo de uso
Sección titulada «Ejemplo de uso»"use client";import { useState } from "react";import { ReportDesigner } from "@zentto/shared-reports";import type { ReportLayout } from "@zentto/report-core";
export default function DesignerPage() { const [layout, setLayout] = useState<ReportLayout | null>(null);
const handleSave = async (updated: ReportLayout) => { await fetch(`/api/v1/reportes/saved/${layout?.name}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ layout: updated }), }); };
return ( <ReportDesigner layout={layout} sampleData={null} showPreview gridSnap={1} onLayoutChange={handleSave} style={{ height: "100vh" }} /> );}Web component directo (sin React)
Sección titulada «Web component directo (sin React)»Si necesitas usar el designer como web component puro:
<zentto-report-designer id="designer"></zentto-report-designer>
<script type="module"> import "@zentto/report-designer";
const el = document.getElementById("designer"); el.layout = { /* ReportLayout JSON */ }; el.sampleData = { /* DataSet */ }; el.showPreview = true;
el.addEventListener("layout-change", (e) => { console.log("Layout modificado:", e.detail.layout); });</script>7. ReportViewer Props
Sección titulada «7. ReportViewer Props»| Prop | Tipo | Default | Descripcion |
|---|---|---|---|
layout | ReportLayout | null | — | Layout del reporte a renderizar |
data | DataSet | null | — | Datos para llenar el reporte |
zoom | number | 100 | Nivel de zoom (%) |
showToolbar | boolean | true | Mostrar barra de herramientas |
theme | "light" | "dark" | "light" | Tema visual |
viewMode | "single" | "all" | "single" | single = una pagina a la vez, all = scroll continuo |
showThumbnails | boolean | false | Mostrar panel de miniaturas |
toolbarItems | string[] | ver abajo | Items visibles en la toolbar |
style | CSSProperties | — | Estilos CSS del contenedor |
className | string | — | Clase CSS |
toolbarItems por defecto
Sección titulada «toolbarItems por defecto»["navigation", "zoom", "view-mode", "fit-width", "print", "download-html", "theme"]8. DataSources con Endpoint
Sección titulada «8. DataSources con Endpoint»Cuando un DataSourceDef incluye endpoint, el viewer puede obtener datos automaticamente
usando un DataFetchProvider:
interface DataFetchProvider { fetch( endpoint: string, params?: Record<string, string> ): Promise<Record<string, unknown> | Record<string, unknown>[]>;}Ejemplo con parametros
Sección titulada «Ejemplo con parametros»{ id: "factura", name: "Factura", type: "object", endpoint: "/v1/documentos-venta/:id", endpointParams: { id: ":documentId" }, fields: [ { name: "numero", label: "Numero", type: "string" }, { name: "fecha", label: "Fecha", type: "date" }, { name: "total", label: "Total", type: "currency" }, ],}El provider reemplaza :id en la URL con el valor del parametro documentId
proporcionado al momento de renderizar.
Flujo de datos
Sección titulada «Flujo de datos»- El layout declara
endpointen cada dataSource. - Al renderizar, el motor invoca
DataFetchProvider.fetch(endpoint, params). - La API ejecuta el SP optimizado correspondiente.
- Los datos se inyectan en el
DataSetantes de pasar al Band Engine.
Esto garantiza:
- Seguridad: sin SQL injection, respeta permisos del usuario autenticado.
- Performance: SPs optimizados con indices.
- Compatibilidad: funciona con SQL Server y PostgreSQL.
9. Expresiones
Sección titulada «9. Expresiones»El motor de expresiones es seguro (sin eval). La sintaxis usa llaves para campos
y funciones tipo Crystal Reports.
Sintaxis basica
Sección titulada «Sintaxis basica»={campo} Referencia a campo={precio} * {cantidad} Expresion aritmetica=IF({qty} > 10, "BULK", "RETAIL") Condicional=FORMAT({total}, "$#,##0.00") Formato=SUM({monto}) Agregado sobre todos los registrosFunciones disponibles (80+)
Sección titulada «Funciones disponibles (80+)»LEFT, RIGHT, MID, LEN, TRIM, LTRIM, RTRIM, UPPER, LOWER, PROPERCASE,
REPLACE, INSTR, SPLIT, JOIN, CHR, ASC, SPACE, REPLICATESTRING, STRREVERSE,
CONTAINS, STARTSWITH, ENDSWITH, PADLEFT, PADRIGHT, TOTEXT, TOWORDS, CONCAT
Matematicas
Sección titulada «Matematicas»ABS, ROUND, TRUNCATE, FLOOR, CEILING, CEIL, REMAINDER, MOD, SGN, SQRT,
EXP, LOG, LOG10, PI, POWER, RANDOM, MIN2, MAX2
NOW, TODAY, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, DATEADD, DATEDIFF,
DAYOFWEEK, MONTHNAME, DAYNAME, ISDATE, FORMATDATE, DATESERIAL
Conversion
Sección titulada «Conversion»TONUMBER, TOSTRING, TODATE, TOBOOLEAN, ISNUMBER, ISNULL, COALESCE
Agregados (contexto de banda)
Sección titulada «Agregados (contexto de banda)»SUM, AVG, COUNT, DISTINCTCOUNT, MIN, MAX, MEDIAN, STDDEV, VARIANCE,
PERCENTOFSUM, NTHLARGEST, NTHSMALLEST
Agregados de grupo/total
Sección titulada «Agregados de grupo/total»SUM_GROUP, AVG_GROUP, COUNT_GROUP, SUM_ALL, AVG_ALL, COUNT_ALL
Estado de impresion
Sección titulada «Estado de impresion»PREVIOUS, NEXT, RECORDNUMBER, GROUPNUMBER, PAGENUMBER, TOTALPAGECOUNT,
ONFIRSTRECORD, ONLASTRECORD, INREPEATEDGROUPHEADER
IF, IIF, SWITCH, CHOOSE
Variables
Sección titulada «Variables»SETVAR, GETVAR
Formato
Sección titulada «Formato»FORMAT, FORMATCURRENCY, FORMATPERCENT
10. API Endpoints
Sección titulada «10. API Endpoints»Todos los endpoints estan bajo /v1/reportes y requieren autenticacion JWT.
Reportes guardados (por usuario)
Sección titulada «Reportes guardados (por usuario)»| Metodo | Ruta | Descripcion |
|---|---|---|
GET | /v1/reportes/saved | Listar reportes guardados del usuario |
GET | /v1/reportes/saved/:id | Obtener layout + sampleData de un reporte |
PUT | /v1/reportes/saved/:id | Guardar/actualizar un reporte |
DELETE | /v1/reportes/saved/:id | Eliminar un reporte guardado |
Reportes publicos (por empresa)
Sección titulada «Reportes publicos (por empresa)»| Metodo | Ruta | Descripcion |
|---|---|---|
GET | /v1/reportes/public | Listar reportes publicos de la empresa |
PUT | /v1/reportes/public/:id | Guardar un reporte publico |
Generacion PDF
Sección titulada «Generacion PDF»| Metodo | Ruta | Descripcion |
|---|---|---|
POST | /v1/reportes/pdf | Generar PDF desde layout + data |
POST | /v1/reportes/pdf?format=base64 | Generar PDF en formato base64 (para APIs externas) |
Motores externos (legacy)
Sección titulada «Motores externos (legacy)»| Metodo | Ruta | Descripcion |
|---|---|---|
GET | /v1/reportes/engines | Estado de todos los motores (Crystal, jsreport, SSRS) |
GET | /v1/reportes/crystal/catalogo | Catalogo de reportes Crystal |
POST | /v1/reportes/crystal/render | Renderizar reporte Crystal |
POST | /v1/reportes/jsreport/render | Renderizar reporte jsreport |
Persistencia
Sección titulada «Persistencia»Los reportes guardados se persisten en zentto-cache (Redis) con claves
compuestas por companyId + userId + reportId. Los reportes publicos usan
solo companyId + reportId y son visibles para todos los usuarios de la empresa.
11. Tipos de grafico (Charts)
Sección titulada «11. Tipos de grafico (Charts)»El motor soporta graficos SVG embebidos en cualquier banda:
| chartType | Descripcion |
|---|---|
bar | Barras verticales |
line | Lineas |
pie | Pastel |
area | Area |
donut | Dona |
scatter | Dispersion |
stacked | Barras apiladas |
combo | Combinado (barras + linea) |
{ id: "chart-ventas", type: "chart", chartType: "bar", dataSource: "ventas_mensuales", labelField: "mes", valueFields: ["total", "meta"], title: "Ventas vs Meta", colors: ["#3b82f6", "#ef4444"], x: 10, y: 5, width: 160, height: 80,}12. Paquetes npm
Sección titulada «12. Paquetes npm»| Paquete | Descripcion | Registro |
|---|---|---|
@zentto/report-core | Motor puro: tipos, engine 3-pass, expresiones, charts SVG, templates | npm |
@zentto/report-designer | Web component <zentto-report-designer> — editor WYSIWYG | npm |
@zentto/report-viewer | Web component <zentto-report-viewer> — visualizacion | npm |
@zentto/shared-reports | React wrappers, hooks, layouts centralizados, PrintButton | Monorepo interno |
Instalacion
Sección titulada «Instalacion»npm install @zentto/report-core @zentto/report-viewer @zentto/report-designernext.config.mjs
Sección titulada «next.config.mjs»// Los web components Lit requieren transpilacion en Next.jstranspilePackages: [ '@zentto/report-core', '@zentto/report-viewer', '@zentto/report-designer', 'lit',]