nexabase-report 0.2.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +347 -196
- package/dist/{html2canvas-d7a22Xlx.js → html2canvas-Cyaj-wj-.js} +2 -2
- package/dist/{html2canvas-DTMBknrb.js → html2canvas-Dxt9O3_f.js} +1 -1
- package/dist/{html2pdf-CUIndHPV.js → html2pdf-ChLbdK-F.js} +3 -3
- package/dist/{index-B77DEEx0.js → index-WXx32NMF.js} +60 -5
- package/dist/{index.es-B-paKwhr.js → index.es-DFPmbBml.js} +2 -2
- package/dist/{jspdf.es.min-Bq3BwrOB.js → jspdf.es.min-HvNQriJ1.js} +2 -2
- package/dist/nexabase-report.es.js +1 -1
- package/dist/nexabase-report.umd.js +2 -2
- package/docs/PUBLISHING.md +22 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,24 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/nexabase-report)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](https://vuejs.org/)
|
|
7
7
|
|
|
8
|
-
> Librería Vue 3 + TypeScript para diseñar y visualizar reportes tipo banded (inspirada en Stimulsoft / DevExpress). Incluye **diseñador WYSIWYG
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Tabla de contenidos
|
|
13
|
-
|
|
14
|
-
- [Características](#características)
|
|
15
|
-
- [Instalación](#instalación)
|
|
16
|
-
- [Uso rápido](#uso-rápido)
|
|
17
|
-
- [Diseñador vs Viewer](#diseñador-vs-viewer)
|
|
18
|
-
- [API del Viewer](#api-del-viewer)
|
|
19
|
-
- [Formato de datos](#formato-de-datos)
|
|
20
|
-
- [Variables y expresiones](#variables-y-expresiones)
|
|
21
|
-
- [Ejemplos de integración](#ejemplos-de-integración)
|
|
22
|
-
- [Documentación adicional](#documentación-adicional)
|
|
8
|
+
> Librería Vue 3 + TypeScript para diseñar y visualizar reportes tipo banded (inspirada en Stimulsoft / DevExpress). Incluye **diseñador WYSIWYG** multilingüe (ES/EN/PT), **visor como Custom Element** framework-agnostic, y **exportación 100% cliente** a PDF, Excel, Word y CSV.
|
|
23
9
|
|
|
24
10
|
---
|
|
25
11
|
|
|
@@ -28,17 +14,27 @@
|
|
|
28
14
|
| Característica | Descripción |
|
|
29
15
|
|----------------|-------------|
|
|
30
16
|
| **Custom Element** | `<nexa-viewer>` funciona en Vue, React, Angular, Svelte, Blazor o HTML puro |
|
|
31
|
-
| **Diseñador WYSIWYG** | `NexaDesigner.vue` —
|
|
32
|
-
| **Visor standalone** |
|
|
33
|
-
| **Exportación cliente** | PDF
|
|
34
|
-
| **
|
|
35
|
-
| **
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
38
|
-
| **
|
|
39
|
-
| **
|
|
40
|
-
| **
|
|
41
|
-
| **
|
|
17
|
+
| **Diseñador WYSIWYG** | `NexaDesigner.vue` — ribbon, lienzo con snap-to-grid, sidebars, i18n |
|
|
18
|
+
| **Visor standalone** | Sin dependencia de Vue en el consumidor |
|
|
19
|
+
| **Exportación 100% cliente** | PDF (html2pdf.js + fallback jsPDF vectorial), Excel (SheetJS), Word (docx), CSV (UTF-8 BOM) |
|
|
20
|
+
| **Motor de paginación** | Algoritmo en dos fases: streaming + particionado por página, con splitting de tablas a través de páginas |
|
|
21
|
+
| **Renderers** | Texto, imágenes, barras, QR, shapes, drill-down, charts (ECharts), crosstabs, subreportes, widgets |
|
|
22
|
+
| **Watermarks** | Modo tile (texto repetido en grilla) o single posicionado (center, top-left, bottom-right, etc.) |
|
|
23
|
+
| **TOC** | Tabla de contenidos auto-generada en página 1 con enlaces a secciones |
|
|
24
|
+
| **Dashboard** | Modo de visualización con grilla de widgets |
|
|
25
|
+
| **Gráficos** | ECharts (barras, líneas, pastel, área, scatter, radar, etc.) |
|
|
26
|
+
| **Tablas dinámicas** | Crosstabs con agregaciones (sum, count, avg, min, max) |
|
|
27
|
+
| **Códigos QR / Barras** | jsbarcode + qrcode con bindings a datos |
|
|
28
|
+
| **Formato condicional** | Reglas por operador (eq, gt, contains, between, regex, etc.) |
|
|
29
|
+
| **Master-Detail** | DataBand con DetailBand hijo, filtro por campo clave |
|
|
30
|
+
| **Drill-Down** | Navegación a reporte destino con paso de parámetros |
|
|
31
|
+
| **Subreportes** | Anidamiento vía definición embebida o remota (API REST) |
|
|
32
|
+
| **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `FormatDate`, `IIF`, `ISNULL`, agregaciones, `SUBSTRING`, etc. |
|
|
33
|
+
| **Parámetros** | Diálogo de entrada con tipos (text, number, date, boolean, select), validación, skip opcional |
|
|
34
|
+
| **Shapes** | Rectángulos, elipses, líneas y flechas |
|
|
35
|
+
| **Búsqueda** | Highlight inline con navegación entre resultados |
|
|
36
|
+
| **Zoom** | Zoom +/- 10%, fitToWidth, fitToPage, modo continuo/página simple |
|
|
37
|
+
| **i18n** | Visor y diseñador en español, inglés, portugués |
|
|
42
38
|
|
|
43
39
|
---
|
|
44
40
|
|
|
@@ -48,7 +44,7 @@
|
|
|
48
44
|
npm install nexabase-report
|
|
49
45
|
```
|
|
50
46
|
|
|
51
|
-
Importar estilos
|
|
47
|
+
Importar estilos una sola vez al inicio:
|
|
52
48
|
|
|
53
49
|
```ts
|
|
54
50
|
import 'nexabase-report/style.css';
|
|
@@ -64,18 +60,18 @@ import 'nexabase-report/style.css';
|
|
|
64
60
|
<!DOCTYPE html>
|
|
65
61
|
<html>
|
|
66
62
|
<head>
|
|
67
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report/dist/style.css">
|
|
63
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/style.css">
|
|
68
64
|
</head>
|
|
69
65
|
<body>
|
|
70
66
|
<nexa-viewer id="viewer" minimal></nexa-viewer>
|
|
71
67
|
|
|
72
|
-
<script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
|
|
68
|
+
<script src="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/nexabase-report.umd.js"></script>
|
|
73
69
|
<script>
|
|
74
70
|
NexaReport.registerNexaReport();
|
|
75
71
|
|
|
76
72
|
const viewer = document.getElementById('viewer');
|
|
77
|
-
viewer.definition = { /*
|
|
78
|
-
viewer.data = [ /*
|
|
73
|
+
viewer.definition = { /* definición del reporte */ };
|
|
74
|
+
viewer.data = [ /* datos */ ];
|
|
79
75
|
</script>
|
|
80
76
|
</body>
|
|
81
77
|
</html>
|
|
@@ -90,8 +86,8 @@ import 'nexabase-report/style.css';
|
|
|
90
86
|
|
|
91
87
|
registerNexaReport();
|
|
92
88
|
|
|
93
|
-
export function ReportViewer({ definition, data }) {
|
|
94
|
-
const ref = useRef<
|
|
89
|
+
export function ReportViewer({ definition, data }: { definition: any; data: any[] }) {
|
|
90
|
+
const ref = useRef<any>(null);
|
|
95
91
|
|
|
96
92
|
useEffect(() => {
|
|
97
93
|
if (!ref.current) return;
|
|
@@ -106,16 +102,11 @@ export function ReportViewer({ definition, data }) {
|
|
|
106
102
|
### Vue 3
|
|
107
103
|
|
|
108
104
|
```vue
|
|
109
|
-
<script setup>
|
|
105
|
+
<script setup lang="ts">
|
|
110
106
|
import { registerNexaReport } from 'nexabase-report';
|
|
111
107
|
import 'nexabase-report/style.css';
|
|
112
108
|
|
|
113
109
|
registerNexaReport();
|
|
114
|
-
|
|
115
|
-
const props = defineProps<{
|
|
116
|
-
definition: object;
|
|
117
|
-
data: any[];
|
|
118
|
-
}>();
|
|
119
110
|
</script>
|
|
120
111
|
|
|
121
112
|
<template>
|
|
@@ -136,158 +127,321 @@ import 'nexabase-report/style.css';
|
|
|
136
127
|
|
|
137
128
|
registerNexaReport();
|
|
138
129
|
|
|
139
|
-
// En tu componente
|
|
140
130
|
@Component({
|
|
141
131
|
template: '<nexa-viewer #viewer [definition]="reportDef" [data]="reportData"></nexa-viewer>',
|
|
142
132
|
})
|
|
143
133
|
export class ReportComponent {
|
|
144
134
|
@ViewChild('viewer') viewerRef!: ElementRef;
|
|
145
|
-
|
|
146
|
-
reportDef = null;
|
|
135
|
+
reportDef: any = null;
|
|
147
136
|
reportData: any[] = [];
|
|
148
137
|
|
|
149
138
|
async ngOnInit() {
|
|
150
|
-
|
|
151
|
-
this.reportDef = await res.json();
|
|
139
|
+
this.reportDef = await fetch('/assets/report.json').then(r => r.json());
|
|
152
140
|
this.reportData = await fetch('/api/data').then(r => r.json());
|
|
153
141
|
}
|
|
154
142
|
}
|
|
155
143
|
```
|
|
156
144
|
|
|
157
|
-
### Blazor
|
|
145
|
+
### Blazor (.NET 8+)
|
|
158
146
|
|
|
159
147
|
```razor
|
|
160
148
|
@page "/reporte/{Id:int}"
|
|
161
|
-
@inject HttpClient Http
|
|
162
149
|
@inject IJSRuntime JS
|
|
163
150
|
|
|
164
151
|
<link rel="stylesheet" href="_content/nexabase-report/style.css" />
|
|
165
152
|
<script src="_content/nexabase-report/nexabase-report.umd.js"></script>
|
|
166
153
|
|
|
167
|
-
<
|
|
168
|
-
<nexa-viewer id="viewer" minimal></nexa-viewer>
|
|
169
|
-
</div>
|
|
154
|
+
<nexa-viewer id="viewer" minimal></nexa-viewer>
|
|
170
155
|
|
|
171
156
|
@code {
|
|
172
157
|
[Parameter] public int Id { get; set; }
|
|
173
158
|
|
|
174
|
-
protected override async Task
|
|
159
|
+
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
175
160
|
{
|
|
176
|
-
|
|
177
|
-
var
|
|
178
|
-
|
|
179
|
-
await JS.InvokeVoidAsync("renderNexaReport",
|
|
161
|
+
if (!firstRender) return;
|
|
162
|
+
var def = await Http.GetFromJsonAsync<object>($"/api/reportes/{Id}");
|
|
163
|
+
var data = await Http.GetFromJsonAsync<List<object>>($"/api/reportes/{Id}/datos");
|
|
164
|
+
await JS.InvokeVoidAsync("renderNexaReport", def, data);
|
|
180
165
|
}
|
|
181
166
|
}
|
|
182
167
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
window.nexaRegistered = true;
|
|
189
|
-
}
|
|
190
|
-
const v = document.getElementById('viewer');
|
|
191
|
-
v.definition = definition;
|
|
192
|
-
v.data = data;
|
|
168
|
+
<script>
|
|
169
|
+
window.renderNexaReport = function(definition, data) {
|
|
170
|
+
if (!window.__nexaRegistered) {
|
|
171
|
+
NexaReport.registerNexaReport();
|
|
172
|
+
window.__nexaRegistered = true;
|
|
193
173
|
}
|
|
194
|
-
|
|
195
|
-
|
|
174
|
+
const v = document.getElementById('viewer');
|
|
175
|
+
v.definition = definition;
|
|
176
|
+
v.data = data;
|
|
177
|
+
};
|
|
178
|
+
</script>
|
|
196
179
|
```
|
|
197
180
|
|
|
198
|
-
|
|
181
|
+
---
|
|
199
182
|
|
|
200
|
-
|
|
201
|
-
@* Shared/ReportViewer.razor *@
|
|
202
|
-
@inject IJSRuntime JS
|
|
183
|
+
## Diseñador (NexaDesigner)
|
|
203
184
|
|
|
204
|
-
|
|
205
|
-
<nexa-viewer @ref="viewerRef" id="@Id" minimal></nexa-viewer>
|
|
206
|
-
</div>
|
|
185
|
+
Diseñador WYSIWYG como componente Vue 3:
|
|
207
186
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
187
|
+
- **Ribbon** — formato de texto (fuente, tamaño, color, negrita, cursiva, alineación)
|
|
188
|
+
- **Lienzo** — snap-to-grid (5 px), rulers, selección múltiple, arrastrar/redimensionar
|
|
189
|
+
- **Panel de propiedades** — secciones: apariencia, datos, bordes, formato condicional
|
|
190
|
+
- **Diccionario de datos** — campos del datasource con drag al lienzo
|
|
191
|
+
- **Multilingüe** — español, inglés, portugués, cambiable desde la barra
|
|
192
|
+
- **Import/Export** — definiciones JSON
|
|
193
|
+
- **Deshacer/Rehacer** — Ctrl+Z / Ctrl+Y
|
|
194
|
+
- **Clipboard** — copiar/pegar elementos entre bandas
|
|
213
195
|
|
|
214
|
-
|
|
196
|
+
### Uso
|
|
215
197
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
198
|
+
```vue
|
|
199
|
+
<script setup lang="ts">
|
|
200
|
+
import { NexaDesigner } from 'nexabase-report';
|
|
201
|
+
import 'nexabase-report/style.css';
|
|
202
|
+
import { ref } from 'vue';
|
|
203
|
+
|
|
204
|
+
const designerRef = ref(null);
|
|
205
|
+
const locale = ref('es');
|
|
223
206
|
|
|
224
|
-
|
|
225
|
-
|
|
207
|
+
function onSave(reportDef: any) {
|
|
208
|
+
console.log('Reporte guardado:', reportDef);
|
|
226
209
|
}
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<template>
|
|
213
|
+
<NexaDesigner ref="designerRef" :locale="locale" @save="onSave" style="height: 100vh" />
|
|
214
|
+
</template>
|
|
227
215
|
```
|
|
228
216
|
|
|
229
|
-
|
|
217
|
+
### Props
|
|
230
218
|
|
|
231
|
-
|
|
219
|
+
| Prop | Tipo | Default | Descripción |
|
|
220
|
+
|------|------|---------|-------------|
|
|
221
|
+
| `locale` | `'es' \| 'en' \| 'pt'` | `'es'` | Idioma del diseñador |
|
|
222
|
+
| `initialReport` | `NexaReportDefinition` | — | Reporte a editar (nuevo si no se pasa) |
|
|
232
223
|
|
|
233
|
-
|
|
234
|
-
|---------|-------------|------------|
|
|
235
|
-
| **Tag** | `<NexaReportDesigner>` | `<nexa-viewer>` |
|
|
236
|
-
| **Framework** | Requiere Vue 3 | Framework-agnostic |
|
|
237
|
-
| **Uso** | Crear/editar reportes | Solo visualización |
|
|
238
|
-
| **Exportación** | No | Sí (PDF, Excel, Word, CSV) |
|
|
239
|
-
| **Props data** | No | Sí |
|
|
224
|
+
### Eventos
|
|
240
225
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
3. Usuario exporta / imprime desde el viewer
|
|
226
|
+
| Evento | Payload | Descripción |
|
|
227
|
+
|--------|---------|-------------|
|
|
228
|
+
| `save` | `NexaReportDefinition` | Usuario presionó guardar |
|
|
245
229
|
|
|
246
230
|
---
|
|
247
231
|
|
|
248
|
-
##
|
|
232
|
+
## Visor (`<nexa-viewer>`)
|
|
233
|
+
|
|
234
|
+
El visor se registra como Custom Element y funciona sin Vue en el proyecto consumidor.
|
|
235
|
+
|
|
236
|
+
### Formas de uso
|
|
237
|
+
|
|
238
|
+
```html
|
|
239
|
+
<nexa-viewer
|
|
240
|
+
minimal
|
|
241
|
+
showToolbar
|
|
242
|
+
showThumbs
|
|
243
|
+
locale="es"
|
|
244
|
+
currentPage="1"
|
|
245
|
+
skipParamsDialog
|
|
246
|
+
></nexa-viewer>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Como Vue SFC directamente:
|
|
250
|
+
|
|
251
|
+
```vue
|
|
252
|
+
<script setup lang="ts">
|
|
253
|
+
import { NexaViewerElement, registerNexaReport } from 'nexabase-report';
|
|
254
|
+
import 'nexabase-report/style.css';
|
|
255
|
+
|
|
256
|
+
registerNexaReport();
|
|
257
|
+
</script>
|
|
258
|
+
|
|
259
|
+
<template>
|
|
260
|
+
<nexa-viewer
|
|
261
|
+
:definition="reportDef"
|
|
262
|
+
:data="reportData"
|
|
263
|
+
:parameters="params"
|
|
264
|
+
locale="es"
|
|
265
|
+
@page-change="onPageChange"
|
|
266
|
+
@drill-click="onDrillClick"
|
|
267
|
+
/>
|
|
268
|
+
</template>
|
|
269
|
+
```
|
|
249
270
|
|
|
250
271
|
### Props
|
|
251
272
|
|
|
252
273
|
| Prop | Tipo | Default | Descripción |
|
|
253
274
|
|------|------|---------|-------------|
|
|
254
|
-
| `definition` | `string \|
|
|
255
|
-
| `data` | `string \| any[] \|
|
|
256
|
-
| `parameters` | `
|
|
275
|
+
| `definition` | `string \| NexaReportDefinition` | — | Definición del reporte (objeto o JSON string) |
|
|
276
|
+
| `data` | `string \| any[] \| Record<string, any[]>` | — | Datos: array simple (alias "main"), objeto multi-alias, o JSON string |
|
|
277
|
+
| `parameters` | `Record<string, any>` | — | Valores iniciales de parámetros |
|
|
257
278
|
| `minimal` | `boolean` | `false` | Oculta toolbar y thumbnails |
|
|
258
|
-
| `showToolbar` | `boolean` | `true` |
|
|
259
|
-
| `showThumbs` | `boolean` | `true` |
|
|
260
|
-
| `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de parámetros |
|
|
261
|
-
| `currentPage` | `number` | `1` | Página inicial |
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
279
|
+
| `showToolbar` | `boolean` | `true` | Forzar toolbar visible (útil en minimal) |
|
|
280
|
+
| `showThumbs` | `boolean` | `true` | Forzar miniaturas visibles (útil en minimal) |
|
|
281
|
+
| `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de parámetros al cargar |
|
|
282
|
+
| `currentPage` | `number` | `1` | Página inicial (soporta v-model) |
|
|
283
|
+
| `locale` | `string` | `'es'` | Idioma: `'es'`, `'en'`, `'pt'` |
|
|
284
|
+
| `apiBaseUrl` | `string` | — | URL base para subreportes/drill-down remotos |
|
|
285
|
+
| `apiKey` | `string` | — | API Key para requests al backend |
|
|
264
286
|
|
|
265
|
-
### Métodos (DOM)
|
|
287
|
+
### Métodos (acceso vía ref o DOM)
|
|
266
288
|
|
|
267
289
|
```ts
|
|
268
290
|
const v = document.querySelector('nexa-viewer');
|
|
269
291
|
|
|
270
|
-
|
|
271
|
-
await v.
|
|
292
|
+
// Exportación
|
|
293
|
+
await v.exportPdf(); // pdf (html2pdf.js — fallback jsPDF vectorial)
|
|
294
|
+
await v.exportPdfWithBookmarks(); // pdf con marcadores
|
|
272
295
|
await v.exportExcel(); // .xlsx (SheetJS)
|
|
273
296
|
await v.exportWord(); // .docx
|
|
274
|
-
await v.exportCsv(); // .csv con
|
|
275
|
-
|
|
276
|
-
|
|
297
|
+
await v.exportCsv(); // .csv con UTF-8 BOM
|
|
298
|
+
|
|
299
|
+
// Navegación
|
|
300
|
+
v.goToPage(5);
|
|
301
|
+
|
|
302
|
+
// Datos
|
|
303
|
+
v.updateData(nuevosDatos);
|
|
304
|
+
|
|
305
|
+
// Parámetros
|
|
306
|
+
v.applyParams();
|
|
307
|
+
v.applyParamsWithValidation();
|
|
308
|
+
v.validateParams(); // retorna errores
|
|
309
|
+
|
|
310
|
+
// Utilidad
|
|
311
|
+
v.getDictionaryFields(); // retorna campos disponibles
|
|
312
|
+
|
|
313
|
+
// Propiedades de lectura
|
|
314
|
+
v.pageNumber; // número actual
|
|
315
|
+
v.totalPages; // total de páginas
|
|
316
|
+
v.paramValues; // valores actuales de parámetros
|
|
317
|
+
v.showParamsDialog; // estado del diálogo
|
|
318
|
+
v.paramValidationErrors; // errores de validación
|
|
277
319
|
```
|
|
278
320
|
|
|
279
321
|
### Eventos
|
|
280
322
|
|
|
281
323
|
```ts
|
|
282
|
-
v.addEventListener('page-change',
|
|
283
|
-
v.addEventListener('drill-click',
|
|
324
|
+
v.addEventListener('page-change', e => console.log(e.detail.page));
|
|
325
|
+
v.addEventListener('drill-click', e => console.log(e.detail));
|
|
326
|
+
v.addEventListener('subreport-toggle', e => console.log(e.detail));
|
|
327
|
+
v.addEventListener('data-request', e => console.log(e.detail.alias));
|
|
284
328
|
```
|
|
285
329
|
|
|
286
330
|
---
|
|
287
331
|
|
|
332
|
+
## Arquitectura del visor
|
|
333
|
+
|
|
334
|
+
### Template
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
<nexa-viewer>
|
|
338
|
+
├── Toolbar (condicional)
|
|
339
|
+
│ ├── Exportación: PDF, Excel, Word, CSV, imprimir
|
|
340
|
+
│ ├── Zoom: −, %, +, fitToWidth, fitToPage
|
|
341
|
+
│ └── Paginación: ◀, input/total, ▶, búsqueda, info
|
|
342
|
+
│
|
|
343
|
+
├── Sidebar (condicional, 220px)
|
|
344
|
+
│ ├── Pestaña Pages — miniaturas vía html2canvas
|
|
345
|
+
│ ├── Pestaña TOC — tabla de contenidos con navegación
|
|
346
|
+
│ └── Pestaña Params — formulario de parámetros (text, number, date, boolean, select)
|
|
347
|
+
│
|
|
348
|
+
├── Viewport (scrollable)
|
|
349
|
+
│ └── Pages root (transform: scale(Z%) )
|
|
350
|
+
│ ├── Dashboard: grilla de DashboardWidgetRenderer
|
|
351
|
+
│ └── Reporte: v-for pages
|
|
352
|
+
│ └── report-page (sombra, fondo blanco)
|
|
353
|
+
│ ├── Watermark (tile o single posicionado)
|
|
354
|
+
│ ├── TOC page (si página 1 y generateTOC)
|
|
355
|
+
│ └── Bands
|
|
356
|
+
│ ├── DataBand tabular → Table, Chart, Crosstab, SubReport
|
|
357
|
+
│ └── DataBand iterativa → Text, Image, Barcode, QR, Shape, DrillDown
|
|
358
|
+
│
|
|
359
|
+
├── Modal DrillDown (reporte anidado vía API)
|
|
360
|
+
└── Modal About
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### PaginationEngine
|
|
364
|
+
|
|
365
|
+
El motor de paginación es una clase (`src/lib/viewer/services/PaginationEngine.ts`) que opera en **dos fases**:
|
|
366
|
+
|
|
367
|
+
**Fase 1 — generateStream()**: Generator que produce un stream de `PageBandDef`:
|
|
368
|
+
1. Emite `ReportHeader` primero
|
|
369
|
+
2. Bandas estáticas (ni DataBand ni DetailBand)
|
|
370
|
+
3. Para cada `DataBand`:
|
|
371
|
+
- **Tabular** (Table, Chart, Crosstab, SubReport): emite la banda completa con todas las filas en `rows`
|
|
372
|
+
- **Iterativa** (una instancia por fila): emite una banda por fila, intercalando GroupHeader/GroupFooter según agrupación y procesando DetailBand hijos (master-detail)
|
|
373
|
+
4. Emite `ReportFooter` al final
|
|
374
|
+
|
|
375
|
+
**Fase 2 — generatePages()**: Algoritmo de particionado:
|
|
376
|
+
- Calcula altura disponible: `pageHeight - pageHeader - pageFooter`
|
|
377
|
+
- Itera el stream, acumulando bandas
|
|
378
|
+
- Si una banda **tabular** no cabe:
|
|
379
|
+
- **Tabla**: calcula filas que entran (`(h disponible - h header) / h fila`), corta en chunks, coloca cada chunk en página nueva
|
|
380
|
+
- **Tabla agrupada**: corta por grupos completos (respeta `tableShowGroupHeader`/`tableShowGroupFooter`)
|
|
381
|
+
- Charts/Crosstabs: saltan a página nueva enteros
|
|
382
|
+
- Respeta `pageBreakBefore` / `pageBreakAfter`
|
|
383
|
+
- Asigna coordenadas Y absolutas para posicionamiento CSS
|
|
384
|
+
- Cada ~10 páginas cede el hilo (`setTimeout`) para no bloquear en datasets grandes
|
|
385
|
+
|
|
386
|
+
Métodos estáticos auxiliares: `applyFilters`, `applySort`, `applyJoins`, `matchesFilterValue` (soporta eq, ne, gt, gte, lt, lte, contains, starts_with, ends_with, is_null, is_not_null, between, regex).
|
|
387
|
+
|
|
388
|
+
### Renderers
|
|
389
|
+
|
|
390
|
+
| Renderer | Archivo | Descripción |
|
|
391
|
+
|----------|---------|-------------|
|
|
392
|
+
| TextRenderer | `viewer/renderers/TextRenderer.vue` | Texto plano con soporte de highlight de búsqueda |
|
|
393
|
+
| ImageRenderer | `viewer/renderers/ImageRenderer.vue` | `<img>` con resolución de binding/URL |
|
|
394
|
+
| BarcodeRenderer | `viewer/renderers/BarcodeRenderer.vue` | SVG vía jsbarcode (CODE128 default) |
|
|
395
|
+
| QRCodeRenderer | `viewer/renderers/QRCodeRenderer.vue` | QR vía librería qrcode |
|
|
396
|
+
| ShapeRenderer | `viewer/renderers/ShapeRenderer.vue` | Rectángulo, elipse, línea, flecha (CSS inline) |
|
|
397
|
+
| DrillDownRenderer | `viewer/renderers/DrillDownRenderer.vue` | Elemento clickeable que emite `drill-click` |
|
|
398
|
+
| FallbackRenderer | `viewer/renderers/FallbackRenderer.vue` | Muestra tipo no soportado |
|
|
399
|
+
| SubreportRenderer | `viewer/renderers/SubreportRenderer.vue` | `<nexa-viewer>` anidado (embebido o remoto) |
|
|
400
|
+
| ChartRenderer | `viewer/ChartRenderer.vue` | ECharts (barras, líneas, pastel, etc.) |
|
|
401
|
+
| CrosstabRenderer | `viewer/CrosstabRenderer.vue` | Tabla dinámica con agrupación fila/columna |
|
|
402
|
+
| DashboardWidgetRenderer | `viewer/DashboardWidgetRenderer.vue` | Widgets en modo dashboard |
|
|
403
|
+
|
|
404
|
+
Total: **11 renderers** (8 en `viewer/renderers/` + 3 en `viewer/`).
|
|
405
|
+
|
|
406
|
+
### Zoom (`useZoom`)
|
|
407
|
+
|
|
408
|
+
- Rango: 25%–250%, default 100%
|
|
409
|
+
- `zoomIn()` / `zoomOut()` — ±10 puntos
|
|
410
|
+
- `fitToWidth()` — escala al ancho del viewport
|
|
411
|
+
- `fitToPage()` — escala al alto y ancho
|
|
412
|
+
- Modos: `continuous` (scroll vertical) / `single` (página por página)
|
|
413
|
+
- Atajo: Ctrl+`+`, Ctrl+`-`, Ctrl+`0`
|
|
414
|
+
|
|
415
|
+
### Búsqueda (`useSearch`)
|
|
416
|
+
|
|
417
|
+
- `searchQuery` con debounce de 200ms
|
|
418
|
+
- Escanea hasta 250 páginas, case-insensitive, máx 500 resultados
|
|
419
|
+
- Navegación: `nextMatch()` / `prevMatch()` con scroll automático
|
|
420
|
+
- Highlight: `getHighlightParts(text)` retorna segmentos marcados/no marcados
|
|
421
|
+
- Input de búsqueda en toolbar con atajo de teclado
|
|
422
|
+
|
|
423
|
+
### Watermarks
|
|
424
|
+
|
|
425
|
+
- **Tile**: texto repetido en grilla (20 spans CSS grid), opacidad configurable, rotado
|
|
426
|
+
- **Single**: posicionado absoluto (center, top-left, top-right, bottom-left, bottom-right, top-center, bottom-center)
|
|
427
|
+
- Opacidad, color, tamaño, rotación por configuración
|
|
428
|
+
|
|
429
|
+
### TOC (Tabla de Contenidos)
|
|
430
|
+
|
|
431
|
+
- Generación automática si `generateTOC: true` en la definición
|
|
432
|
+
- Se Renderiza en página 1
|
|
433
|
+
- Cada ítem: texto + dots + número de página
|
|
434
|
+
- Navegación: click → `goToPage(n)` con scroll
|
|
435
|
+
|
|
436
|
+
### Dashboard
|
|
437
|
+
|
|
438
|
+
Si `documentType === 'Dashboard'`, el reporte se Renderiza como grilla de widgets (`DashboardWidgetRenderer`) en lugar de páginas con bandas.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
288
442
|
## Formato de datos
|
|
289
443
|
|
|
290
|
-
###
|
|
444
|
+
### Array simple
|
|
291
445
|
|
|
292
446
|
```json
|
|
293
447
|
[
|
|
@@ -296,9 +450,9 @@ v.addEventListener('drill-click', e => console.log(e.detail));
|
|
|
296
450
|
]
|
|
297
451
|
```
|
|
298
452
|
|
|
299
|
-
El
|
|
453
|
+
El DataSource se asocia automáticamente al alias `"main"`.
|
|
300
454
|
|
|
301
|
-
###
|
|
455
|
+
### Múltiples DataSources
|
|
302
456
|
|
|
303
457
|
```json
|
|
304
458
|
{
|
|
@@ -311,16 +465,15 @@ El reporte usa `dataSource: ""` (vacío) para tomar este array como `"main"`.
|
|
|
311
465
|
}
|
|
312
466
|
```
|
|
313
467
|
|
|
314
|
-
|
|
468
|
+
Cada banda referencia su DataSource por alias: `dataSource: "clientes"`.
|
|
315
469
|
|
|
316
|
-
###
|
|
470
|
+
### Parámetros
|
|
317
471
|
|
|
318
472
|
```ts
|
|
319
|
-
// Pasando parameters al viewer
|
|
320
473
|
viewer.parameters = {
|
|
321
474
|
fechaInicio: '2024-01-01',
|
|
322
475
|
fechaFin: '2024-12-31',
|
|
323
|
-
categoria: '
|
|
476
|
+
categoria: 'Electronics'
|
|
324
477
|
};
|
|
325
478
|
```
|
|
326
479
|
|
|
@@ -334,98 +487,96 @@ viewer.parameters = {
|
|
|
334
487
|
|----------|-------------|---------|
|
|
335
488
|
| `{{Page}}` | Página actual | `1` |
|
|
336
489
|
| `{{TotalPages}}` | Total de páginas | `5` |
|
|
337
|
-
| `{{Today}}` | Fecha actual (YYYY-MM-DD) | `
|
|
338
|
-
| `{{Now}}` | Fecha y hora actual | `
|
|
339
|
-
| `{{Year}}` | Año actual | `
|
|
340
|
-
| `{{Month}}` | Mes actual | `
|
|
341
|
-
| `{{Day}}` | Día actual | `
|
|
490
|
+
| `{{Today}}` | Fecha actual (YYYY-MM-DD) | `2026-05-13` |
|
|
491
|
+
| `{{Now}}` | Fecha y hora actual | `2026-05-13 14:30:00` |
|
|
492
|
+
| `{{Year}}` | Año actual | `2026` |
|
|
493
|
+
| `{{Month}}` | Mes actual | `5` |
|
|
494
|
+
| `{{Day}}` | Día actual | `13` |
|
|
342
495
|
| `{{ReportName}}` | Nombre del reporte | `Ventas Mensuales` |
|
|
343
496
|
| `{{RowNumber}}` | Número de fila (1-based) | `1` |
|
|
344
497
|
| `{{TotalRows}}` | Total de filas | `50` |
|
|
345
|
-
| `{{EvenRow}}`
|
|
346
|
-
| `{{
|
|
347
|
-
| `{{
|
|
348
|
-
| `{{
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
{[FormatNumber(precio, 'es-ES')]}
|
|
355
|
-
{[FormatDate(fecha, 'dd/MM/yyyy')]}
|
|
356
|
-
{[FormatCurrency(total, 'USD')]}
|
|
357
|
-
{[IIF(total > 100000, 'ALTO', 'BAJO')]}
|
|
358
|
-
{[ISNULL(cliente, 'Sin nombre')]}
|
|
359
|
-
{[UPPER(nombre)]}
|
|
360
|
-
{[
|
|
361
|
-
{[
|
|
362
|
-
{[
|
|
363
|
-
{[
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
Mínimo: {[min(total)]}
|
|
375
|
-
Primero: {[first(nombre)]}
|
|
376
|
-
Último: {[last(apellido)]}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### Bindings mixtos (content + binding)
|
|
380
|
-
|
|
381
|
-
```json
|
|
382
|
-
{
|
|
383
|
-
"type": "Text",
|
|
384
|
-
"content": "Cliente: {{nombre}} ({{ciudad}})",
|
|
385
|
-
"binding": "nombre"
|
|
386
|
-
}
|
|
387
|
-
```
|
|
498
|
+
| `{{EvenRow}}` / `{{OddRow}}` | Fila par / impar | `true` / `false` |
|
|
499
|
+
| `{{FirstRow}}` / `{{LastRow}}` | Primera / última fila | `true` |
|
|
500
|
+
| `{{GroupKey}}` | Clave del grupo actual | `Electronics` |
|
|
501
|
+
| `{{GroupCount}}` | Registros en el grupo actual | `12` |
|
|
502
|
+
|
|
503
|
+
### Expresiones `{[...]}`
|
|
504
|
+
|
|
505
|
+
| Expresión | Resultado |
|
|
506
|
+
|-----------|-----------|
|
|
507
|
+
| `{[FormatNumber(precio, 'es-ES')]}` | `1.234,56` |
|
|
508
|
+
| `{[FormatDate(fecha, 'dd/MM/yyyy')]}` | `15/01/2024` |
|
|
509
|
+
| `{[FormatCurrency(total, 'USD')]}` | `$1,234.56` |
|
|
510
|
+
| `{[IIF(total > 100000, 'ALTO', 'BAJO')]}` | `ALTO` |
|
|
511
|
+
| `{[ISNULL(cliente, 'Sin nombre')]}` | `Sin nombre` |
|
|
512
|
+
| `{[UPPER(nombre)]}` | `JUAN PÉREZ` |
|
|
513
|
+
| `{[LOWER(nombre)]}` | `juan pérez` |
|
|
514
|
+
| `{[SUBSTRING(texto, 0, 3)]}` | `Hel` |
|
|
515
|
+
| `{[LENGTH(nombre)]}` | `10` |
|
|
516
|
+
| `{[TRIM(texto)]}` | sin espacios |
|
|
517
|
+
| `{[ABS(-5)]}` | `5` |
|
|
518
|
+
| `{[CEIL(5.3)]}` | `6` |
|
|
519
|
+
| `{[FLOOR(5.9)]}` | `5` |
|
|
520
|
+
| `{[SUM(cantidad)]}` | suma |
|
|
521
|
+
| `{[COUNT(id)]}` | conteo |
|
|
522
|
+
| `{[AVG(precio)]}` | promedio |
|
|
523
|
+
| `{[MIN(precio)]}` | mínimo |
|
|
524
|
+
| `{[MAX(precio)]}` | máximo |
|
|
525
|
+
| `{[CONCAT(nombre, ' - ', ciudad)]}` | `Juan - Bogotá` |
|
|
526
|
+
| `{[REPLACE(texto, 'a', 'o')]}` | reemplazo |
|
|
388
527
|
|
|
389
528
|
---
|
|
390
529
|
|
|
391
|
-
## Ejemplos
|
|
530
|
+
## Ejemplos
|
|
392
531
|
|
|
393
|
-
Los
|
|
532
|
+
Los reportes de ejemplo están en `examples/` y son compatibles con diseñador y visor.
|
|
394
533
|
|
|
395
534
|
| Archivo | Descripción |
|
|
396
535
|
|---------|-------------|
|
|
397
|
-
| `report-factura.json` | Factura con
|
|
398
|
-
| `factura_de_recolección_de_residuos.json` | Factura ambiental
|
|
399
|
-
| `report-
|
|
400
|
-
| `report-
|
|
536
|
+
| `report-factura.json` | Factura con ítems, cliente y totales |
|
|
537
|
+
| `factura_de_recolección_de_residuos.json` | Factura ambiental multi-DataSource |
|
|
538
|
+
| `report-factura-residuos.json` | Factura de residuos con GroupHeader/Footer |
|
|
539
|
+
| `report-anexo-factura.json` | Anexo con tabla expandida |
|
|
540
|
+
| `report-ventas-logo-tabla.json` | Corporativo con logo y tabla agrupada |
|
|
541
|
+
| `report-agrupado-por-cliente.json` | GroupHeader + agrupación por cliente |
|
|
401
542
|
| `report-productos.json` | Lista simple de productos |
|
|
402
|
-
| `report-crosstab-categoria-mes.json` |
|
|
403
|
-
| `report-grafico-ventas-por-mes.json` | Gráfico de barras
|
|
404
|
-
| `report-master-detail-ordenes.json` | Master-
|
|
543
|
+
| `report-crosstab-categoria-mes.json` | Crosstab categoría × mes |
|
|
544
|
+
| `report-grafico-ventas-por-mes.json` | Gráfico de barras ECharts |
|
|
545
|
+
| `report-master-detail-ordenes.json` | Master-Detail con subreportes |
|
|
405
546
|
| `report-codigos-qr-barcode.json` | Códigos QR y barras |
|
|
406
547
|
|
|
407
|
-
|
|
548
|
+
---
|
|
408
549
|
|
|
409
|
-
|
|
410
|
-
const res = await fetch('/examples/report-factura.json');
|
|
411
|
-
const definition = await res.json();
|
|
412
|
-
const data = [ /* tus datos */ ];
|
|
550
|
+
## Publicación
|
|
413
551
|
|
|
414
|
-
|
|
415
|
-
|
|
552
|
+
```bash
|
|
553
|
+
npm run build
|
|
554
|
+
npm version patch|minor|major
|
|
555
|
+
npm publish --access public
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
CDN automático:
|
|
559
|
+
|
|
560
|
+
```html
|
|
561
|
+
<script src="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/nexabase-report.umd.js"></script>
|
|
562
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/style.css">
|
|
416
563
|
```
|
|
417
564
|
|
|
565
|
+
Ver [`docs/PUBLISHING.md`](docs/PUBLISHING.md) para CI/CD y registro privado.
|
|
566
|
+
|
|
418
567
|
---
|
|
419
568
|
|
|
420
569
|
## Documentación adicional
|
|
421
570
|
|
|
422
|
-
- [API del Viewer](docs/VIEWER_API.md) —
|
|
423
|
-
- [
|
|
424
|
-
- [
|
|
425
|
-
- [Plan de QA](docs/QA_PLAN.md) —
|
|
571
|
+
- [API completa del Viewer](docs/VIEWER_API.md) — props, métodos, eventos, tipos
|
|
572
|
+
- [API REST](docs/API_REST.md) — endpoints serverless
|
|
573
|
+
- [Integración externa](docs/EXTERNAL_INTEGRATION.md) — ASP.NET, Django, Laravel, Rails
|
|
574
|
+
- [Plan de QA](docs/QA_PLAN.md) — checklist de pruebas manuales
|
|
575
|
+
- [Checklist de regresión exportación](docs/EXPORT_REGRESSION.md) — PDF, Excel, Word, CSV
|
|
576
|
+
- [Guía de publicación npm](docs/PUBLISHING.md) — build, versionado, CI/CD
|
|
426
577
|
|
|
427
578
|
---
|
|
428
579
|
|
|
429
580
|
## Licencia
|
|
430
581
|
|
|
431
|
-
MIT © NexaBase Team
|
|
582
|
+
MIT © NexaBase Team
|