nexabase-report 0.2.6 → 0.2.8

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 CHANGED
@@ -2,27 +2,23 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/nexabase-report)](https://www.npmjs.com/package/nexabase-report)
4
4
  [![license](https://img.shields.io/npm/l/nexabase-report)](LICENSE)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue)](https://www.typescriptlang.org/)
6
+ [![Vue 3](https://img.shields.io/badge/Vue-3.5-green)](https://vuejs.org/)
5
7
 
6
8
  > Librería Vue 3 + TypeScript para diseñar y visualizar reportes tipo banded (inspirada en Stimulsoft / DevExpress). Incluye **diseñador WYSIWYG**, **visor framework-agnostic** como Custom Element, y **exportación 100% cliente** a PDF, Excel, Word y CSV.
7
9
 
10
+ ---
11
+
8
12
  ## Tabla de contenidos
9
13
 
10
14
  - [Características](#características)
11
15
  - [Instalación](#instalación)
12
16
  - [Uso rápido](#uso-rápido)
13
- - [React](#react)
14
- - [Vue 3](#vue-3)
15
- - [Angular](#angular)
16
- - [HTML puro (CDN)](#html-puro-cdn)
17
+ - [Diseñador vs Viewer](#diseñador-vs-viewer)
17
18
  - [API del Viewer](#api-del-viewer)
18
- - [Props](#props)
19
- - [Métodos](#métodos)
20
- - [Eventos](#eventos)
21
19
  - [Formato de datos](#formato-de-datos)
22
- - [Variables de sistema](#variables-de-sistema)
23
- - [Funciones de expresiones](#funciones-de-expresiones)
24
- - [Exportación programática](#exportación-programática)
25
- - [Solución de problemas](#solución-de-problemas)
20
+ - [Variables y expresiones](#variables-y-expresiones)
21
+ - [Ejemplos de integración](#ejemplos-de-integración)
26
22
  - [Documentación adicional](#documentación-adicional)
27
23
 
28
24
  ---
@@ -31,16 +27,18 @@
31
27
 
32
28
  | Característica | Descripción |
33
29
  |----------------|-------------|
34
- | **Custom Element** | `<nexa-viewer>` funciona en Vue, React, Angular, Svelte o HTML puro |
35
- | **Diseñador WYSIWYG** | Arrastra y suelta bandas, textos, tablas, imágenes, gráficos |
36
- | **Exportación cliente** | PDF (vectorial con jsPDF), Excel (SheetJS), Word (docx), CSV |
37
- | **Gráficos** | ECharts integrado (barras, líneas, pastel, etc.) |
38
- | **Tablas dinámicas** | Crosstabs (pivot tables) nativos |
30
+ | **Custom Element** | `<nexa-viewer>` funciona en Vue, React, Angular, Svelte, Blazor o HTML puro |
31
+ | **Diseñador WYSIWYG** | `NexaDesigner.vue` arrastra/suelta bandas, textos, tablas, imágenes, gráficos |
32
+ | **Visor standalone** | `<nexa-viewer>` no requiere Vue en el proyecto consumidor |
33
+ | **Exportación cliente** | PDF vectorial (jsPDF), Excel (SheetJS), Word (docx), CSV |
34
+ | **Gráficos** | ECharts integrado (barras, líneas, pastel, area, scatter) |
35
+ | **Tablas dinámicas** | Crosstabs nativos con agregaciones |
39
36
  | **Códigos QR / Barras** | jsbarcode + qrcode |
40
- | **Formato condicional** | Reglas visuales por campo y valor |
41
- | **Master-Detail** | Subreportes anidados |
42
- | **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `IIF`, `ISNULL`, etc. |
43
- | **Sin backend requerido** | Los datos se pasan directamente por props |
37
+ | **Formato condicional** | Reglas visuales por campo y operador |
38
+ | **Master-Detail** | Subreportes anidados con datos relacionados |
39
+ | **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `FormatDate`, `IIF`, `ISNULL`, `sum`, `count`, etc. |
40
+ | **Parámetros** | Dilogo de entrada, valores por props, skipParamsDialog |
41
+ | **Paginación** | Automática por altura de página, thumbnails, navegación |
44
42
 
45
43
  ---
46
44
 
@@ -50,7 +48,7 @@
50
48
  npm install nexabase-report
51
49
  ```
52
50
 
53
- También necesitarás importar los estilos globales **una vez** en tu app:
51
+ Importar estilos **una sola vez** al inicio de la app:
54
52
 
55
53
  ```ts
56
54
  import 'nexabase-report/style.css';
@@ -60,30 +58,40 @@ import 'nexabase-report/style.css';
60
58
 
61
59
  ## Uso rápido
62
60
 
61
+ ### HTML puro (sin framework)
62
+
63
+ ```html
64
+ <!DOCTYPE html>
65
+ <html>
66
+ <head>
67
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report/dist/style.css">
68
+ </head>
69
+ <body>
70
+ <nexa-viewer id="viewer" minimal></nexa-viewer>
71
+
72
+ <script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
73
+ <script>
74
+ NexaReport.registerNexaReport();
75
+
76
+ const viewer = document.getElementById('viewer');
77
+ viewer.definition = { /* tu definición de reporte */ };
78
+ viewer.data = [ /* array de datos */ ];
79
+ </script>
80
+ </body>
81
+ </html>
82
+ ```
83
+
63
84
  ### React
64
85
 
65
86
  ```tsx
66
- import { useEffect, useRef, useState } from 'react';
87
+ import { useEffect, useRef } from 'react';
67
88
  import { registerNexaReport } from 'nexabase-report';
68
89
  import 'nexabase-report/style.css';
69
90
 
70
91
  registerNexaReport();
71
92
 
72
- export function ReportViewer() {
93
+ export function ReportViewer({ definition, data }) {
73
94
  const ref = useRef<HTMLElement>(null);
74
- const [definition, setDefinition] = useState<any>(null);
75
- const [data, setData] = useState<any[]>([]);
76
-
77
- useEffect(() => {
78
- (async () => {
79
- const res = await fetch('/report.json');
80
- setDefinition(await res.json());
81
- setData([
82
- { nombre: 'Producto A', precio: 100 },
83
- { nombre: 'Producto B', precio: 50 },
84
- ]);
85
- })();
86
- }, []);
87
95
 
88
96
  useEffect(() => {
89
97
  if (!ref.current) return;
@@ -98,80 +106,143 @@ export function ReportViewer() {
98
106
  ### Vue 3
99
107
 
100
108
  ```vue
101
- <script setup lang="ts">
102
- import { ref, watch } from 'vue';
109
+ <script setup>
103
110
  import { registerNexaReport } from 'nexabase-report';
104
111
  import 'nexabase-report/style.css';
105
112
 
106
113
  registerNexaReport();
107
114
 
108
- const props = defineProps<{ definition: any; data: any[] }>();
109
- const viewerRef = ref<HTMLElement | null>(null);
110
-
111
- watch(
112
- () => [props.definition, props.data],
113
- () => {
114
- const el = viewerRef.value as any;
115
- if (!el) return;
116
- el.definition = props.definition;
117
- el.data = props.data;
118
- },
119
- { immediate: true }
120
- );
115
+ const props = defineProps<{
116
+ definition: object;
117
+ data: any[];
118
+ }>();
121
119
  </script>
122
120
 
123
121
  <template>
124
- <nexa-viewer ref="viewerRef" minimal style="display:block;height:100%" />
122
+ <nexa-viewer
123
+ :definition="definition"
124
+ :data="data"
125
+ minimal
126
+ style="display:block;height:700px"
127
+ />
125
128
  </template>
126
129
  ```
127
130
 
128
131
  ### Angular
129
132
 
130
133
  ```ts
131
- import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
132
134
  import { registerNexaReport } from 'nexabase-report';
133
135
  import 'nexabase-report/style.css';
134
136
 
135
137
  registerNexaReport();
136
138
 
139
+ // En tu componente
137
140
  @Component({
138
- selector: 'app-root',
139
- template: '<nexa-viewer #viewer minimal></nexa-viewer>',
141
+ template: '<nexa-viewer #viewer [definition]="reportDef" [data]="reportData"></nexa-viewer>',
140
142
  })
141
- export class AppComponent implements AfterViewInit {
142
- @ViewChild('viewer', { static: true }) viewerRef!: ElementRef<HTMLElement>;
143
+ export class ReportComponent {
144
+ @ViewChild('viewer') viewerRef!: ElementRef;
143
145
 
144
- async ngAfterViewInit() {
145
- const el = this.viewerRef.nativeElement as any;
146
+ reportDef = null;
147
+ reportData: any[] = [];
148
+
149
+ async ngOnInit() {
146
150
  const res = await fetch('/assets/report.json');
147
- el.definition = await res.json();
148
- el.data = [{ nombre: 'Producto A', precio: 100 }];
151
+ this.reportDef = await res.json();
152
+ this.reportData = await fetch('/api/data').then(r => r.json());
149
153
  }
150
154
  }
151
155
  ```
152
156
 
153
- ### HTML puro (CDN)
157
+ ### Blazor Razor (.NET 8)
154
158
 
155
- ```html
156
- <!DOCTYPE html>
157
- <html>
158
- <head>
159
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report@latest/dist/style.css">
160
- </head>
161
- <body>
162
- <nexa-viewer id="viewer" minimal></nexa-viewer>
159
+ ```razor
160
+ @page "/reporte/{Id:int}"
161
+ @inject HttpClient Http
162
+ @inject IJSRuntime JS
163
163
 
164
- <script src="https://cdn.jsdelivr.net/npm/nexabase-report@latest/dist/nexabase-report.umd.js"></script>
165
- <script>
166
- window.NexaReport.registerNexaReport();
167
- const v = document.getElementById('viewer');
168
- v.definition = { /* ... */ };
169
- v.data = [ /* ... */ ];
170
- </script>
171
- </body>
172
- </html>
164
+ <link rel="stylesheet" href="_content/nexabase-report/style.css" />
165
+ <script src="_content/nexabase-report/nexabase-report.umd.js"></script>
166
+
167
+ <div style="height:700px">
168
+ <nexa-viewer id="viewer" minimal></nexa-viewer>
169
+ </div>
170
+
171
+ @code {
172
+ [Parameter] public int Id { get; set; }
173
+
174
+ protected override async Task OnInitializedAsync()
175
+ {
176
+ var definition = await Http.GetFromJsonAsync<dynamic>($"/api/reportes/{Id}");
177
+ var data = await Http.GetFromJsonAsync<List<dynamic>>($"/api/reportes/{Id}/datos");
178
+
179
+ await JS.InvokeVoidAsync("renderNexaReport", definition, data);
180
+ }
181
+ }
182
+
183
+ @section Scripts {
184
+ <script>
185
+ function renderNexaReport(definition, data) {
186
+ if (!window.nexaRegistered) {
187
+ NexaReport.registerNexaReport();
188
+ window.nexaRegistered = true;
189
+ }
190
+ const v = document.getElementById('viewer');
191
+ v.definition = definition;
192
+ v.data = data;
193
+ }
194
+ </script>
195
+ }
173
196
  ```
174
197
 
198
+ O si prefieres usar un componente wrapper en Razor:
199
+
200
+ ```razor
201
+ @* Shared/ReportViewer.razor *@
202
+ @inject IJSRuntime JS
203
+
204
+ <div style="height: @Height">
205
+ <nexa-viewer @ref="viewerRef" id="@Id" minimal></nexa-viewer>
206
+ </div>
207
+
208
+ @code {
209
+ [Parameter] public string Id { get; set; } = "viewer";
210
+ [Parameter] public string Height { get; set; } = "700px";
211
+ [Parameter] public object Definition { get; set; }
212
+ [Parameter] public object Data { get; set; }
213
+
214
+ private ElementReference viewerRef;
215
+
216
+ protected override async Task OnAfterRenderAsync(bool firstRender)
217
+ {
218
+ if (firstRender)
219
+ {
220
+ await JS.InvokeVoidAsync("nexaReportInterop.init", viewerRef, Definition, Data);
221
+ }
222
+ }
223
+
224
+ public async Task ExportPdf() => await JS.InvokeVoidAsync("nexaReportInterop.exportPdf", viewerRef);
225
+ public async Task ExportExcel() => await JS.InvokeVoidAsync("nexaReportInterop.exportExcel", viewerRef);
226
+ }
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Diseñador vs Viewer
232
+
233
+ | Aspecto | NexaDesigner | NexaViewer |
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í |
240
+
241
+ **Flujo típico:**
242
+ 1. Diseñador crea → guarda JSON (definición del reporte)
243
+ 2. Viewer consume esa definición + datos del backend
244
+ 3. Usuario exporta / imprime desde el viewer
245
+
175
246
  ---
176
247
 
177
248
  ## API del Viewer
@@ -180,39 +251,36 @@ export class AppComponent implements AfterViewInit {
180
251
 
181
252
  | Prop | Tipo | Default | Descripción |
182
253
  |------|------|---------|-------------|
183
- | `definition` | `string \| object` | — | Definición del reporte (JSON o objeto) |
184
- | `data` | `string \| any[] \| Record<string, any[]>` | — | Datos del reporte (Array o Objeto con alias) |
185
- | `parameters` | `Record<string, any>` | `{}` | Valores para parámetros del reporte |
186
- | `minimal` | `boolean \| string` | `false` | Modo compacto: oculta toolbar y miniaturas |
187
- | `showToolbar` | `boolean \| string` | `true` | Muestra/oculta barra de herramientas superior |
188
- | `showThumbs` | `boolean \| string` | `true` | Muestra/oculta panel lateral de miniaturas |
189
- | `skipParamsDialog` | `boolean` | `false` | Salta el diálogo inicial de parámetros |
190
- | `currentPage` | `number` | `1` | Página inicial a mostrar |
191
- | `apiBaseUrl` | `string` | — | URL base de NexaBase para carga dinámica |
192
- | `apiKey` | `string` | — | API Key para autenticación de datos |
193
-
194
- ### Métodos (vía ref DOM)
254
+ | `definition` | `string \| object` | — | Definición del reporte (JSON) |
255
+ | `data` | `string \| any[] \| object` | — | Datos: `[]` para single DS, `{alias: []}` para multi |
256
+ | `parameters` | `object` | `{}` | Valores para parámetros del reporte |
257
+ | `minimal` | `boolean` | `false` | Oculta toolbar y thumbnails |
258
+ | `showToolbar` | `boolean` | `true` | Muestra/oculta barra superior |
259
+ | `showThumbs` | `boolean` | `true` | Muestra panel de miniaturas |
260
+ | `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de parámetros |
261
+ | `currentPage` | `number` | `1` | Página inicial |
262
+ | `apiBaseUrl` | `string` | — | URL base de NexaBase |
263
+ | `apiKey` | `string` | — | API key para NexaBase |
264
+
265
+ ### Métodos (DOM)
195
266
 
196
267
  ```ts
197
- const viewer = document.querySelector('nexa-viewer');
198
-
199
- // Exportación
200
- await viewer.exportPdf(); // PDF vectorial
201
- await viewer.exportPdfWithBookmarks(); // PDF con panel de marcadores
202
- await viewer.exportExcel(); // Excel (.xlsx)
203
- await viewer.exportWord(); // Word (.docx)
204
- await viewer.exportCsv(); // CSV (.csv)
205
-
206
- // Navegación y Datos
207
- viewer.goToPage(3); // Cambiar a página específica
208
- viewer.updateData(data); // Actualizar registros dinámicamente
268
+ const v = document.querySelector('nexa-viewer');
269
+
270
+ await v.exportPdf(); // PDF vectorial
271
+ await v.exportPdfWithBookmarks(); // PDF con marcadores
272
+ await v.exportExcel(); // .xlsx (SheetJS)
273
+ await v.exportWord(); // .docx
274
+ await v.exportCsv(); // .csv con BOM UTF-8
275
+ v.goToPage(3); // Navegar a página
276
+ v.updateData(nuevosDatos); // Actualizar datos
209
277
  ```
210
278
 
211
279
  ### Eventos
212
280
 
213
281
  ```ts
214
- viewer.addEventListener('page-change', (e) => console.log(e.detail));
215
- viewer.addEventListener('drill-click', (e) => console.log(e.detail));
282
+ v.addEventListener('page-change', e => console.log(e.detail.page));
283
+ v.addEventListener('drill-click', e => console.log(e.detail));
216
284
  ```
217
285
 
218
286
  ---
@@ -223,125 +291,141 @@ viewer.addEventListener('drill-click', (e) => console.log(e.detail));
223
291
 
224
292
  ```json
225
293
  [
226
- { "id": 1, "nombre": "Juan", "ventas": 1500 },
227
- { "id": 2, "nombre": "María", "ventas": 2300 }
294
+ { "id": 1, "nombre": "Juan Pérez", "total": 150000 },
295
+ { "id": 2, "nombre": "María López", "total": 230000 }
228
296
  ]
229
297
  ```
230
298
 
299
+ El reporte usa `dataSource: ""` (vacío) para tomar este array como `"main"`.
300
+
231
301
  ### Multiple DataSources (objeto con alias)
232
302
 
233
303
  ```json
234
304
  {
235
305
  "clientes": [
236
- { "id": 1, "nombre": "Juan" }
306
+ { "id": 1, "nombre": "Juan", "ciudad": "Bogotá" }
237
307
  ],
238
308
  "pedidos": [
239
- { "id": 101, "cliente_id": 1, "total": 500 }
309
+ { "cliente_id": 1, "producto": "Camisa", "cantidad": 3 }
240
310
  ]
241
311
  }
242
312
  ```
243
313
 
314
+ El reporte usa `dataSource: "clientes"` para referirse a ese alias.
315
+
316
+ ### Con parámetros
317
+
318
+ ```ts
319
+ // Pasando parameters al viewer
320
+ viewer.parameters = {
321
+ fechaInicio: '2024-01-01',
322
+ fechaFin: '2024-12-31',
323
+ categoria: ' electronics'
324
+ };
325
+ ```
326
+
244
327
  ---
245
328
 
246
- ## Variables de sistema
329
+ ## Variables y expresiones
330
+
331
+ ### Variables de sistema
247
332
 
248
333
  | Variable | Descripción | Ejemplo |
249
334
  |----------|-------------|---------|
250
335
  | `{{Page}}` | Página actual | `1` |
251
336
  | `{{TotalPages}}` | Total de páginas | `5` |
252
- | `{{Today}}` | Fecha actual | `2024-01-15` |
253
- | `{{Now}}` | Fecha y hora actual | `2024-01-15 14:30` |
337
+ | `{{Today}}` | Fecha actual (YYYY-MM-DD) | `2024-01-15` |
338
+ | `{{Now}}` | Fecha y hora actual | `2024-01-15 14:30:00` |
339
+ | `{{Year}}` | Año actual | `2024` |
340
+ | `{{Month}}` | Mes actual | `1` |
341
+ | `{{Day}}` | Día actual | `15` |
254
342
  | `{{ReportName}}` | Nombre del reporte | `Ventas Mensuales` |
255
- | `{{RowNumber}}` | Número de fila | `1` |
343
+ | `{{RowNumber}}` | Número de fila (1-based) | `1` |
256
344
  | `{{TotalRows}}` | Total de filas | `50` |
257
- | `{{EvenRow}}` / `{{OddRow}}` | Fila par/impar | `true` / `false` |
258
-
259
- ---
345
+ | `{{EvenRow}}` | `true` si fila es par | `true` |
346
+ | `{{OddRow}}` | `true` si fila es impar | `false` |
347
+ | `{{FirstRow}}` / `{{LastRow}}` | Primera/última fila | `true` |
348
+ | `{{GroupKey}}` | Clave de agrupación actual | `electronics` |
349
+ | `{{GroupCount}}` | Conteo del grupo actual | `12` |
260
350
 
261
- ## Funciones de expresiones
262
-
263
- Usar sintaxis `{[...]}`:
351
+ ### Expresiones con `{[...]}`
264
352
 
265
353
  ```
266
354
  {[FormatNumber(precio, 'es-ES')]} → 1.234,56
267
355
  {[FormatDate(fecha, 'dd/MM/yyyy')]} → 15/01/2024
268
356
  {[FormatCurrency(total, 'USD')]} → $1,234.56
269
- {[IIF(total > 100, 'OK', 'Bajo')]} OK
270
- {[ISNULL(campo, 'Sin dato')]} → Sin dato
271
- {[UPPER(nombre)]} → JUAN
272
- {[SUBSTRING(texto, 1, 3)]} → pri
357
+ {[IIF(total > 100000, 'ALTO', 'BAJO')]} ALTO
358
+ {[ISNULL(cliente, 'Sin nombre')]} → Sin nombre
359
+ {[UPPER(nombre)]} → JUAN PÉREZ
360
+ {[SUBSTRING(texto, 0, 3)]} → "Hel" (primeros 3)
273
361
  {[ABS(-5)]} → 5
274
362
  {[CEIL(5.3)]} → 6
275
363
  {[FLOOR(5.9)]} → 5
276
364
  ```
277
365
 
278
- ---
366
+ ### Agregaciones en tablas
279
367
 
280
- ## Exportación programática
368
+ En `footerText` de columnas:
369
+ ```
370
+ Total: {[sum(cantidad)]}
371
+ Cantidad: {[count(id)]}
372
+ Promedio: {[avg(precio)]}
373
+ Máximo: {[max(total)]}
374
+ Mínimo: {[min(total)]}
375
+ Primero: {[first(nombre)]}
376
+ Último: {[last(apellido)]}
377
+ ```
281
378
 
282
- ```ts
283
- // React / Vue / Angular
284
- const viewer = viewerRef.current;
285
- await viewer.exportPdf();
379
+ ### Bindings mixtos (content + binding)
286
380
 
287
- // HTML puro
288
- const viewer = document.getElementById('viewer');
289
- await viewer.exportPdf();
381
+ ```json
382
+ {
383
+ "type": "Text",
384
+ "content": "Cliente: {{nombre}} ({{ciudad}})",
385
+ "binding": "nombre"
386
+ }
290
387
  ```
291
388
 
292
389
  ---
293
390
 
294
- ## Solución de problemas
295
-
296
- ### `<nexa-viewer>` no se renderiza
391
+ ## Ejemplos de integración
297
392
 
298
- Asegúrate de llamar `registerNexaReport()` **antes** de montar el componente, e importar `nexabase-report/style.css`.
393
+ Los ejemplos JSON en `examples/` son reportes completos que puedes cargar:
299
394
 
300
- ### TypeScript: `'nexa-viewer' is not a known element`
395
+ | Archivo | Descripción |
396
+ |---------|-------------|
397
+ | `report-factura.json` | Factura con tabla de items, cliente, totales |
398
+ | `factura_de_recolección_de_residuos.json` | Factura ambiental con 3 DataSources relacionados |
399
+ | `report-ventas-logo-tabla.json` | Reporte con logo, tabla agrupada, resumen |
400
+ | `report-agrupado-por-cliente.json` | GroupHeader + Table con agrupación |
401
+ | `report-productos.json` | Lista simple de productos |
402
+ | `report-crosstab-categoria-mes.json` | Tabla dinámica (crosstab) |
403
+ | `report-grafico-ventas-por-mes.json` | Gráfico de barras con datos mensuales |
404
+ | `report-master-detail-ordenes.json` | Master-detail con subreportes |
405
+ | `report-codigos-qr-barcode.json` | Códigos QR y barras |
301
406
 
302
- En React/Vue/Angular, el custom element no está en el JSX/TSX estándar. Agrega declaraciones de tipo locales:
407
+ ### Cargar un ejemplo
303
408
 
304
409
  ```ts
305
- declare module 'nexabase-report' {
306
- export function registerNexaReport(): void;
307
- }
410
+ const res = await fetch('/examples/report-factura.json');
411
+ const definition = await res.json();
412
+ const data = [ /* tus datos */ ];
308
413
 
309
- declare global {
310
- namespace JSX {
311
- interface IntrinsicElements {
312
- 'nexa-viewer': any;
313
- }
314
- }
315
- }
414
+ viewer.definition = definition;
415
+ viewer.data = data;
316
416
  ```
317
417
 
318
- En Angular usa `CUSTOM_ELEMENTS_SCHEMA` en tu `@Component`.
319
-
320
- ### Los datos no aparecen
321
-
322
- Verifica que `dataSource` en la definición del reporte coincida con el alias de tus datos. El primer datasource suele tener alias `"main"`.
323
-
324
418
  ---
325
419
 
326
- ## Ejemplos de integración
327
-
328
- Puedes abrir estos archivos HTML directamente en tu navegador (requiere `npm run build` primero):
329
-
330
- | Archivo | Framework | Descripción |
331
- |---------|-----------|-------------|
332
- | [HTML puro](examples/integration-vanilla.html) | Ninguno | Carga inline con UMD bundle |
333
- | [Vue 3](examples/integration-vue.html) | Vue 3 (CDN) | Integración con Vue via CDN |
334
- | [React](examples/integration-react.html) | React 18 (CDN) | Integración con React via CDN |
335
- | [Angular](examples/integration-angular.html) | Angular 18 (CDN) | Integración con Angular via CDN |
336
- | [Carga desde API](examples/integration-api.html) | Ninguno | Carga dinámica desde URL |
337
-
338
420
  ## Documentación adicional
339
421
 
340
- - [API del Viewer](docs/VIEWER_API.md)
341
- - [Integración externa](docs/EXTERNAL_INTEGRATION.md)
342
- - [Plan de QA](docs/QA_PLAN.md)
343
- - [Guía de publicación](docs/PUBLISHING.md)
422
+ - [API del Viewer](docs/VIEWER_API.md) — Métodos, eventos, props detallados
423
+ - [Integración con sistemas externos](docs/EXTERNAL_INTEGRATION.md) — ASP.NET, Django, Rails, etc.
424
+ - [Guía de publicación npm](docs/PUBLISHING.md) — CI/CD, versiones, npmjs
425
+ - [Plan de QA](docs/QA_PLAN.md) — Checklist de pruebas manuales
426
+
427
+ ---
344
428
 
345
429
  ## Licencia
346
430
 
347
- MIT © NexaBase Team
431
+ MIT © NexaBase Team
@@ -1,5 +1,5 @@
1
- import { a as c } from "./index-DxNI7UUB.js";
2
- import { r as f } from "./html2canvas-CVvkCg-2.js";
1
+ import { a as c } from "./index-Db31OLYK.js";
2
+ import { r as f } from "./html2canvas-IHQe1dL8.js";
3
3
  function l(r, n) {
4
4
  for (var o = 0; o < n.length; o++) {
5
5
  const e = n[o];
@@ -1,4 +1,4 @@
1
- import { c as MQ } from "./index-DxNI7UUB.js";
1
+ import { c as MQ } from "./index-Db31OLYK.js";
2
2
  var nt = { exports: {} };
3
3
  /*!
4
4
  * html2canvas 1.4.1 <https://html2canvas.hertzen.com>
@@ -1,6 +1,6 @@
1
- import { g as De, a as Ke, c as Te } from "./index-DxNI7UUB.js";
2
- import { j as Ge } from "./jspdf.es.min-CluyHBde.js";
3
- import { r as Be } from "./html2canvas-CVvkCg-2.js";
1
+ import { g as De, a as Ke, c as Te } from "./index-Db31OLYK.js";
2
+ import { j as Ge } from "./jspdf.es.min-CHME-xrd.js";
3
+ import { r as Be } from "./html2canvas-IHQe1dL8.js";
4
4
  function Ue(ge, we) {
5
5
  for (var me = 0; me < we.length; me++) {
6
6
  const ce = we[me];