# DDL-SPEC: Schema core_reports

## Identificacion

| Campo | Valor |
|-------|-------|
| **Schema** | core_reports |
| **Modulo** | MGN-009 |
| **Version** | 1.0 |
| **Estado** | En Diseno |
| **Autor** | Requirements-Analyst |
| **Fecha** | 2025-12-05 |

---

## Descripcion General

El schema `core_reports` gestiona definiciones de reportes, dashboards personalizables, widgets, reportes customizados via builder, y programacion de reportes automaticos.

### RF Cubiertos

| RF | Titulo | Tablas |
|----|--------|--------|
| RF-REPORT-001 | Reportes Predefinidos | report_definitions, report_executions |
| RF-REPORT-002 | Dashboards | dashboards, widgets, widget_data |
| RF-REPORT-003 | Report Builder | custom_reports, report_fields |
| RF-REPORT-004 | Reportes Programados | scheduled_reports |

---

## Diagrama ER

```mermaid
erDiagram
    report_definitions {
        uuid id PK
        varchar code UK
        varchar name
        varchar category
        jsonb default_params
        text query_template
        jsonb columns
        boolean is_active
    }

    report_executions {
        uuid id PK
        uuid report_definition_id FK
        uuid tenant_id FK
        uuid user_id FK
        jsonb params
        varchar status
        int row_count
        jsonb result_summary
        timestamptz started_at
        timestamptz completed_at
    }

    dashboards {
        uuid id PK
        uuid tenant_id FK
        uuid owner_id FK
        varchar name
        varchar visibility
        jsonb layout
        boolean is_default
    }

    widgets {
        uuid id PK
        uuid dashboard_id FK
        varchar type
        varchar title
        jsonb config
        jsonb position
    }

    custom_reports {
        uuid id PK
        uuid tenant_id FK
        uuid created_by FK
        varchar name
        varchar base_entity
        jsonb selected_fields
        jsonb filters
        jsonb grouping
        jsonb sorting
    }

    scheduled_reports {
        uuid id PK
        uuid tenant_id FK
        uuid report_id FK
        varchar report_type
        varchar schedule
        varchar format
        text[] recipients
        boolean is_active
    }

    report_definitions ||--o{ report_executions : "genera"
    dashboards ||--o{ widgets : "contiene"
    report_definitions ||--o{ scheduled_reports : "programa"
    custom_reports ||--o{ scheduled_reports : "programa"
```

---

## Tablas

### 1. report_definitions

Definiciones de reportes predefinidos del sistema.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `code` | VARCHAR(50) | NOT NULL | - | Codigo unico |
| `name` | VARCHAR(255) | NOT NULL | - | Nombre descriptivo |
| `description` | TEXT | NULL | - | Descripcion |
| `category` | VARCHAR(50) | NOT NULL | - | Categoria |
| `module` | VARCHAR(50) | NULL | - | Modulo origen |
| `default_params` | JSONB | NOT NULL | '{}' | Parametros por defecto |
| `query_template` | TEXT | NOT NULL | - | Template SQL/Query |
| `columns` | JSONB | NOT NULL | '[]' | Definicion de columnas |
| `charts` | JSONB | NULL | '[]' | Graficos disponibles |
| `export_formats` | VARCHAR(10)[] | NOT NULL | '{pdf,xlsx,csv}' | Formatos permitidos |
| `is_cacheable` | BOOLEAN | NOT NULL | true | Permite cache |
| `cache_ttl_seconds` | INTEGER | NULL | 300 | TTL cache |
| `is_async` | BOOLEAN | NOT NULL | false | Requiere ejecucion async |
| `max_rows` | INTEGER | NULL | 10000 | Limite de filas |
| `is_active` | BOOLEAN | NOT NULL | true | Activo |
| `created_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha creacion |
| `updated_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha actualizacion |

```sql
CREATE TABLE core_reports.report_definitions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    category VARCHAR(50) NOT NULL,
    module VARCHAR(50),
    default_params JSONB NOT NULL DEFAULT '{}',
    query_template TEXT NOT NULL,
    columns JSONB NOT NULL DEFAULT '[]',
    charts JSONB DEFAULT '[]',
    export_formats VARCHAR(10)[] NOT NULL DEFAULT '{pdf,xlsx,csv}',
    is_cacheable BOOLEAN NOT NULL DEFAULT true,
    cache_ttl_seconds INTEGER DEFAULT 300,
    is_async BOOLEAN NOT NULL DEFAULT false,
    max_rows INTEGER DEFAULT 10000,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT chk_category CHECK (category IN (
        'sales', 'inventory', 'finance', 'hr', 'operations', 'audit', 'custom'
    ))
);

CREATE INDEX idx_report_definitions_category ON core_reports.report_definitions(category);
CREATE INDEX idx_report_definitions_module ON core_reports.report_definitions(module);
```

---

### 2. report_executions

Historial de ejecuciones de reportes.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `report_definition_id` | UUID | NULL | - | FK a report_definitions |
| `custom_report_id` | UUID | NULL | - | FK a custom_reports |
| `tenant_id` | UUID | NOT NULL | - | FK a tenants |
| `user_id` | UUID | NOT NULL | - | Usuario que ejecuto |
| `params` | JSONB | NOT NULL | '{}' | Parametros usados |
| `status` | VARCHAR(20) | NOT NULL | 'pending' | Estado |
| `row_count` | INTEGER | NULL | - | Filas retornadas |
| `result_summary` | JSONB | NULL | - | Resumen/totales |
| `result_url` | VARCHAR(500) | NULL | - | URL archivo generado |
| `error` | TEXT | NULL | - | Mensaje de error |
| `execution_time_ms` | INTEGER | NULL | - | Tiempo ejecucion |
| `started_at` | TIMESTAMPTZ | NULL | - | Inicio |
| `completed_at` | TIMESTAMPTZ | NULL | - | Fin |
| `expires_at` | TIMESTAMPTZ | NULL | - | Expiracion del archivo |
| `created_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha creacion |

```sql
CREATE TABLE core_reports.report_executions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    report_definition_id UUID REFERENCES core_reports.report_definitions(id),
    custom_report_id UUID,
    tenant_id UUID NOT NULL,
    user_id UUID NOT NULL,
    params JSONB NOT NULL DEFAULT '{}',
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    row_count INTEGER,
    result_summary JSONB,
    result_url VARCHAR(500),
    error TEXT,
    execution_time_ms INTEGER,
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT chk_status CHECK (status IN (
        'pending', 'running', 'completed', 'failed', 'cancelled'
    )),
    CONSTRAINT chk_report_type CHECK (
        (report_definition_id IS NOT NULL AND custom_report_id IS NULL) OR
        (report_definition_id IS NULL AND custom_report_id IS NOT NULL)
    )
);

CREATE INDEX idx_report_executions_tenant ON core_reports.report_executions(tenant_id, created_at DESC);
CREATE INDEX idx_report_executions_user ON core_reports.report_executions(user_id, created_at DESC);
CREATE INDEX idx_report_executions_status ON core_reports.report_executions(status)
    WHERE status IN ('pending', 'running');
CREATE INDEX idx_report_executions_expires ON core_reports.report_executions(expires_at)
    WHERE expires_at IS NOT NULL;

-- RLS
ALTER TABLE core_reports.report_executions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON core_reports.report_executions
    FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
```

---

### 3. dashboards

Dashboards personalizables por usuario o tenant.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `tenant_id` | UUID | NOT NULL | - | FK a tenants |
| `owner_id` | UUID | NULL | - | Usuario propietario |
| `name` | VARCHAR(255) | NOT NULL | - | Nombre |
| `description` | TEXT | NULL | - | Descripcion |
| `visibility` | VARCHAR(20) | NOT NULL | 'private' | private/team/tenant |
| `layout` | JSONB | NOT NULL | '{}' | Configuracion layout |
| `theme` | VARCHAR(50) | NULL | - | Tema visual |
| `refresh_interval` | INTEGER | NULL | - | Auto-refresh segundos |
| `is_default` | BOOLEAN | NOT NULL | false | Dashboard por defecto |
| `is_archived` | BOOLEAN | NOT NULL | false | Archivado |
| `created_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha creacion |
| `updated_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha actualizacion |

```sql
CREATE TABLE core_reports.dashboards (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    owner_id UUID,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    visibility VARCHAR(20) NOT NULL DEFAULT 'private',
    layout JSONB NOT NULL DEFAULT '{}',
    theme VARCHAR(50),
    refresh_interval INTEGER,
    is_default BOOLEAN NOT NULL DEFAULT false,
    is_archived BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT chk_visibility CHECK (visibility IN ('private', 'team', 'tenant'))
);

CREATE INDEX idx_dashboards_tenant ON core_reports.dashboards(tenant_id);
CREATE INDEX idx_dashboards_owner ON core_reports.dashboards(owner_id);
CREATE INDEX idx_dashboards_default ON core_reports.dashboards(tenant_id, is_default)
    WHERE is_default = true;

-- RLS
ALTER TABLE core_reports.dashboards ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON core_reports.dashboards
    FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
```

---

### 4. widgets

Widgets dentro de dashboards.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `dashboard_id` | UUID | NOT NULL | - | FK a dashboards |
| `type` | VARCHAR(50) | NOT NULL | - | Tipo de widget |
| `title` | VARCHAR(255) | NOT NULL | - | Titulo |
| `subtitle` | VARCHAR(255) | NULL | - | Subtitulo |
| `config` | JSONB | NOT NULL | '{}' | Configuracion |
| `data_source` | JSONB | NOT NULL | - | Fuente de datos |
| `position` | JSONB | NOT NULL | - | Posicion grid |
| `size` | JSONB | NOT NULL | - | Tamano grid |
| `refresh_interval` | INTEGER | NULL | - | Override refresh |
| `is_visible` | BOOLEAN | NOT NULL | true | Visible |
| `sort_order` | INTEGER | NOT NULL | 0 | Orden |
| `created_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha creacion |
| `updated_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha actualizacion |

```sql
CREATE TABLE core_reports.widgets (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    dashboard_id UUID NOT NULL REFERENCES core_reports.dashboards(id) ON DELETE CASCADE,
    type VARCHAR(50) NOT NULL,
    title VARCHAR(255) NOT NULL,
    subtitle VARCHAR(255),
    config JSONB NOT NULL DEFAULT '{}',
    data_source JSONB NOT NULL,
    position JSONB NOT NULL,
    size JSONB NOT NULL,
    refresh_interval INTEGER,
    is_visible BOOLEAN NOT NULL DEFAULT true,
    sort_order INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT chk_widget_type CHECK (type IN (
        'metric', 'chart_line', 'chart_bar', 'chart_pie', 'chart_area',
        'table', 'list', 'progress', 'gauge', 'map', 'calendar', 'custom'
    ))
);

CREATE INDEX idx_widgets_dashboard ON core_reports.widgets(dashboard_id, sort_order);
```

---

### 5. custom_reports

Reportes creados via Report Builder.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `tenant_id` | UUID | NOT NULL | - | FK a tenants |
| `created_by` | UUID | NOT NULL | - | Usuario creador |
| `name` | VARCHAR(255) | NOT NULL | - | Nombre |
| `description` | TEXT | NULL | - | Descripcion |
| `base_entity` | VARCHAR(100) | NOT NULL | - | Entidad base |
| `selected_fields` | JSONB | NOT NULL | '[]' | Campos seleccionados |
| `joins` | JSONB | NULL | '[]' | Joins adicionales |
| `filters` | JSONB | NULL | '[]' | Filtros |
| `grouping` | JSONB | NULL | '[]' | Agrupaciones |
| `aggregations` | JSONB | NULL | '[]' | Funciones agregadas |
| `sorting` | JSONB | NULL | '[]' | Ordenamiento |
| `visibility` | VARCHAR(20) | NOT NULL | 'private' | Visibilidad |
| `is_favorite` | BOOLEAN | NOT NULL | false | Favorito |
| `last_run_at` | TIMESTAMPTZ | NULL | - | Ultima ejecucion |
| `run_count` | INTEGER | NOT NULL | 0 | Veces ejecutado |
| `created_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha creacion |
| `updated_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha actualizacion |

```sql
CREATE TABLE core_reports.custom_reports (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    created_by UUID NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    base_entity VARCHAR(100) NOT NULL,
    selected_fields JSONB NOT NULL DEFAULT '[]',
    joins JSONB DEFAULT '[]',
    filters JSONB DEFAULT '[]',
    grouping JSONB DEFAULT '[]',
    aggregations JSONB DEFAULT '[]',
    sorting JSONB DEFAULT '[]',
    visibility VARCHAR(20) NOT NULL DEFAULT 'private',
    is_favorite BOOLEAN NOT NULL DEFAULT false,
    last_run_at TIMESTAMPTZ,
    run_count INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT chk_visibility CHECK (visibility IN ('private', 'team', 'tenant'))
);

CREATE INDEX idx_custom_reports_tenant ON core_reports.custom_reports(tenant_id);
CREATE INDEX idx_custom_reports_user ON core_reports.custom_reports(created_by);
CREATE INDEX idx_custom_reports_entity ON core_reports.custom_reports(base_entity);

-- RLS
ALTER TABLE core_reports.custom_reports ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON core_reports.custom_reports
    FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
```

---

### 6. report_fields

Catalogo de campos disponibles para Report Builder.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `entity` | VARCHAR(100) | NOT NULL | - | Nombre entidad |
| `field_name` | VARCHAR(100) | NOT NULL | - | Nombre campo |
| `display_name` | VARCHAR(255) | NOT NULL | - | Nombre UI |
| `data_type` | VARCHAR(50) | NOT NULL | - | Tipo de dato |
| `is_aggregatable` | BOOLEAN | NOT NULL | false | Permite agregacion |
| `is_groupable` | BOOLEAN | NOT NULL | false | Permite agrupar |
| `is_filterable` | BOOLEAN | NOT NULL | true | Permite filtrar |
| `is_sortable` | BOOLEAN | NOT NULL | true | Permite ordenar |
| `format` | VARCHAR(50) | NULL | - | Formato display |
| `related_entity` | VARCHAR(100) | NULL | - | Entidad relacionada |
| `join_path` | TEXT | NULL | - | Path para join |

```sql
CREATE TABLE core_reports.report_fields (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    entity VARCHAR(100) NOT NULL,
    field_name VARCHAR(100) NOT NULL,
    display_name VARCHAR(255) NOT NULL,
    data_type VARCHAR(50) NOT NULL,
    is_aggregatable BOOLEAN NOT NULL DEFAULT false,
    is_groupable BOOLEAN NOT NULL DEFAULT false,
    is_filterable BOOLEAN NOT NULL DEFAULT true,
    is_sortable BOOLEAN NOT NULL DEFAULT true,
    format VARCHAR(50),
    related_entity VARCHAR(100),
    join_path TEXT,

    CONSTRAINT uk_report_fields UNIQUE (entity, field_name),
    CONSTRAINT chk_data_type CHECK (data_type IN (
        'string', 'number', 'decimal', 'integer', 'boolean',
        'date', 'datetime', 'time', 'json', 'array', 'uuid'
    ))
);

CREATE INDEX idx_report_fields_entity ON core_reports.report_fields(entity);
```

---

### 7. scheduled_reports

Programacion de reportes automaticos.

| Columna | Tipo | Nullable | Default | Descripcion |
|---------|------|----------|---------|-------------|
| `id` | UUID | NOT NULL | gen_random_uuid() | PK |
| `tenant_id` | UUID | NOT NULL | - | FK a tenants |
| `report_definition_id` | UUID | NULL | - | FK a report_definitions |
| `custom_report_id` | UUID | NULL | - | FK a custom_reports |
| `name` | VARCHAR(255) | NOT NULL | - | Nombre descriptivo |
| `params` | JSONB | NOT NULL | '{}' | Parametros del reporte |
| `schedule` | VARCHAR(100) | NOT NULL | - | Expresion cron |
| `timezone` | VARCHAR(50) | NOT NULL | 'UTC' | Timezone |
| `format` | VARCHAR(10) | NOT NULL | 'pdf' | Formato exportacion |
| `recipients` | TEXT[] | NOT NULL | - | Emails destinatarios |
| `is_active` | BOOLEAN | NOT NULL | true | Activo |
| `last_run_at` | TIMESTAMPTZ | NULL | - | Ultima ejecucion |
| `last_status` | VARCHAR(20) | NULL | - | Ultimo estado |
| `next_run_at` | TIMESTAMPTZ | NULL | - | Proxima ejecucion |
| `created_by` | UUID | NOT NULL | - | Usuario creador |
| `created_at` | TIMESTAMPTZ | NOT NULL | NOW() | Fecha creacion |

```sql
CREATE TABLE core_reports.scheduled_reports (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    report_definition_id UUID REFERENCES core_reports.report_definitions(id),
    custom_report_id UUID REFERENCES core_reports.custom_reports(id),
    name VARCHAR(255) NOT NULL,
    params JSONB NOT NULL DEFAULT '{}',
    schedule VARCHAR(100) NOT NULL,
    timezone VARCHAR(50) NOT NULL DEFAULT 'UTC',
    format VARCHAR(10) NOT NULL DEFAULT 'pdf',
    recipients TEXT[] NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT true,
    last_run_at TIMESTAMPTZ,
    last_status VARCHAR(20),
    next_run_at TIMESTAMPTZ,
    created_by UUID NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT chk_format CHECK (format IN ('pdf', 'xlsx', 'csv', 'json')),
    CONSTRAINT chk_report_type CHECK (
        (report_definition_id IS NOT NULL AND custom_report_id IS NULL) OR
        (report_definition_id IS NULL AND custom_report_id IS NOT NULL)
    )
);

CREATE INDEX idx_scheduled_reports_tenant ON core_reports.scheduled_reports(tenant_id);
CREATE INDEX idx_scheduled_reports_next_run ON core_reports.scheduled_reports(next_run_at)
    WHERE is_active = true;

-- RLS
ALTER TABLE core_reports.scheduled_reports ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON core_reports.scheduled_reports
    FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
```

---

## Funciones de Utilidad

### Ejecutar Reporte Predefinido

```sql
CREATE OR REPLACE FUNCTION core_reports.execute_report(
    p_report_code VARCHAR,
    p_tenant_id UUID,
    p_user_id UUID,
    p_params JSONB DEFAULT '{}'
) RETURNS TABLE (
    execution_id UUID,
    data JSONB
) AS $$
DECLARE
    v_report RECORD;
    v_execution_id UUID;
    v_query TEXT;
    v_start_time TIMESTAMPTZ;
    v_result JSONB;
    v_row_count INTEGER;
BEGIN
    -- Obtener definicion
    SELECT * INTO v_report
    FROM core_reports.report_definitions
    WHERE code = p_report_code AND is_active = true;

    IF v_report IS NULL THEN
        RAISE EXCEPTION 'Report % not found or inactive', p_report_code;
    END IF;

    -- Crear registro de ejecucion
    INSERT INTO core_reports.report_executions (
        report_definition_id, tenant_id, user_id, params, status, started_at
    ) VALUES (
        v_report.id, p_tenant_id, p_user_id, p_params, 'running', NOW()
    ) RETURNING id INTO v_execution_id;

    v_start_time := clock_timestamp();

    -- Construir y ejecutar query
    v_query := core_reports.build_query(v_report.query_template, p_params, p_tenant_id);

    EXECUTE format('SELECT jsonb_agg(row_to_json(t)) FROM (%s LIMIT %s) t',
        v_query, COALESCE(v_report.max_rows, 10000))
    INTO v_result;

    v_row_count := COALESCE(jsonb_array_length(v_result), 0);

    -- Actualizar ejecucion
    UPDATE core_reports.report_executions
    SET status = 'completed',
        row_count = v_row_count,
        execution_time_ms = EXTRACT(MILLISECONDS FROM clock_timestamp() - v_start_time)::INTEGER,
        completed_at = NOW()
    WHERE id = v_execution_id;

    RETURN QUERY SELECT v_execution_id, v_result;

EXCEPTION WHEN OTHERS THEN
    UPDATE core_reports.report_executions
    SET status = 'failed',
        error = SQLERRM,
        completed_at = NOW()
    WHERE id = v_execution_id;

    RAISE;
END;
$$ LANGUAGE plpgsql;
```

### Construir Query Dinamico

```sql
CREATE OR REPLACE FUNCTION core_reports.build_query(
    p_template TEXT,
    p_params JSONB,
    p_tenant_id UUID
) RETURNS TEXT AS $$
DECLARE
    v_query TEXT;
    v_key TEXT;
    v_value TEXT;
BEGIN
    v_query := p_template;

    -- Reemplazar tenant_id
    v_query := replace(v_query, '{{tenant_id}}', quote_literal(p_tenant_id));

    -- Reemplazar parametros
    FOR v_key, v_value IN SELECT * FROM jsonb_each_text(p_params)
    LOOP
        v_query := replace(v_query, '{{' || v_key || '}}', quote_literal(v_value));
    END LOOP;

    RETURN v_query;
END;
$$ LANGUAGE plpgsql;
```

---

## Seed Data

### Report Definitions

```sql
INSERT INTO core_reports.report_definitions (code, name, category, module, query_template, columns, default_params) VALUES
('USERS_LIST', 'Lista de Usuarios', 'hr', 'MGN-002',
 'SELECT u.id, u.email, u.full_name, u.status, u.created_at
  FROM core_users.users u WHERE u.tenant_id = {{tenant_id}}',
 '[{"field": "email", "label": "Email", "type": "string"},
   {"field": "full_name", "label": "Nombre", "type": "string"},
   {"field": "status", "label": "Estado", "type": "badge"},
   {"field": "created_at", "label": "Creado", "type": "date"}]',
 '{}'),

('LOGIN_ACTIVITY', 'Actividad de Login', 'audit', 'MGN-007',
 'SELECT u.email, la.ip_address, la.success, la.attempted_at
  FROM core_auth.login_attempts la
  JOIN core_users.users u ON u.id = la.user_id
  WHERE la.tenant_id = {{tenant_id}}
  AND la.attempted_at >= {{date_from}}::timestamptz
  ORDER BY la.attempted_at DESC',
 '[{"field": "email", "label": "Usuario", "type": "string"},
   {"field": "ip_address", "label": "IP", "type": "string"},
   {"field": "success", "label": "Exitoso", "type": "boolean"},
   {"field": "attempted_at", "label": "Fecha", "type": "datetime"}]',
 '{"date_from": "NOW() - INTERVAL ''7 days''"}'),

('TENANT_SUMMARY', 'Resumen del Tenant', 'operations', 'MGN-004',
 'SELECT
    (SELECT COUNT(*) FROM core_users.users WHERE tenant_id = {{tenant_id}}) as total_users,
    (SELECT COUNT(*) FROM core_users.users WHERE tenant_id = {{tenant_id}} AND status = ''active'') as active_users,
    (SELECT COUNT(*) FROM core_auth.sessions WHERE tenant_id = {{tenant_id}} AND is_valid = true) as active_sessions',
 '[{"field": "total_users", "label": "Total Usuarios", "type": "number"},
   {"field": "active_users", "label": "Usuarios Activos", "type": "number"},
   {"field": "active_sessions", "label": "Sesiones Activas", "type": "number"}]',
 '{}');
```

### Report Fields (para Builder)

```sql
INSERT INTO core_reports.report_fields (entity, field_name, display_name, data_type, is_aggregatable, is_groupable) VALUES
-- Users
('users', 'id', 'ID', 'uuid', false, false),
('users', 'email', 'Email', 'string', false, true),
('users', 'full_name', 'Nombre Completo', 'string', false, true),
('users', 'status', 'Estado', 'string', false, true),
('users', 'created_at', 'Fecha Creacion', 'datetime', false, true),

-- Contacts
('contacts', 'id', 'ID', 'uuid', false, false),
('contacts', 'name', 'Nombre', 'string', false, true),
('contacts', 'contact_type', 'Tipo', 'string', false, true),
('contacts', 'email', 'Email', 'string', false, false),
('contacts', 'phone', 'Telefono', 'string', false, false),
('contacts', 'country_code', 'Pais', 'string', false, true),
('contacts', 'is_active', 'Activo', 'boolean', false, true),
('contacts', 'created_at', 'Creado', 'datetime', false, true);
```

---

## Mantenimiento

### Limpiar Ejecuciones Antiguas

```sql
CREATE OR REPLACE FUNCTION core_reports.cleanup_old_executions(
    p_days INTEGER DEFAULT 30
) RETURNS INTEGER AS $$
DECLARE
    v_deleted INTEGER;
BEGIN
    -- Eliminar archivos expirados (implementar logica de storage)
    -- DELETE FILES WHERE ...

    -- Eliminar registros
    DELETE FROM core_reports.report_executions
    WHERE created_at < NOW() - (p_days || ' days')::INTERVAL
    AND status IN ('completed', 'failed', 'cancelled');

    GET DIAGNOSTICS v_deleted = ROW_COUNT;
    RETURN v_deleted;
END;
$$ LANGUAGE plpgsql;
```

---

## Historial

| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | Requirements-Analyst | Creacion inicial |
