# NexaReport Viewer API

## Registro

```ts
import { registerNexaReport } from 'nexabase-report';
registerNexaReport(); // Registra el custom element <nexa-viewer>
```

## Uso como Custom Element (HTML/Vue/React/Angular)

### Vue 3

```vue
<script setup>
import { ref } from 'vue';
import { registerNexaReport } from 'nexabase-report';
registerNexaReport();

const reportDef = ref(null); // Se carga desde API o archivo JSON
const data = ref([]);
</script>

<template>
  <nexa-viewer
    :definition="reportDef"
    :data="data"
    :parameters="{ fechaDesde: '2024-01-01', categoria: 'A' }"
    skip-params-dialog
    minimal
  />
</template>
```

### HTML puro (UMD)

```html
<script src="nexabase-report.umd.js"></script>
<script>
  window.NexaReport.registerNexaReport();
</script>

<nexa-viewer id="miReporte"></nexa-viewer>

<script>
  // Cargar definición (desde fetch, inline, etc.)
  const viewer = document.getElementById('miReporte');

  // Opción 1: Definición inline
  viewer.definition = {
    metadata: { name: 'Mi Reporte', version: '1.0', createdAt: '2024-01-01' },
    layout: {
      page: { format: 'A4', orientation: 'portrait' },
      bands: [{ id: 'b1', type: 'ReportHeader', height: 60, elements: [
        { id: 'e1', type: 'Text', x: 20, y: 10, width: 300, height: 30,
          content: '{{ReportName}}', style: { fontSize: '20px', fontWeight: 'bold' } }
      ]}]
    },
    dataSources: [{ id: 'ds1', collection: 'ventas', alias: 'main', enabled: true }]
  };

  // Opción 2: Datos inline
  viewer.data = [
    { id: 1, nombre: 'Producto A', precio: 100, cantidad: 5 },
    { id: 2, nombre: 'Producto B', precio: 200, cantidad: 3 }
  ];

  // Opción 3: Múltiples dataSources
  viewer.data = {
    clientes: [{ id: 1, nombre: 'Juan' }, { id: 2, nombre: 'María' }],
    pedidos: [{ id: 1, cliente_id: 1, total: 500 }, { id: 2, cliente_id: 2, total: 300 }]
  };
</script>
```

### React

```jsx
import { useEffect, useRef } from 'react';
import { registerNexaReport } from 'nexabase-report';

registerNexaReport();

function MiReporte({ definition, data, parameters }) {
  const viewerRef = useRef(null);

  useEffect(() => {
    if (viewerRef.current) {
      viewerRef.current.definition = definition;
      viewerRef.current.data = data;
      viewerRef.current.parameters = parameters;
    }
  }, [definition, data, parameters]);

  return <nexa-viewer ref={viewerRef} minimal />;
}
```

## Props del Viewer

| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| `definition` | `string \| NexaReportDefinition` | — | Definición del reporte (objeto o JSON string). |
| `data` | `string \| any[] \| Record<string, any[]>` | — | Datos (array, objeto por alias o JSON string). |
| `parameters` | `Record<string, any>` | `{}` | Valores iniciales para los parámetros del reporte. |
| `minimal` | `boolean` | `false` | Modo compacto: oculta toolbar y miniaturas laterales. |
| `showToolbar` | `boolean` | `true` | Muestra/oculta la barra de herramientas de exportación. |
| `showThumbs` | `boolean` | `true` | Muestra/oculta el panel lateral de miniaturas. |
| `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de entrada de parámetros al cargar. |
| `currentPage` | `number` | `1` | Página inicial que se debe mostrar. |
| `apiBaseUrl` | `string` | — | URL base de NexaBase para reportes con datos dinámicos. |
| `apiKey` | `string` | — | API Key para autenticación de datos desde NexaBase. |
| `licenseKey` | `string` | — | Clave de licencia comercial. Sin licencia válida se muestra un aviso "Sin licencia" en el visor. |

## Métodos (vía ref DOM)

```ts
const viewer = document.querySelector('nexa-viewer');

// --- Exportación ---
await viewer.exportPdf();              // PDF estándar
await viewer.exportPdfWithBookmarks(); // PDF con panel de marcadores
await viewer.exportExcel();            // Excel (.xlsx)
await viewer.exportWord();             // Word (.docx)
await viewer.exportCsv();              // CSV (.csv)

// --- Navegación ---
viewer.goToPage(3);                    // Ir a página específica
console.log(viewer.pageNumber);         // Página actual (get)
console.log(viewer.totalPages);         // Total páginas (get)

// --- Datos ---
viewer.updateData(nuevosDatos);        // Actualiza datos sin recargar definición

// --- Parámetros ---
console.log(viewer.paramValues);       // { fechaDesde: '2024-01-01', ... }
viewer.showParamsDialog = true;        // Mostrar/ocultar diálogo manualmente
viewer.applyParams();                  // Aplicar parámetros actuales
viewer.validateParams();               // Validar parámetros (devuelve boolean)
```

## Formato de `definition` (NexaReportDefinition)

```ts
interface NexaReportDefinition {
  metadata: {
    version: string;
    name: string;
    description?: string;
    author?: string;
    createdAt: string;
  };
  layout: {
    page: {
      format: 'A4' | 'Letter' | 'Ticket';
      orientation: 'portrait' | 'landscape';
      margins?: { top: number; right: number; bottom: number; left: number };
    };
    bands: NexaReportBand[];
  };
  dataSources: NexaDataSource[];
  parameters?: ReportParameter[];
  expressions?: ExpressionField[];
}
```

## Formato de `data`

### Single DataSource (array simple)
```json
[
  { "id": 1, "nombre": "Juan", "ventas": 1500 },
  { "id": 2, "nombre": "María", "ventas": 2300 }
]
```

### Multiple DataSources (objeto con alias)
```json
{
  "main": [
    { "id": 1, "nombre": "Juan", "email": "juan@mail.com" }
  ],
  "pedidos": [
    { "id": 101, "cliente_id": 1, "total": 500, "fecha": "2024-01-15" },
    { "id": 102, "cliente_id": 1, "total": 300, "fecha": "2024-01-20" }
  ],
  "productos": [
    { "id": 1, "nombre": "Laptop", "precio": 1200 },
    { "id": 2, "nombre": "Mouse", "precio": 25 }
  ]
}
```

### Como JSON string (compatible con cualquier framework)
```js
viewer.data = JSON.stringify([
  { id: 1, nombre: "Juan" },
  { id: 2, nombre: "María" }
]);
```

## Variables del Sistema disponibles

### Página
| Binding | Descripción |
|---------|-------------|
| `{{Page}}` | Número de página actual |
| `{{TotalPages}}` | Total de páginas |
| `{{FirstPage}}` | Es la primera página (true/false) |
| `{{LastPage}}` | Es la última página (true/false) |

### Fecha/Hora
| Binding | Descripción |
|---------|-------------|
| `{{Today}}` | Fecha actual (YYYY-MM-DD) |
| `{{Now}}` | Fecha y hora actual |
| `{{Year}}` | Año actual |
| `{{Month}}` | Mes actual (1-12) |
| `{{Day}}` | Día del mes (1-31) |

### Reporte
| Binding | Descripción |
|---------|-------------|
| `{{ReportName}}` | Nombre del reporte |
| `{{ReportAuthor}}` | Autor del reporte |
| `{{ReportDescription}}` | Descripción del reporte |
| `{{ReportCreated}}` | Fecha de creación |

### Fila
| Binding | Descripción |
|---------|-------------|
| `{{RowNumber}}` | Número de fila actual (1-based) |
| `{{TotalRows}}` | Total de filas |
| `{{FirstRow}}` | Es la primera fila |
| `{{LastRow}}` | Es la última fila |
| `{{EvenRow}}` | Fila par |
| `{{OddRow}}` | Fila impar |

### Funciones (usar en expresiones `{[...]}`)

| Categoría | Funciones |
|-----------|-----------|
| **Agregación** | `sum(campo)`, `count(campo)`, `avg(campo)`, `min(campo)`, `max(campo)`, `first(campo)`, `last(campo)` |
| **Texto** | `concat(a, b, ...)`, `upper(s)`, `lower(s)`, `len(s)`, `trim(s)`, `substring(s, ini, len)`, `left(s, n)`, `right(s, n)`, `replace(s, bus, rep)` |
| **Matemáticas** | `round(n)`, `abs(n)`, `floor(n)`, `ceil(n)` |
| **Formato** | `FormatNumber(v, fmt)`, `FormatDate(v, fmt)`, `FormatCurrency(v, iso)` |
| **Lógica** | `if(cond, v, f)`, `IIF(cond, v, f)`, `ISNULL(v, rep)` |
| **Fecha** | `now()`, `today()` |
| **Reporte** | `page()`, `totalPages()`, `total()` (total registros) |

## Elementos soportados

| Tipo | Propiedades clave |
|------|-------------------|
| `Text` | `content`, `binding`, `style` |
| `Image` | `options.imageUrl`, `options.imageSource`, `binding` |
| `Barcode` | `binding`, `options.barcodeFormat`, `options.displayValue` |
| `QRCode` | `content`, `binding` |
| `Table` | `tableColumns[]` (id, title, width, binding) |
| `Chart` | `chartConfig` (chartType, categoryField, valueFields[], showLegend, etc.) |
| `Crosstab` | `crosstabConfig` (rowFields[], columnField, valueFields[]) |
| `SubReport` | `subReportDefinition` o `subReportId`, `bindingField`, `parameterBindings` |
| `DrillDown` | `drillDownConfig` (targetReportId, parameterMappings, showAsModal) |
| `Rectangle` | `shapeType`, `options.shapeStyle`, `options.strokeWidth`, `options.lineColor` |
| `Ellipse` | `shapeType`, `options.shapeStyle`, `options.strokeWidth`, `options.lineColor` |
| `Line` | `shapeType`, `options.shapeStyle`, `options.strokeWidth`, `options.lineColor` |
| `Arrow` | `shapeType`, `options.shapeStyle`, `options.strokeWidth`, `options.lineColor` |

### Bandas soportadas
`ReportHeader`, `PageHeader`, `GroupHeader`, `DataBand`, `DetailBand` (Master-Detail), `GroupFooter`, `PageFooter`, `ReportFooter`.

## Formato condicional

```json
{
  "conditionalFormats": [
    {
      "id": "cf_1",
      "name": "Precio alto",
      "condition": { "field": "precio", "operator": "gt", "value": "100" },
      "style": { "backgroundColor": "#fff3cd", "color": "#856404", "fontWeight": "bold" },
      "enabled": true
    }
  ]
}
```

Operadores: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `is_null`, `is_not_null`, `between`

## Import/Export JSON

```ts
// Exportar la definición actual a archivo JSON
function exportToJSON() { /* descarga .json */ }

// Importar desde archivo JSON
function importFromJSON(file: File) { /* reemplaza el reporte */ }
```
