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 +255 -171
- package/dist/{html2canvas-CVos72rP.js → html2canvas-DreS5Xph.js} +2 -2
- package/dist/{html2canvas-CVvkCg-2.js → html2canvas-IHQe1dL8.js} +1 -1
- package/dist/{html2pdf-Cyvo1NsU.js → html2pdf-BxljWFVL.js} +3 -3
- package/dist/{index-DxNI7UUB.js → index-Db31OLYK.js} +22644 -20905
- package/dist/{index.es-Cl2FwCxT.js → index.es-BOI7YUNH.js} +2 -2
- package/dist/{jspdf.es.min-CluyHBde.js → jspdf.es.min-CHME-xrd.js} +2 -2
- package/dist/nexabase-report.es.js +1 -1
- package/dist/nexabase-report.umd.js +151 -151
- package/examples/blazor-razor-invoice/invoice-report.json +537 -0
- package/examples/integration/AspNetCoreRazorPages.cshtml +52 -0
- package/examples/integration/BlazorRazor.razor +84 -0
- package/examples/integration/BlazorServer.razor +91 -0
- package/examples/integration/index.html +100 -0
- package/examples/integration/integration-jquery.html +142 -0
- package/package.json +1 -1
- package/dist/browser-lud4wlfC.js +0 -1473
package/README.md
CHANGED
|
@@ -2,27 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/nexabase-report)
|
|
4
4
|
[](LICENSE)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](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
|
-
|
|
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
|
|
23
|
-
- [
|
|
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** |
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
38
|
-
| **
|
|
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
|
|
41
|
-
| **Master-Detail** | Subreportes anidados |
|
|
42
|
-
| **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `IIF`, `ISNULL`, etc. |
|
|
43
|
-
| **
|
|
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
|
-
|
|
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
|
|
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
|
|
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<{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
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
|
-
|
|
139
|
-
template: '<nexa-viewer #viewer minimal></nexa-viewer>',
|
|
141
|
+
template: '<nexa-viewer #viewer [definition]="reportDef" [data]="reportData"></nexa-viewer>',
|
|
140
142
|
})
|
|
141
|
-
export class
|
|
142
|
-
@ViewChild('viewer'
|
|
143
|
+
export class ReportComponent {
|
|
144
|
+
@ViewChild('viewer') viewerRef!: ElementRef;
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
reportDef = null;
|
|
147
|
+
reportData: any[] = [];
|
|
148
|
+
|
|
149
|
+
async ngOnInit() {
|
|
146
150
|
const res = await fetch('/assets/report.json');
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
###
|
|
157
|
+
### Blazor Razor (.NET 8)
|
|
154
158
|
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
184
|
-
| `data` | `string \| any[] \|
|
|
185
|
-
| `parameters` | `
|
|
186
|
-
| `minimal` | `boolean
|
|
187
|
-
| `showToolbar` | `boolean
|
|
188
|
-
| `showThumbs` | `boolean
|
|
189
|
-
| `skipParamsDialog` | `boolean` | `false` | Salta el diálogo
|
|
190
|
-
| `currentPage` | `number` | `1` | Página inicial
|
|
191
|
-
| `apiBaseUrl` | `string` | — | URL base de NexaBase
|
|
192
|
-
| `apiKey` | `string` | — | API
|
|
193
|
-
|
|
194
|
-
### Métodos (
|
|
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
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
await
|
|
201
|
-
await
|
|
202
|
-
await
|
|
203
|
-
await
|
|
204
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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", "
|
|
227
|
-
{ "id": 2, "nombre": "María", "
|
|
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
|
-
{ "
|
|
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
|
|
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}}`
|
|
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
|
-
|
|
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 >
|
|
270
|
-
{[ISNULL(
|
|
271
|
-
{[UPPER(nombre)]} → JUAN
|
|
272
|
-
{[SUBSTRING(texto,
|
|
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
|
-
|
|
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
|
-
|
|
283
|
-
// React / Vue / Angular
|
|
284
|
-
const viewer = viewerRef.current;
|
|
285
|
-
await viewer.exportPdf();
|
|
379
|
+
### Bindings mixtos (content + binding)
|
|
286
380
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"type": "Text",
|
|
384
|
+
"content": "Cliente: {{nombre}} ({{ciudad}})",
|
|
385
|
+
"binding": "nombre"
|
|
386
|
+
}
|
|
290
387
|
```
|
|
291
388
|
|
|
292
389
|
---
|
|
293
390
|
|
|
294
|
-
##
|
|
295
|
-
|
|
296
|
-
### `<nexa-viewer>` no se renderiza
|
|
391
|
+
## Ejemplos de integración
|
|
297
392
|
|
|
298
|
-
|
|
393
|
+
Los ejemplos JSON en `examples/` son reportes completos que puedes cargar:
|
|
299
394
|
|
|
300
|
-
|
|
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
|
-
|
|
407
|
+
### Cargar un ejemplo
|
|
303
408
|
|
|
304
409
|
```ts
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
410
|
+
const res = await fetch('/examples/report-factura.json');
|
|
411
|
+
const definition = await res.json();
|
|
412
|
+
const data = [ /* tus datos */ ];
|
|
308
413
|
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
342
|
-
- [
|
|
343
|
-
- [
|
|
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-
|
|
2
|
-
import { r as f } from "./html2canvas-
|
|
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,6 +1,6 @@
|
|
|
1
|
-
import { g as De, a as Ke, c as Te } from "./index-
|
|
2
|
-
import { j as Ge } from "./jspdf.es.min-
|
|
3
|
-
import { r as Be } from "./html2canvas-
|
|
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];
|