nexabase-report 0.1.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 +106 -0
- package/dist/browser-lud4wlfC.js +1473 -0
- package/dist/favicon.svg +1 -0
- package/dist/html2canvas-BXLDsEU4.js +26 -0
- package/dist/html2canvas-CSJ68r_Q.js +4877 -0
- package/dist/html2pdf-DwP6YZUu.js +4242 -0
- package/dist/icons.svg +24 -0
- package/dist/index-BJcVIdAR.js +82960 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es-CLzdqdqQ.js +5646 -0
- package/dist/jspdf.es.min-CDx0J4wI.js +8109 -0
- package/dist/nexabase-report.es.js +7 -0
- package/dist/nexabase-report.umd.js +513 -0
- package/dist/purify.es-Bo7Q7b72.js +471 -0
- package/dist/style.css +1723 -0
- package/docs/API_REST.md +376 -0
- package/docs/EXPORT_REGRESSION.md +54 -0
- package/docs/EXTERNAL_INTEGRATION.md +211 -0
- package/docs/PUBLISHING.md +304 -0
- package/docs/QA_PLAN.md +138 -0
- package/docs/VIEWER_API.md +288 -0
- package/examples/report-agrupado-por-cliente.json +186 -0
- package/examples/report-codigos-qr-barcode.json +178 -0
- package/examples/report-crosstab-categoria-mes.json +123 -0
- package/examples/report-factura.json +287 -0
- package/examples/report-grafico-ventas-por-mes.json +127 -0
- package/examples/report-master-detail-ordenes.json +215 -0
- package/examples/report-productos.json +160 -0
- package/examples/report-ventas-logo-tabla.json +154 -0
- package/package.json +88 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Publicar NexaReport para que otros lo usen
|
|
2
|
+
|
|
3
|
+
## Opciones de distribución
|
|
4
|
+
|
|
5
|
+
| Método | Para quién | Complejidad | URL ejemplo |
|
|
6
|
+
|--------|-----------|-------------|-------------|
|
|
7
|
+
| **npm registry** | Cualquiera con `npm install` | Baja | `npm install nexabase-report` |
|
|
8
|
+
| **GitHub Releases** | Equipos internos, CI/CD | Baja | `npm i github:nexabase/nexabase-report` |
|
|
9
|
+
| **Verdaccio (privado)** | Empresa interna | Media | `npm install nexabase-report --registry=https://npm.miempresa.com` |
|
|
10
|
+
| **CDN (unpkg/jsDelivr)** | Quick demo, prototipos | Baja | `<script src="https://unpkg.com/nexabase-report">` |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. npm registry (recomendado)
|
|
15
|
+
|
|
16
|
+
### Prerequisitos
|
|
17
|
+
```bash
|
|
18
|
+
# Crear cuenta en npmjs.com (si no tienes)
|
|
19
|
+
npm adduser
|
|
20
|
+
# Username: tu-usuario-nexabase
|
|
21
|
+
# Password: ******
|
|
22
|
+
# Email: dev@nexabase.dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Preparar el paquete
|
|
26
|
+
```bash
|
|
27
|
+
# 1. Verificar que package.json esté correcto
|
|
28
|
+
# - "private" debe ser false (ya lo está)
|
|
29
|
+
# - version debe incrementarse
|
|
30
|
+
# - "files" debe incluir dist/, docs/, examples/
|
|
31
|
+
# - "exports" debe definir entry points
|
|
32
|
+
|
|
33
|
+
# 2. Build de producción
|
|
34
|
+
npm run build
|
|
35
|
+
|
|
36
|
+
# 3. Verificar qué se va a publicar
|
|
37
|
+
npm pack --dry-run
|
|
38
|
+
# Debe mostrar: dist/, docs/, examples/, README.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Publicar
|
|
42
|
+
```bash
|
|
43
|
+
# Publicar versión
|
|
44
|
+
npm publish --access public
|
|
45
|
+
|
|
46
|
+
# O si usas scoped package (@nexabase/report):
|
|
47
|
+
npm publish --access public
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Consumir
|
|
51
|
+
```bash
|
|
52
|
+
# En cualquier proyecto:
|
|
53
|
+
npm install nexabase-report
|
|
54
|
+
# o
|
|
55
|
+
npm install @nexabase/nexabase-report
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Actualizar versión
|
|
59
|
+
```bash
|
|
60
|
+
# patch: 0.1.0 → 0.1.1
|
|
61
|
+
npm version patch
|
|
62
|
+
|
|
63
|
+
# minor: 0.1.0 → 0.2.0
|
|
64
|
+
npm version minor
|
|
65
|
+
|
|
66
|
+
# major: 0.1.0 → 1.0.0
|
|
67
|
+
npm version major
|
|
68
|
+
|
|
69
|
+
# Luego publicar
|
|
70
|
+
npm publish
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 2. GitHub Releases
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Taggear versión
|
|
79
|
+
git tag v0.1.0
|
|
80
|
+
git push origin v0.1.0
|
|
81
|
+
|
|
82
|
+
# En GitHub → Releases → Draft new release → v0.1.0
|
|
83
|
+
# Adjuntar el .tgz generado por `npm pack`
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Consumir desde otro proyecto:
|
|
87
|
+
```bash
|
|
88
|
+
npm install github:nexabase/nexabase-report#v0.1.0
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 3. Registry privado (Verdaccio)
|
|
94
|
+
|
|
95
|
+
Para empresas que no quieren publicar en npm público:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Instalar Verdaccio
|
|
99
|
+
npm install -g verdaccio
|
|
100
|
+
verdaccio
|
|
101
|
+
|
|
102
|
+
# En .npmrc del proyecto consumidor:
|
|
103
|
+
# registry=https://npm.miempresa.com/
|
|
104
|
+
|
|
105
|
+
# Publicar
|
|
106
|
+
npm publish --registry=http://localhost:4873
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 4. CDN (sin instalación)
|
|
112
|
+
|
|
113
|
+
Una vez publicado en npm, automáticamente disponible en:
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<!-- unpkg -->
|
|
117
|
+
<script src="https://unpkg.com/nexabase-report@0.1.0/dist/nexabase-report.umd.js"></script>
|
|
118
|
+
|
|
119
|
+
<!-- jsDelivr -->
|
|
120
|
+
<script src="https://cdn.jsdelivr.net/npm/nexabase-report@0.1.0/dist/nexabase-report.umd.js"></script>
|
|
121
|
+
|
|
122
|
+
<!-- Uso -->
|
|
123
|
+
<script>
|
|
124
|
+
NexaReport.registerNexaReport();
|
|
125
|
+
const viewer = document.getElementById('miViewer');
|
|
126
|
+
viewer.definition = { /* ... */ };
|
|
127
|
+
viewer.data = [ /* ... */ ];
|
|
128
|
+
</script>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Estructura del paquete publicado
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
nexabase-report-0.1.0.tgz
|
|
137
|
+
├── dist/
|
|
138
|
+
│ ├── nexabase-report.es.js # ESM (import/export)
|
|
139
|
+
│ ├── nexabase-report.umd.js # UMD (script tag)
|
|
140
|
+
│ ├── index.d.ts # Types
|
|
141
|
+
│ ├── types/report.d.ts # Types de definiciones
|
|
142
|
+
│ ├── designer/ # Designer component
|
|
143
|
+
│ └── viewer/ # Viewer custom element
|
|
144
|
+
├── docs/
|
|
145
|
+
│ └── VIEWER_API.md # Documentación
|
|
146
|
+
├── examples/
|
|
147
|
+
│ ├── report-productos.json # Ejemplo simple
|
|
148
|
+
│ └── report-factura.json # Ejemplo factura
|
|
149
|
+
└── README.md
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Checklist antes de publicar
|
|
155
|
+
|
|
156
|
+
- [ ] `npm run build` pasa sin errores
|
|
157
|
+
- [ ] `package.json` tiene `"private": false` (o no tiene la clave)
|
|
158
|
+
- [ ] `version` incrementada correctamente
|
|
159
|
+
- [ ] `files` incluye `dist/`, `docs/`, `examples/`
|
|
160
|
+
- [ ] `exports` define todos los entry points
|
|
161
|
+
- [ ] `README.md` tiene instrucciones de uso
|
|
162
|
+
- [ ] `CHANGELOG.md` actualizado (opcional pero recomendado)
|
|
163
|
+
- [ ] `npm pack --dry-run` muestra solo los archivos correctos
|
|
164
|
+
- [ ] Licencia definida en `package.json`
|
|
165
|
+
- [ ] Dependencias `peerDependencies` si aplica (vue, @nexabase/sdk)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## peerDependencies (recomendado agregar)
|
|
170
|
+
|
|
171
|
+
Para evitar duplicar Vue y otras libs en el proyecto consumidor:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"peerDependencies": {
|
|
176
|
+
"vue": "^3.5.0",
|
|
177
|
+
"@nexabase/sdk": "^2.12.0"
|
|
178
|
+
},
|
|
179
|
+
"peerDependenciesMeta": {
|
|
180
|
+
"@nexabase/sdk": {
|
|
181
|
+
"optional": true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Esto le dice al consumidor: "necesitas Vue 3.5+, y opcionalmente @nexabase/sdk si quieres conectar al backend".
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Flujo de CI/CD recomendado (GitHub Actions)
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
# .github/workflows/publish.yml
|
|
195
|
+
name: Publish to npm
|
|
196
|
+
on:
|
|
197
|
+
release:
|
|
198
|
+
types: [published]
|
|
199
|
+
|
|
200
|
+
jobs:
|
|
201
|
+
publish:
|
|
202
|
+
runs-on: ubuntu-latest
|
|
203
|
+
steps:
|
|
204
|
+
- uses: actions/checkout@v4
|
|
205
|
+
- uses: actions/setup-node@v4
|
|
206
|
+
with:
|
|
207
|
+
node-version: 20
|
|
208
|
+
registry-url: https://registry.npmjs.org
|
|
209
|
+
- run: npm ci
|
|
210
|
+
- run: npm run build
|
|
211
|
+
- run: npm publish --access public
|
|
212
|
+
env:
|
|
213
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Ejemplo completo de uso en proyecto externo
|
|
219
|
+
|
|
220
|
+
### Vue 3 + Vite
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm install nexabase-report @nexabase/sdk
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<script setup>
|
|
228
|
+
import { ref, onMounted } from 'vue';
|
|
229
|
+
import { registerNexaReport } from 'nexabase-report';
|
|
230
|
+
import 'nexabase-report/style.css';
|
|
231
|
+
|
|
232
|
+
registerNexaReport();
|
|
233
|
+
|
|
234
|
+
const reportDef = ref(null);
|
|
235
|
+
const data = ref([]);
|
|
236
|
+
|
|
237
|
+
onMounted(async () => {
|
|
238
|
+
// Cargar desde archivo JSON local
|
|
239
|
+
const res = await fetch('/examples/report-productos.json');
|
|
240
|
+
reportDef.value = await res.json();
|
|
241
|
+
|
|
242
|
+
// Datos reales desde API
|
|
243
|
+
const dataRes = await fetch('/api/productos');
|
|
244
|
+
data.value = await dataRes.json();
|
|
245
|
+
});
|
|
246
|
+
</script>
|
|
247
|
+
|
|
248
|
+
<template>
|
|
249
|
+
<nexa-viewer :definition="reportDef" :data="data" minimal />
|
|
250
|
+
</template>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### React
|
|
254
|
+
|
|
255
|
+
```jsx
|
|
256
|
+
import { useEffect, useRef } from 'react';
|
|
257
|
+
import { registerNexaReport } from 'nexabase-report';
|
|
258
|
+
import 'nexabase-report/style.css';
|
|
259
|
+
|
|
260
|
+
registerNexaReport();
|
|
261
|
+
|
|
262
|
+
function Reporte() {
|
|
263
|
+
const viewerRef = useRef(null);
|
|
264
|
+
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
fetch('/api/reports/123')
|
|
267
|
+
.then(r => r.json())
|
|
268
|
+
.then(report => {
|
|
269
|
+
viewerRef.current.definition = report.definition;
|
|
270
|
+
viewerRef.current.data = report.data;
|
|
271
|
+
});
|
|
272
|
+
}, []);
|
|
273
|
+
|
|
274
|
+
return <nexa-viewer ref={viewerRef} />;
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### HTML puro
|
|
279
|
+
|
|
280
|
+
```html
|
|
281
|
+
<!DOCTYPE html>
|
|
282
|
+
<html>
|
|
283
|
+
<head>
|
|
284
|
+
<script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
|
|
285
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report/dist/style.css">
|
|
286
|
+
</head>
|
|
287
|
+
<body>
|
|
288
|
+
<nexa-viewer id="miReporte" minimal></nexa-viewer>
|
|
289
|
+
|
|
290
|
+
<script>
|
|
291
|
+
NexaReport.registerNexaReport();
|
|
292
|
+
|
|
293
|
+
const viewer = document.getElementById('miReporte');
|
|
294
|
+
|
|
295
|
+
fetch('/api/reports/123')
|
|
296
|
+
.then(r => r.json())
|
|
297
|
+
.then(report => {
|
|
298
|
+
viewer.definition = report.definition;
|
|
299
|
+
viewer.data = report.data;
|
|
300
|
+
});
|
|
301
|
+
</script>
|
|
302
|
+
</body>
|
|
303
|
+
</html>
|
|
304
|
+
```
|
package/docs/QA_PLAN.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Plan de Pruebas (Antes de UX) — NexaReport
|
|
2
|
+
|
|
3
|
+
Este plan busca estabilizar el motor (viewer/designer/export) antes de invertir en UX.
|
|
4
|
+
|
|
5
|
+
## 1) Objetivo
|
|
6
|
+
|
|
7
|
+
- Confirmar que el viewer renderiza igual en pantalla y en export (PDF/Excel/Word/CSV).
|
|
8
|
+
- Cubrir paginación, bandas, bindings/expresiones, filtros, formatos condicionales, drilldown y subreportes.
|
|
9
|
+
- Detectar regresiones rápido con una rutina repetible.
|
|
10
|
+
|
|
11
|
+
## 2) Precondiciones
|
|
12
|
+
|
|
13
|
+
- Compilación OK:
|
|
14
|
+
- `npm.cmd run build`
|
|
15
|
+
- Para desarrollo local:
|
|
16
|
+
- `npm.cmd run dev`
|
|
17
|
+
|
|
18
|
+
## 3) Matriz de Escenarios (Checklist)
|
|
19
|
+
|
|
20
|
+
### A. Render/Paginación
|
|
21
|
+
|
|
22
|
+
- A4 portrait, A4 landscape, Letter, Ticket.
|
|
23
|
+
- Márgenes (cm) distintos: 0, 1, 2.5.
|
|
24
|
+
- Bandas:
|
|
25
|
+
- ReportHeader, PageHeader, DataBand, DetailBand, PageFooter, ReportFooter.
|
|
26
|
+
- `pageBreakBefore` y `pageBreakAfter` en banda intermedia.
|
|
27
|
+
- Paginación:
|
|
28
|
+
- Dataset chico (1–5 filas).
|
|
29
|
+
- Dataset mediano (50–200 filas).
|
|
30
|
+
- Dataset grande (1000+ filas).
|
|
31
|
+
- Verificar:
|
|
32
|
+
- Total de páginas calculado coincide con páginas renderizadas.
|
|
33
|
+
- PageFooter aparece al final de cada página.
|
|
34
|
+
|
|
35
|
+
### B. Bindings/Expresiones
|
|
36
|
+
|
|
37
|
+
- Bindings:
|
|
38
|
+
- `{{campoSimple}}`, `{{obj.campo}}`
|
|
39
|
+
- Variables sistema:
|
|
40
|
+
- `{{Page}}`, `{{TotalPages}}`, `{{Today}}`, `{{Now}}`, `{{RowNumber}}`, `{{TotalRows}}`, `{{ReportName}}`
|
|
41
|
+
- Expresiones:
|
|
42
|
+
- `{[FormatNumber(total, 'es-ES')]}`, `{[IIF(total>100,'OK','Bajo')]}`
|
|
43
|
+
- Parámetros: `{[$fecha_desde]}` (si aplica)
|
|
44
|
+
- Verificar:
|
|
45
|
+
- En pantalla y en PDF muestran el mismo valor.
|
|
46
|
+
|
|
47
|
+
### C. Tablas (Table)
|
|
48
|
+
|
|
49
|
+
- Tabla con 5–8 columnas y ancho variable (forzar wrap).
|
|
50
|
+
- 100+ filas para multipágina.
|
|
51
|
+
- Verificar:
|
|
52
|
+
- Repite `thead` en cada página exportada.
|
|
53
|
+
- No corta filas de manera extraña.
|
|
54
|
+
- No se duplican tablas por fila (DataBand tabular por página).
|
|
55
|
+
|
|
56
|
+
### D. Crosstab / Chart
|
|
57
|
+
|
|
58
|
+
- Crosstab con:
|
|
59
|
+
- 2 rowFields, 1 columnField, 2 valueFields (sum/count).
|
|
60
|
+
- Chart con:
|
|
61
|
+
- bar/line/pie.
|
|
62
|
+
- Verificar:
|
|
63
|
+
- Render en pantalla.
|
|
64
|
+
- Export PDF: se ve completo (sin recortes/overflow).
|
|
65
|
+
|
|
66
|
+
### E. Imágenes / Barcodes / QR
|
|
67
|
+
|
|
68
|
+
- Imagen por URL y por binding.
|
|
69
|
+
- Barcode con binding (CODE128) y con displayValue on/off.
|
|
70
|
+
- QRCode (placeholder actual).
|
|
71
|
+
- Verificar:
|
|
72
|
+
- En PDF respeta tamaño y posición.
|
|
73
|
+
- Barcode se renderiza en export (si no, registrar issue).
|
|
74
|
+
|
|
75
|
+
### F. Formato condicional
|
|
76
|
+
|
|
77
|
+
- Operadores: `eq`, `gt`, `contains`, `starts_with`, `between`, `regex`.
|
|
78
|
+
- Verificar:
|
|
79
|
+
- Se aplica igual en pantalla y en PDF.
|
|
80
|
+
|
|
81
|
+
### G. Filtros de DataSource y de Banda
|
|
82
|
+
|
|
83
|
+
- `dataSources[].filters` (por alias) y `band.filter`.
|
|
84
|
+
- Verificar:
|
|
85
|
+
- La cantidad de filas cambia como corresponde.
|
|
86
|
+
- PDF/Excel exporta el mismo conjunto filtrado.
|
|
87
|
+
|
|
88
|
+
### H. DrillDown
|
|
89
|
+
|
|
90
|
+
- DrillDown con:
|
|
91
|
+
- Mapeo de parámetros desde fila actual.
|
|
92
|
+
- Modal (showAsModal) y new tab (openInNewTab).
|
|
93
|
+
- Verificar:
|
|
94
|
+
- Modal carga definición y datos del reporte destino.
|
|
95
|
+
|
|
96
|
+
### I. SubReport
|
|
97
|
+
|
|
98
|
+
- SubReport embebido (definition) y por `subReportId` si se usa.
|
|
99
|
+
- Verificar:
|
|
100
|
+
- No rompe export.
|
|
101
|
+
- Se renderiza con datos esperados (issue conocido si se requiere dataset distinto).
|
|
102
|
+
|
|
103
|
+
## 4) Set de Datos Recomendado
|
|
104
|
+
|
|
105
|
+
- `ventas` (main): id, cliente, fecha, total, categoria
|
|
106
|
+
- `detalle` (detail): venta_id, producto, cantidad, precio
|
|
107
|
+
- `clientes`: id, nombre, email
|
|
108
|
+
|
|
109
|
+
En pruebas rápidas, usar 3 tamaños: 10, 100, 1000 registros.
|
|
110
|
+
|
|
111
|
+
## 4.1) Reportes de ejemplo (JSON)
|
|
112
|
+
|
|
113
|
+
- `examples/report-ventas-logo-tabla.json` — logo (imagen) + tabla multipágina
|
|
114
|
+
- `examples/report-master-detail-ordenes.json` — master–detail (DataBand + DetailBand)
|
|
115
|
+
- `examples/report-agrupado-por-cliente.json` — agrupación (GroupHeader/GroupFooter)
|
|
116
|
+
- `examples/report-codigos-qr-barcode.json` — imágenes (URL/base64) + barcode + QR
|
|
117
|
+
- `examples/report-grafico-ventas-por-mes.json` — gráfico (bar)
|
|
118
|
+
- `examples/report-crosstab-categoria-mes.json` — crosstab (pivot)
|
|
119
|
+
|
|
120
|
+
## 5) Rutina de Smoke Test (10–15 min)
|
|
121
|
+
|
|
122
|
+
- Abrir un reporte ejemplo por cada formato de papel.
|
|
123
|
+
- Renderizar:
|
|
124
|
+
- 1 DataBand (texto) + PageHeader/Footer
|
|
125
|
+
- 1 Table (100 filas)
|
|
126
|
+
- 1 Crosstab
|
|
127
|
+
- Exportar:
|
|
128
|
+
- PDF (validar páginas y encabezados de tabla)
|
|
129
|
+
- Excel (validar hojas)
|
|
130
|
+
- CSV (validar delimitadores/UTF-8 BOM)
|
|
131
|
+
|
|
132
|
+
## 6) Criterios de Aceptación (Gate para UX)
|
|
133
|
+
|
|
134
|
+
- PDF fiel al visor (posiciones, fuentes, márgenes, sin páginas extra).
|
|
135
|
+
- Paginación consistente en visor y export.
|
|
136
|
+
- Table multipágina con encabezado repetido sin duplicación.
|
|
137
|
+
- DrillDown modal estable.
|
|
138
|
+
- Filtros y formato condicional coherentes.
|