nexabase-report 0.2.13 → 0.2.14

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,10 +2,10 @@
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/)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue)](https://www.typescriptlang.org/)
6
6
  [![Vue 3](https://img.shields.io/badge/Vue-3.5-green)](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**, **visor framework-agnostic** como Custom Element, y **exportación 100% cliente** a PDF, Excel, Word y CSV.
8
+ > Librería Vue 3 + TypeScript para diseñar y visualizar reportes tipo banded (inspirada en Stimulsoft / DevExpress). Incluye **diseñador WYSIWYG** con multilingüe (ES/EN/PT), **visor framework-agnostic** como Custom Element, y **exportación 100% cliente** a PDF vectorial, Excel, Word y CSV.
9
9
 
10
10
  ---
11
11
 
@@ -14,12 +14,13 @@
14
14
  - [Características](#características)
15
15
  - [Instalación](#instalación)
16
16
  - [Uso rápido](#uso-rápido)
17
- - [Diseñador vs Viewer](#diseñador-vs-viewer)
17
+ - [Diseñador (NexaDesigner)](#diseñador-nexadesigner)
18
18
  - [API del Viewer](#api-del-viewer)
19
19
  - [Formato de datos](#formato-de-datos)
20
20
  - [Variables y expresiones](#variables-y-expresiones)
21
- - [Ejemplos de integración](#ejemplos-de-integración)
21
+ - [Ejemplos](#ejemplos)
22
22
  - [Documentación adicional](#documentación-adicional)
23
+ - [Publicación](#publicación)
23
24
 
24
25
  ---
25
26
 
@@ -28,17 +29,21 @@
28
29
  | Característica | Descripción |
29
30
  |----------------|-------------|
30
31
  | **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 |
36
- | **Códigos QR / Barras** | jsbarcode + qrcode |
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 |
32
+ | **Diseñador WYSIWYG** | `NexaDesigner.vue` — lienzo, ribbon, sidebars, multilingüe ES/EN/PT |
33
+ | **Visor standalone** | `<nexa-viewer>` — sin Vue en el proyecto consumidor |
34
+ | **Exportación 100% cliente** | PDF vectorial (jsPDF), Excel (SheetJS), Word (docx), CSV (UTF-8 BOM) |
35
+ | **Gráficos** | ECharts integrado (barras, líneas, pastel, área, scatter, radar, etc.) |
36
+ | **Tablas dinámicas** | Crosstabs nativos con agregaciones (sum, count, avg, min, max) |
37
+ | **Códigos QR / Barras** | jsbarcode + qrcode con bindings a datos |
38
+ | **Formato condicional** | Reglas visuales por campo y operador (eq, gt, contains, between, etc.) |
39
+ | **Master-Detail** | Relaciones padre-hijo entre bandas con filtro automático |
40
+ | **Drill-Down** | Navegación a reportes destino con paso de parámetros |
41
+ | **Subreportes** | Anidamiento de reportes desde definición embebida o remota |
42
+ | **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `FormatDate`, `IIF`, `ISNULL`, agregaciones |
43
+ | **Parámetros** | Diálogo de entrada, valores por props, `skipParamsDialog` |
44
+ | **Paginación** | Automática por altura de página con thumbnails y navegación |
45
+ | **Shapes** | Rectángulos, elipses, líneas y flechas con estilo configurable |
46
+ | **i18n** | Interfaz multilingüe del diseñador: español, inglés, portugués |
42
47
 
43
48
  ---
44
49
 
@@ -64,23 +69,25 @@ import 'nexabase-report/style.css';
64
69
  <!DOCTYPE html>
65
70
  <html>
66
71
  <head>
67
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report/dist/style.css">
72
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/style.css">
68
73
  </head>
69
74
  <body>
70
75
  <nexa-viewer id="viewer" minimal></nexa-viewer>
71
76
 
72
- <script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
77
+ <script src="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/nexabase-report.umd.js"></script>
73
78
  <script>
74
79
  NexaReport.registerNexaReport();
75
80
 
76
81
  const viewer = document.getElementById('viewer');
77
- viewer.definition = { /* tu definición de reporte */ };
78
- viewer.data = [ /* array de datos */ ];
82
+ viewer.definition = { /* definición del reporte */ };
83
+ viewer.data = [ /* datos */ ];
79
84
  </script>
80
85
  </body>
81
86
  </html>
82
87
  ```
83
88
 
89
+ Ver ejemplo completo: [`examples/../viewer.html`](examples/../viewer.html)
90
+
84
91
  ### React
85
92
 
86
93
  ```tsx
@@ -90,8 +97,8 @@ import 'nexabase-report/style.css';
90
97
 
91
98
  registerNexaReport();
92
99
 
93
- export function ReportViewer({ definition, data }) {
94
- const ref = useRef<HTMLElement>(null);
100
+ export function ReportViewer({ definition, data }: { definition: any; data: any[] }) {
101
+ const ref = useRef<any>(null);
95
102
 
96
103
  useEffect(() => {
97
104
  if (!ref.current) return;
@@ -106,14 +113,14 @@ export function ReportViewer({ definition, data }) {
106
113
  ### Vue 3
107
114
 
108
115
  ```vue
109
- <script setup>
116
+ <script setup lang="ts">
110
117
  import { registerNexaReport } from 'nexabase-report';
111
118
  import 'nexabase-report/style.css';
112
119
 
113
120
  registerNexaReport();
114
121
 
115
122
  const props = defineProps<{
116
- definition: object;
123
+ definition: any;
117
124
  data: any[];
118
125
  }>();
119
126
  </script>
@@ -136,112 +143,110 @@ import 'nexabase-report/style.css';
136
143
 
137
144
  registerNexaReport();
138
145
 
139
- // En tu componente
140
146
  @Component({
141
147
  template: '<nexa-viewer #viewer [definition]="reportDef" [data]="reportData"></nexa-viewer>',
142
148
  })
143
149
  export class ReportComponent {
144
150
  @ViewChild('viewer') viewerRef!: ElementRef;
145
-
146
- reportDef = null;
151
+ reportDef: any = null;
147
152
  reportData: any[] = [];
148
153
 
149
154
  async ngOnInit() {
150
- const res = await fetch('/assets/report.json');
151
- this.reportDef = await res.json();
155
+ this.reportDef = await fetch('/assets/report.json').then(r => r.json());
152
156
  this.reportData = await fetch('/api/data').then(r => r.json());
153
157
  }
154
158
  }
155
159
  ```
156
160
 
157
- ### Blazor Razor (.NET 8)
161
+ ### Blazor (.NET 8+)
158
162
 
159
163
  ```razor
160
164
  @page "/reporte/{Id:int}"
161
- @inject HttpClient Http
162
165
  @inject IJSRuntime JS
163
166
 
164
167
  <link rel="stylesheet" href="_content/nexabase-report/style.css" />
165
168
  <script src="_content/nexabase-report/nexabase-report.umd.js"></script>
166
169
 
167
- <div style="height:700px">
168
- <nexa-viewer id="viewer" minimal></nexa-viewer>
169
- </div>
170
+ <nexa-viewer id="viewer" minimal></nexa-viewer>
170
171
 
171
172
  @code {
172
173
  [Parameter] public int Id { get; set; }
173
174
 
174
- protected override async Task OnInitializedAsync()
175
+ protected override async Task OnAfterRenderAsync(bool firstRender)
175
176
  {
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);
177
+ if (!firstRender) return;
178
+ var def = await Http.GetFromJsonAsync<object>($"/api/reportes/{Id}");
179
+ var data = await Http.GetFromJsonAsync<List<object>>($"/api/reportes/{Id}/datos");
180
+ await JS.InvokeVoidAsync("renderNexaReport", def, data);
180
181
  }
181
182
  }
182
183
 
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;
184
+ <script>
185
+ window.renderNexaReport = function(definition, data) {
186
+ if (!window.__nexaRegistered) {
187
+ NexaReport.registerNexaReport();
188
+ window.__nexaRegistered = true;
193
189
  }
194
- </script>
195
- }
190
+ const v = document.getElementById('viewer');
191
+ v.definition = definition;
192
+ v.data = data;
193
+ };
194
+ </script>
196
195
  ```
197
196
 
198
- O si prefieres usar un componente wrapper en Razor:
197
+ ---
199
198
 
200
- ```razor
201
- @* Shared/ReportViewer.razor *@
202
- @inject IJSRuntime JS
199
+ ## Diseñador (NexaDesigner)
203
200
 
204
- <div style="height: @Height">
205
- <nexa-viewer @ref="viewerRef" id="@Id" minimal></nexa-viewer>
206
- </div>
201
+ El diseñador WYSIWYG es un componente Vue 3 que se integra en apps con Vue. Incluye:
207
202
 
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; }
203
+ - **Ribbon** con formato de texto (fuente, tamaño, color, negrita, cursiva)
204
+ - **Lienzo** con snap-to-grid, rulers y selección múltiple
205
+ - **Panel de propiedades** modular (estilo, datos, condicional)
206
+ - **Diccionario de datos** con campos del datasource
207
+ - **Multilingüe**: español, inglés y portugués (cambiable desde toolbar)
208
+ - **Import/Export** de definiciones JSON
209
+ - **Deshacer/Rehacer** (Ctrl+Z / Ctrl+Y)
210
+ - **Clipboard** (copiar/pegar elementos entre bandas)
213
211
 
214
- private ElementReference viewerRef;
212
+ ### Uso
215
213
 
216
- protected override async Task OnAfterRenderAsync(bool firstRender)
217
- {
218
- if (firstRender)
219
- {
220
- await JS.InvokeVoidAsync("nexaReportInterop.init", viewerRef, Definition, Data);
221
- }
222
- }
214
+ ```vue
215
+ <script setup lang="ts">
216
+ import { NexaDesigner } from 'nexabase-report';
217
+ import 'nexabase-report/style.css';
218
+ import { ref } from 'vue';
223
219
 
224
- public async Task ExportPdf() => await JS.InvokeVoidAsync("nexaReportInterop.exportPdf", viewerRef);
225
- public async Task ExportExcel() => await JS.InvokeVoidAsync("nexaReportInterop.exportExcel", viewerRef);
220
+ const designerRef = ref(null);
221
+ const locale = ref('es'); // 'es' | 'en' | 'pt'
222
+
223
+ function onSave(reportDef: any) {
224
+ console.log('Reporte guardado:', reportDef);
226
225
  }
226
+ </script>
227
+
228
+ <template>
229
+ <NexaDesigner
230
+ ref="designerRef"
231
+ :locale="locale"
232
+ @save="onSave"
233
+ style="height: 100vh"
234
+ />
235
+ </template>
227
236
  ```
228
237
 
229
- ---
238
+ ### Props
230
239
 
231
- ## Diseñador vs Viewer
240
+ | Prop | Tipo | Default | Descripción |
241
+ |------|------|---------|-------------|
242
+ | `locale` | `'es' \| 'en' \| 'pt'` | `'es'` | Idioma del diseñador |
243
+ | `initialReport` | `NexaReportDefinition` | — | Reporte a editar (nuevo si no se pasa) |
232
244
 
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í |
245
+ ### Eventos
240
246
 
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
247
+ | Evento | Payload | Descripción |
248
+ |--------|---------|-------------|
249
+ | `save` | `NexaReportDefinition` | Usuario presionó guardar |
245
250
 
246
251
  ---
247
252
 
@@ -251,15 +256,15 @@ O si prefieres usar un componente wrapper en Razor:
251
256
 
252
257
  | Prop | Tipo | Default | Descripción |
253
258
  |------|------|---------|-------------|
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 |
259
+ | `definition` | `string \| NexaReportDefinition` | — | Definición del reporte (objeto o JSON string) |
260
+ | `data` | `string \| any[] \| Record<string, any[]>` | — | Datos: array simple, objeto multi-alias, o JSON string |
261
+ | `parameters` | `Record<string, any>` | | Valores iniciales para los parámetros |
257
262
  | `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 |
263
+ | `showToolbar` | `boolean` | `true` | Muestra barra superior de exportación |
264
+ | `showThumbs` | `boolean` | `true` | Muestra miniaturas laterales |
265
+ | `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de parámetros al cargar |
261
266
  | `currentPage` | `number` | `1` | Página inicial |
262
- | `apiBaseUrl` | `string` | — | URL base de NexaBase |
267
+ | `apiBaseUrl` | `string` | — | URL base de NexaBase (backend) |
263
268
  | `apiKey` | `string` | — | API key para NexaBase |
264
269
 
265
270
  ### Métodos (DOM)
@@ -267,13 +272,16 @@ O si prefieres usar un componente wrapper en Razor:
267
272
  ```ts
268
273
  const v = document.querySelector('nexa-viewer');
269
274
 
270
- await v.exportPdf(); // PDF vectorial
271
- await v.exportPdfWithBookmarks(); // PDF con marcadores
275
+ await v.exportPdf(); // PDF vectorial (jsPDF)
276
+ await v.exportPdfWithBookmarks(); // PDF + panel de marcadores
272
277
  await v.exportExcel(); // .xlsx (SheetJS)
273
278
  await v.exportWord(); // .docx
274
- await v.exportCsv(); // .csv con BOM UTF-8
279
+ await v.exportCsv(); // .csv (UTF-8 BOM)
280
+
275
281
  v.goToPage(3); // Navegar a página
276
- v.updateData(nuevosDatos); // Actualizar datos
282
+ v.updateData(nuevosDatos); // Actualizar datos sin recargar definición
283
+
284
+ console.log(v.pageNumber, v.totalPages, v.paramValues);
277
285
  ```
278
286
 
279
287
  ### Eventos
@@ -281,13 +289,15 @@ v.updateData(nuevosDatos); // Actualizar datos
281
289
  ```ts
282
290
  v.addEventListener('page-change', e => console.log(e.detail.page));
283
291
  v.addEventListener('drill-click', e => console.log(e.detail));
292
+ v.addEventListener('subreport-toggle', e => console.log(e.detail));
293
+ v.addEventListener('data-request', e => console.log(e.detail.alias));
284
294
  ```
285
295
 
286
296
  ---
287
297
 
288
298
  ## Formato de datos
289
299
 
290
- ### Single DataSource (array simple)
300
+ ### Array simple (único DataSource)
291
301
 
292
302
  ```json
293
303
  [
@@ -296,9 +306,7 @@ v.addEventListener('drill-click', e => console.log(e.detail));
296
306
  ]
297
307
  ```
298
308
 
299
- El reporte usa `dataSource: ""` (vacío) para tomar este array como `"main"`.
300
-
301
- ### Multiple DataSources (objeto con alias)
309
+ ### Múltiples DataSources (objeto con alias)
302
310
 
303
311
  ```json
304
312
  {
@@ -306,21 +314,21 @@ El reporte usa `dataSource: ""` (vacío) para tomar este array como `"main"`.
306
314
  { "id": 1, "nombre": "Juan", "ciudad": "Bogotá" }
307
315
  ],
308
316
  "pedidos": [
309
- { "cliente_id": 1, "producto": "Camisa", "cantidad": 3 }
317
+ { "cliente_id": 1, "producto": "Camisa", "cantidad": 3 },
318
+ { "cliente_id": 1, "producto": "Pantalón", "cantidad": 2 }
310
319
  ]
311
320
  }
312
321
  ```
313
322
 
314
- El reporte usa `dataSource: "clientes"` para referirse a ese alias.
323
+ El reporte usa `dataSource: "clientes"` (o el alias correspondiente) en la banda.
315
324
 
316
- ### Con parámetros
325
+ ### Parámetros
317
326
 
318
327
  ```ts
319
- // Pasando parameters al viewer
320
328
  viewer.parameters = {
321
329
  fechaInicio: '2024-01-01',
322
330
  fechaFin: '2024-12-31',
323
- categoria: ' electronics'
331
+ categoria: 'Electronics'
324
332
  };
325
333
  ```
326
334
 
@@ -334,95 +342,88 @@ viewer.parameters = {
334
342
  |----------|-------------|---------|
335
343
  | `{{Page}}` | Página actual | `1` |
336
344
  | `{{TotalPages}}` | Total de páginas | `5` |
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` |
345
+ | `{{Today}}` | Fecha actual (YYYY-MM-DD) | `2026-05-13` |
346
+ | `{{Now}}` | Fecha y hora actual | `2026-05-13 14:30:00` |
347
+ | `{{Year}}` | Año actual | `2026` |
348
+ | `{{Month}}` | Mes actual | `5` |
349
+ | `{{Day}}` | Día actual | `13` |
342
350
  | `{{ReportName}}` | Nombre del reporte | `Ventas Mensuales` |
343
351
  | `{{RowNumber}}` | Número de fila (1-based) | `1` |
344
352
  | `{{TotalRows}}` | Total de filas | `50` |
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` |
353
+ | `{{EvenRow}}` / `{{OddRow}}` | Fila par / impar | `true` / `false` |
354
+ | `{{FirstRow}}` / `{{LastRow}}` | Primera / última fila | `true` |
355
+ | `{{GroupKey}}` | Clave del grupo actual | `Electronics` |
356
+ | `{{GroupCount}}` | Registros en el grupo actual | `12` |
357
+
358
+ ### Expresiones `{[...]}`
359
+
360
+ | Expresión | Resultado |
361
+ |-----------|-----------|
362
+ | `{[FormatNumber(precio, 'es-ES')]}` | `1.234,56` |
363
+ | `{[FormatDate(fecha, 'dd/MM/yyyy')]}` | `15/01/2024` |
364
+ | `{[FormatCurrency(total, 'USD')]}` | `$1,234.56` |
365
+ | `{[IIF(total > 100000, 'ALTO', 'BAJO')]}` | `ALTO` |
366
+ | `{[ISNULL(cliente, 'Sin nombre')]}` | `Sin nombre` |
367
+ | `{[UPPER(nombre)]}` | `JUAN PÉREZ` |
368
+ | `{[SUBSTRING(texto, 0, 3)]}` | `Hel` |
369
+ | `{[ABS(-5)]}` / `{[CEIL(5.3)]}` / `{[FLOOR(5.9)]}` | `5` / `6` / `5` |
370
+ | `{[SUM(cantidad)]}` / `{[COUNT(id)]}` / `{[AVG(precio)]}` | Agregaciones |
371
+ | `{[CONCAT(nombre, ' - ', ciudad)]}` | `Juan - Bogotá` |
350
372
 
351
- ### Expresiones con `{[...]}`
373
+ ---
352
374
 
353
- ```
354
- {[FormatNumber(precio, 'es-ES')]} → 1.234,56
355
- {[FormatDate(fecha, 'dd/MM/yyyy')]} → 15/01/2024
356
- {[FormatCurrency(total, 'USD')]} → $1,234.56
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)
361
- {[ABS(-5)]} → 5
362
- {[CEIL(5.3)]} → 6
363
- {[FLOOR(5.9)]} → 5
364
- ```
375
+ ## Ejemplos
365
376
 
366
- ### Agregaciones en tablas
377
+ Los reportes de ejemplo están en `examples/` y son compatibles tanto con el diseñador como con el viewer.
367
378
 
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
- ```
378
-
379
- ### Bindings mixtos (content + binding)
379
+ | Archivo | Descripción |
380
+ |---------|-------------|
381
+ | `report-factura.json` | Factura con ítems, cliente y totales |
382
+ | `factura_de_recolección_de_residuos.json` | Factura ambiental con múltiples DataSources |
383
+ | `report-factura-residuos.json` | Factura de residuos con GroupHeader/Footer |
384
+ | `report-anexo-factura.json` | Anexo complementario con tabla expandida |
385
+ | `report-ventas-logo-tabla.json` | Reporte corporativo con logo y tabla agrupada |
386
+ | `report-agrupado-por-cliente.json` | GroupHeader + Table con agrupación por cliente |
387
+ | `report-productos.json` | Lista simple de productos |
388
+ | `report-crosstab-categoria-mes.json` | Tabla dinámica (crosstab) categoría × mes |
389
+ | `report-grafico-ventas-por-mes.json` | Gráfico de barras (ECharts) |
390
+ | `report-master-detail-ordenes.json` | Master-Detail con subreportes relacionados |
391
+ | `report-codigos-qr-barcode.json` | Códigos QR y barras desde datos |
380
392
 
381
- ```json
382
- {
383
- "type": "Text",
384
- "content": "Cliente: {{nombre}} ({{ciudad}})",
385
- "binding": "nombre"
386
- }
393
+ ```bash
394
+ # Probar un ejemplo local
395
+ curl -s https://raw.githubusercontent.com/nexabase/nexabase-report/main/examples/report-factura.json
387
396
  ```
388
397
 
389
398
  ---
390
399
 
391
- ## Ejemplos de integración
392
-
393
- Los ejemplos JSON en `examples/` son reportes completos que puedes cargar:
400
+ ## Publicación
394
401
 
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 |
406
-
407
- ### Cargar un ejemplo
402
+ ```bash
403
+ npm run build
404
+ npm version patch|minor|major
405
+ npm publish --access public
406
+ ```
408
407
 
409
- ```ts
410
- const res = await fetch('/examples/report-factura.json');
411
- const definition = await res.json();
412
- const data = [ /* tus datos */ ];
408
+ Disponible en CDN automáticamente tras publicar:
413
409
 
414
- viewer.definition = definition;
415
- viewer.data = data;
410
+ ```html
411
+ <script src="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/nexabase-report.umd.js"></script>
412
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report@0.2/dist/style.css">
416
413
  ```
417
414
 
415
+ Ver [`docs/PUBLISHING.md`](docs/PUBLISHING.md) para CI/CD y registry privado.
416
+
418
417
  ---
419
418
 
420
419
  ## Documentación adicional
421
420
 
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
421
+ - [API completa del Viewer](docs/VIEWER_API.md) — Props, métodos, eventos, tipos
422
+ - [API REST (funciones serverless)](docs/API_REST.md) — Endpoints para listar/render/exportar
423
+ - [Integración externa](docs/EXTERNAL_INTEGRATION.md) — ASP.NET, Django, Laravel, Rails
425
424
  - [Plan de QA](docs/QA_PLAN.md) — Checklist de pruebas manuales
425
+ - [Checklist de regresión exportación](docs/EXPORT_REGRESSION.md) — PDF, Excel, Word, CSV
426
+ - [Guía de publicación npm](docs/PUBLISHING.md) — Build, versionado, CI/CD
426
427
 
427
428
  ---
428
429
 
@@ -1,4 +1,4 @@
1
- import { c as MQ } from "./index-B77DEEx0.js";
1
+ import { c as MQ } from "./index-CrlafZ0y.js";
2
2
  var nt = { exports: {} };
3
3
  /*!
4
4
  * html2canvas 1.4.1 <https://html2canvas.hertzen.com>
@@ -1,5 +1,5 @@
1
- import { a as c } from "./index-B77DEEx0.js";
2
- import { r as f } from "./html2canvas-DTMBknrb.js";
1
+ import { a as c } from "./index-CrlafZ0y.js";
2
+ import { r as f } from "./html2canvas-Bz1P596e.js";
3
3
  function l(r, n) {
4
4
  for (var o = 0; o < n.length; o++) {
5
5
  const e = n[o];
@@ -1,6 +1,6 @@
1
- import { g as De, a as Ke, c as Te } from "./index-B77DEEx0.js";
2
- import { j as Ge } from "./jspdf.es.min-Bq3BwrOB.js";
3
- import { r as Be } from "./html2canvas-DTMBknrb.js";
1
+ import { g as De, a as Ke, c as Te } from "./index-CrlafZ0y.js";
2
+ import { j as Ge } from "./jspdf.es.min-DEtCEkBC.js";
3
+ import { r as Be } from "./html2canvas-Bz1P596e.js";
4
4
  function Ue(ge, we) {
5
5
  for (var me = 0; me < we.length; me++) {
6
6
  const ce = we[me];