/**
 * Contexto del schema de Novasis para el LLM.
 *
 * ─── CÓMO AGREGAR NUEVO CONTEXTO ───────────────────────────────────────────
 *
 * 1. Agregá una nueva entrada al array SCHEMA_MODULES con:
 *    - area:        nombre del área (para logging / documentación)
 *    - tablas:      descripción de cada tabla con sus columnas
 *    - ejemplos:    (opcional) queries de ejemplo para ese área
 *
 * 2. El contexto final se construye automáticamente en buildSchemaContext().
 *    No hace falta tocar nada más.
 *
 * ────────────────────────────────────────────────────────────────────────────
 */

interface SchemaModule {
  area: string;
  tablas: string;
  ejemplos?: string;
}

// ─── INSTRUCCIONES BASE ──────────────────────────────────────────────────────
const BASE_INSTRUCTIONS = `
Sos un analista SQL experto del ERP Novasis Paraguay.
Tu trabajo es convertir preguntas en español a consultas SQL válidas para PostgreSQL.

REGLAS CRÍTICAS:
1. SIEMPRE filtrá por empresa_id = $1::uuid en la tabla principal de la consulta (el cast ::uuid es obligatorio)
2. Solo generás SELECT — NUNCA DELETE, UPDATE, INSERT, ALTER, DROP, TRUNCATE
3. Usá aliases claros en español para los nombres de columna
4. Los montos están en Guaraníes (PYG)
5. Las fechas usan zona horaria local (Paraguay = UTC-4)
6. Limitá resultados a máximo 500 filas con LIMIT si no hay límite explícito
7. NUNCA incluyas columnas de tipo UUID en el SELECT final (id, empresa_id, cliente_id, producto_id, etc.) — el usuario no entiende esos valores. Si necesitás un identificador de documento, usá el número o código visible (ej: numero_cheque, cod_producto, numero_recibo). Si no hay número visible, usá el nombre + fecha como identificación.
8. Las fechas mostralas en formato legible: TO_CHAR(fecha, 'DD/MM/YYYY') o TO_CHAR(fecha, 'DD/MM/YYYY HH24:MI')
9. Si una tabla que querés usar NO existe en el schema de abajo, devolvé tipo_grafico "none" y en sql una cadena vacía "", explicando en "explicacion" que esa funcionalidad aún no está disponible.

DEVOLVÉ SIEMPRE un JSON válido con esta estructura exacta:
{
  "sql": "SELECT ...",
  "titulo": "título descriptivo del reporte en español",
  "tipo_grafico": "bar|line|pie|table|number|none",
  "explicacion": "1-2 oraciones explicando qué muestra este reporte"
}

Elegí tipo_grafico así:
- "number": un solo valor agregado (total, promedio, conteo)
- "bar": comparación entre categorías (por cobrador, por producto, por mes)
- "line": evolución en el tiempo (ventas diarias, cobros por semana)
- "pie": distribución proporcional (% por tipo, participación)
- "table": listado detallado con múltiples columnas
- "none": consulta que no necesita visualización
`.trim();

// ─── JOINS COMUNES ───────────────────────────────────────────────────────────
const COMMON_JOINS = `
JOINS FRECUENTES (usar como referencia):

-- Nombre del cliente:
JOIN clientes c ON c.id = <tabla>.cliente_id
JOIN personas p ON p.id = c.persona_id  →  p.razon_social AS cliente

-- Nombre del cobrador / vendedor:
JOIN vendedores_cobradores vc ON vc.id = <tabla>.cobrador_id  →  vc.nombre AS cobrador

-- Nombre del proveedor:
JOIN proveedores prov ON prov.id = <tabla>.proveedor_id
JOIN personas p ON p.id = prov.persona_id  →  p.razon_social AS proveedor

-- Nombre del producto:
JOIN productos pr ON pr.id = <tabla>.producto_id  →  pr.descripcion AS producto

-- Período mensual actual:
DATE_TRUNC('month', <campo_fecha>) = DATE_TRUNC('month', CURRENT_DATE)

-- Últimos N días:
<campo_fecha> >= CURRENT_DATE - INTERVAL 'N days'
`.trim();

// ─── MÓDULOS DE SCHEMA ───────────────────────────────────────────────────────
// Para agregar un área nueva: copiá un bloque y completalo.
// ────────────────────────────────────────────────────────────────────────────
const SCHEMA_MODULES: SchemaModule[] = [
  // ── 1. VENTAS ─────────────────────────────────────────────────────────────
  {
    area: 'Ventas / Facturación',
    tablas: `
── factura_cab ─────────────────────────────────────────────
Cabecera de facturas emitidas.
⚠ NO existe numero_factura ni nro_factura — para identificar una factura usá: cliente + fecha de emisión.
  id               UUID  PK  ← NUNCA mostrar en resultados
  empresa_id       UUID  ← FILTRAR SIEMPRE con $1::uuid
  cliente_id       UUID  FK → clientes
  vendedor_id      UUID  FK → vendedores_cobradores
  cobrador_id      UUID  FK → vendedores_cobradores
  dfeemide         TIMESTAMP  ← fecha de emisión
  dticam           DECIMAL    ← cotización (tipo de cambio) si la factura es en moneda extranjera; NULL = PYG
  moneda_id        UUID  FK → moneda
  total_factura    DECIMAL    ← SIEMPRE NULL en este sistema, NO USAR
  saldo_pendiente  DECIMAL    ← SIEMPRE NULL en este sistema, NO USAR
  estado           VARCHAR    ← 'Pendiente','Pagada','Anulada','Enviado','Aprobado','Rechazado'
  icondcred        INT        ← 1=plazo fijo, 2=cuotas
  dcuotas          INT        ← cantidad de cuotas

⚠ IMPORTANTE: total_factura y saldo_pendiente en factura_cab son siempre NULL.
  Para montos de ventas SIEMPRE usar factura_subtotales.

── factura_subtotales ──────────────────────────────────────
Totales por factura — ÚNICA FUENTE de montos de ventas.
  factura_cab_id   UUID  FK → factura_cab
  dtotgralope      DECIMAL  ← TOTAL VENTA en moneda original de la factura ← USAR ESTO
  dtotiva          DECIMAL  ← total IVA
  dliqtotiva10     DECIMAL  ← IVA 10%
  dliqtotiva5      DECIMAL  ← IVA 5%
  dbasegrav10      DECIMAL  ← base gravada 10%
  dbasegrav5       DECIMAL  ← base gravada 5%
  dsubexe          DECIMAL  ← subtotal exento
  dtotalgs         DECIMAL  ← igual a dtotgralope, usar dtotgralope

⚠ Para convertir a PYG cuando hay moneda extranjera:
  dtotgralope * COALESCE(fc.dticam, 1) — si dticam es NULL ya está en PYG.

── factura_det ─────────────────────────────────────────────
Ítems de cada factura.
  factura_cab_id   UUID  FK → factura_cab
  producto_id      UUID  FK → productos
  ddesproser       VARCHAR  ← descripción del producto/servicio
  dcantproser      DECIMAL  ← cantidad vendida
  duniproser       DECIMAL  ← precio unitario en moneda original
  dtotopeitem         DECIMAL  ← total ítem en moneda original ← USAR ESTO (dtotopegs es NULL)
  dtasiva             DECIMAL  ← tasa IVA (5 o 10)
  costo_unitario_venta DECIMAL ← costo unitario al momento de la venta (para rentabilidad)
  costo_total_venta    DECIMAL ← costo total del ítem (costo_unitario_venta × cantidad) — para margen bruto

── factura_cuotas ──────────────────────────────────────────
Cuotas de facturas a crédito.
  factura_cab_id   UUID  FK → factura_cab
  nro_cuota        INT
  saldo_pendiente  DECIMAL  ← saldo pendiente de la cuota
  dvenccuo         DATE     ← fecha de vencimiento
  estado           VARCHAR  ← 'pendiente','Pendiente','pagado' (inconsistente, usar LOWER(estado))

── nota_credito_cab ────────────────────────────────────────
Notas de crédito emitidas.
  empresa_id       UUID
  cliente_id       UUID  FK → clientes
  factura_cab_id   UUID  FK → factura_cab
  dfeemide         TIMESTAMP  ← fecha de emisión
  estado           VARCHAR  ← 'Vigente','Anulada'

── nota_credito_subtotal ───────────────────────────────────
Totales de notas de crédito.
  nota_credito_cab_id  UUID  FK → nota_credito_cab
  dtotgralope          DECIMAL  ← total de la NC en moneda original
    `.trim(),
    ejemplos: `
-- Total vendido este mes en PYG (forma CORRECTA):
SELECT SUM(fs.dtotgralope * COALESCE(fc.dticam, 1)) AS total_ventas_gs,
       COUNT(fc.id) AS cantidad_facturas
FROM factura_subtotales fs
JOIN factura_cab fc ON fc.id = fs.factura_cab_id
WHERE fc.empresa_id = $1::uuid
  AND fc.estado != 'Anulada'
  AND DATE_TRUNC('month', fc.dfeemide) = DATE_TRUNC('month', CURRENT_DATE)

-- Top 10 productos más vendidos este mes:
SELECT fd.ddesproser AS producto,
       SUM(fd.dcantproser) AS cantidad,
       SUM(fd.dtotopeitem * COALESCE(fc.dticam, 1)) AS total_gs
FROM factura_det fd
JOIN factura_cab fc ON fc.id = fd.factura_cab_id
WHERE fc.empresa_id = $1::uuid
  AND fc.estado != 'Anulada'
  AND DATE_TRUNC('month', fc.dfeemide) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY fd.ddesproser ORDER BY total_gs DESC LIMIT 10

-- Ventas por vendedor del mes:
SELECT vc.nombre AS vendedor,
       COUNT(fc.id) AS facturas,
       SUM(fs.dtotgralope * COALESCE(fc.dticam, 1)) AS total_gs
FROM factura_cab fc
JOIN factura_subtotales fs ON fs.factura_cab_id = fc.id
JOIN vendedores_cobradores vc ON vc.id = fc.vendedor_id
WHERE fc.empresa_id = $1::uuid AND fc.estado != 'Anulada'
  AND DATE_TRUNC('month', fc.dfeemide) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY vc.nombre ORDER BY total_gs DESC

-- Rentabilidad por producto (margen bruto) del mes:
SELECT fd.ddesproser AS producto,
       SUM(fd.dtotopeitem * COALESCE(fc.dticam, 1)) AS venta_gs,
       SUM(fd.costo_total_venta * COALESCE(fc.dticam, 1)) AS costo_gs,
       SUM((fd.dtotopeitem - fd.costo_total_venta) * COALESCE(fc.dticam, 1)) AS margen_gs,
       ROUND(SUM((fd.dtotopeitem - fd.costo_total_venta) * COALESCE(fc.dticam, 1)) /
             NULLIF(SUM(fd.dtotopeitem * COALESCE(fc.dticam, 1)), 0) * 100, 1) AS margen_pct
FROM factura_det fd
JOIN factura_cab fc ON fc.id = fd.factura_cab_id
WHERE fc.empresa_id = $1::uuid AND fc.estado != 'Anulada'
  AND fd.costo_total_venta IS NOT NULL AND fd.costo_total_venta > 0
  AND DATE_TRUNC('month', fc.dfeemide) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY fd.ddesproser ORDER BY margen_gs DESC LIMIT 10
    `.trim(),
  },

  // ── 2. COBROS ─────────────────────────────────────────────────────────────
  {
    area: 'Cobros / Cobranzas',
    tablas: `
── recibos_cobro ───────────────────────────────────────────
Recibos de cobro (pagos recibidos de clientes).
  empresa_id       UUID  ← FILTRAR SIEMPRE
  cliente_id       UUID  FK → clientes
  cobrador_id      UUID  FK → vendedores_cobradores
  fecha_emision    TIMESTAMPTZ  ← fecha del cobro
  monto_total      DECIMAL      ← total cobrado
  mora_total       DECIMAL
  estado           VARCHAR  ← 'emitido','anulado'
  numero_recibo    VARCHAR

── recibo_cobro_detalle ────────────────────────────────────
Detalle de cada recibo (una fila por cuota cobrada).
  recibo_cobro_id     UUID  FK → recibos_cobro
  factura_cuota_id    UUID  FK → factura_cuotas
  monto_pagado        DECIMAL
  descuento_aplicado  DECIMAL
  mora_monto          DECIMAL

── cuentas_cobrar ──────────────────────────────────────────
Cuentas por cobrar (una por factura a crédito).
  empresa_id          UUID
  cliente_id          UUID  FK → clientes
  factura_venta_id    UUID  FK → factura_cab
  monto_total         DECIMAL
  saldo_pendiente     DECIMAL
  fecha_vencimiento   DATE
  estado              VARCHAR  ← 'pendiente','pagada','vencida'

── promesas_pago ───────────────────────────────────────────
Compromisos de pago acordados con clientes.
  empresa_id          UUID
  cliente_id          UUID  FK → clientes
  cobrador_id         UUID  FK → vendedores_cobradores
  fecha_promesa       DATE     ← fecha comprometida
  monto_prometido     DECIMAL
  estado              VARCHAR  ← 'pendiente','cumplida','incumplida'
  observacion         VARCHAR
    `.trim(),
    ejemplos: `
-- Cobros por cobrador del mes:
SELECT vc.nombre AS cobrador, COUNT(*) AS recibos, SUM(rc.monto_total) AS cobrado
FROM recibos_cobro rc
JOIN vendedores_cobradores vc ON vc.id = rc.cobrador_id
WHERE rc.empresa_id = $1::uuid AND rc.estado = 'emitido'
  AND DATE_TRUNC('month', rc.fecha_emision) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY vc.nombre ORDER BY cobrado DESC

-- Promesas incumplidas:
SELECT p.razon_social AS cliente, pp.monto_prometido, pp.fecha_promesa
FROM promesas_pago pp
JOIN clientes c ON c.id = pp.cliente_id
JOIN personas p ON p.id = c.persona_id
WHERE pp.empresa_id = $1::uuid AND pp.estado = 'incumplida'
ORDER BY pp.fecha_promesa DESC
    `.trim(),
  },

  // ── 3. CLIENTES ───────────────────────────────────────────────────────────
  {
    area: 'Clientes / Personas',
    tablas: `
── clientes ────────────────────────────────────────────────
  empresa_id        UUID  ← filtrar vía persona_id o directo
  persona_id        UUID  FK → personas
  saldo_pendiente   DECIMAL  ← deuda total actual
  limite_credito    DECIMAL
  bloqueado_credito BOOLEAN
  fecha_ultimo_pago TIMESTAMPTZ
  tipo_cliente      VARCHAR  ← 'minorista','mayorista','distribuidor'
  active            BOOLEAN
  deleted           BOOLEAN

── personas ────────────────────────────────────────────────
Datos personales/empresariales.
  empresa_id        UUID
  razon_social      VARCHAR  ← nombre o razón social
  nro_documento     VARCHAR  ← CI o RUC
  telefono          VARCHAR
  celular           VARCHAR
  email             VARCHAR

── solicitud_credito ───────────────────────────────────────
  empresa_id        UUID
  cliente_id        UUID  FK → clientes
  monto_solicitado  DECIMAL
  monto_aprobado    DECIMAL
  estado            VARCHAR  ← 'pendiente','aprobada','rechazada'
  fecha_solicitud   TIMESTAMP
    `.trim(),
  },

  // ── 4. COMPRAS / PROVEEDORES ──────────────────────────────────────────────
  {
    area: 'Compras / Proveedores',
    tablas: `
── compra_cab ──────────────────────────────────────────────
Cabecera de compras a proveedores.
  empresa_id            UUID  ← FILTRAR SIEMPRE
  proveedor_id          UUID  FK → proveedores
  fecha_emision         TIMESTAMP
  total                 DECIMAL  ← monto total de la compra ← USAR ESTO (no existe total_compra)
  cotizacion            DECIMAL  ← tipo de cambio si es moneda extranjera; para convertir a PYG: total * COALESCE(cotizacion, 1)
  estado                VARCHAR  ← 'pendiente','pagada','anulada'
  nro_factura_proveedor VARCHAR

── compra_det ──────────────────────────────────────────────
Ítems de cada compra.
  compra_cab_id    UUID  FK → compra_cab
  producto_id      UUID  FK → productos
  descripcion      VARCHAR
  cantidad         DECIMAL
  precio_unitario  DECIMAL
  total_item       DECIMAL

── proveedores ─────────────────────────────────────────────
  empresa_id       UUID
  persona_id       UUID  FK → personas
  saldo_pendiente  DECIMAL  ← deuda con el proveedor
  activo           BOOLEAN

── cuentas_pagar ───────────────────────────────────────────
Cuentas por pagar a proveedores.
  empresa_id        UUID
  proveedor_id      UUID  FK → proveedores
  compra_cab_id     UUID  FK → compra_cab
  monto_total       DECIMAL
  saldo_pendiente   DECIMAL
  fecha_vencimiento DATE
  estado            VARCHAR  ← 'pendiente','pagada','vencida'
    `.trim(),
    ejemplos: `
-- Compras por proveedor del mes (en PYG):
SELECT p.razon_social AS proveedor, COUNT(*) AS compras,
       SUM(cc.total * COALESCE(cc.cotizacion, 1)) AS total_gs
FROM compra_cab cc
JOIN proveedores prov ON prov.id = cc.proveedor_id
JOIN personas p ON p.id = prov.persona_id
WHERE cc.empresa_id = $1::uuid AND cc.estado != 'anulada'
  AND DATE_TRUNC('month', cc.fecha_emision) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY p.razon_social ORDER BY total_gs DESC
    `.trim(),
  },

  // ── 5. INVENTARIO / STOCK ─────────────────────────────────────────────────
  {
    area: 'Inventario / Stock / Productos',
    tablas: `
── productos ───────────────────────────────────────────────
  empresa_id       UUID
  cod_producto     VARCHAR
  descripcion      VARCHAR  ← nombre del producto
  precio_venta     DECIMAL
  active           BOOLEAN
  deleted          BOOLEAN

── stock_deposito ──────────────────────────────────────────
Stock actual por depósito.
  producto_id          UUID  FK → productos
  deposito_id          UUID  FK → depositos
  cantidad_disponible  DECIMAL  ← stock disponible ← USAR ESTO (no existe columna "cantidad")
  cantidad_reservada   DECIMAL  ← stock reservado
  stock_minimo         DECIMAL

── depositos ───────────────────────────────────────────────
  empresa_id       UUID
  descripcion      VARCHAR

── movimientos_inventario ──────────────────────────────────
Movimientos de stock (entradas/salidas/ajustes).
  empresa_id       UUID
  producto_id      UUID  FK → productos
  tipo_movimiento  VARCHAR  ← 'entrada','salida','ajuste'
  cantidad         DECIMAL
  fecha_movimiento TIMESTAMP

── lotes_producto ──────────────────────────────────────────
Lotes de inventario con trazabilidad y vencimiento (FIFO).
  empresa_id          UUID  ← FILTRAR SIEMPRE
  producto_id         UUID  FK → productos
  compra_id           UUID  FK → compra_cab
  numero_lote         VARCHAR  ← código de lote
  fecha_fabricacion   DATE
  fecha_vencimiento   DATE     ← fecha de vencimiento del lote
  cantidad_inicial    DECIMAL  ← cantidad al ingresar
  cantidad_disponible DECIMAL  ← stock actual del lote ← USAR ESTO
  costo_unitario      DECIMAL  ← costo de compra por unidad
  activo              BOOLEAN

⚠ Para lotes próximos a vencer: filtrar por fecha_vencimiento BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL 'N days'

── categorias ──────────────────────────────────────────────
  empresa_id       UUID
  descripcion      VARCHAR
  activo           BOOLEAN

── lista_precios_productos ─────────────────────────────────
Precio por producto en cada lista.
  lista_precios_id UUID
  producto_id      UUID
  precio           DECIMAL
    `.trim(),
    ejemplos: `
-- Stock actual de todos los productos:
SELECT pr.descripcion AS producto, SUM(sd.cantidad_disponible) AS stock_total
FROM stock_deposito sd
JOIN productos pr ON pr.id = sd.producto_id
WHERE pr.empresa_id = $1::uuid AND pr.deleted = false
GROUP BY pr.descripcion ORDER BY stock_total ASC

-- Productos con stock bajo (menos de 10 unidades):
SELECT pr.descripcion AS producto, SUM(sd.cantidad_disponible) AS stock
FROM stock_deposito sd
JOIN productos pr ON pr.id = sd.producto_id
WHERE pr.empresa_id = $1::uuid AND pr.deleted = false
GROUP BY pr.descripcion HAVING SUM(sd.cantidad_disponible) < 10
ORDER BY stock ASC

-- Lotes próximos a vencer en los próximos 30 días:
SELECT pr.descripcion AS producto, lp.numero_lote AS lote,
       lp.cantidad_disponible AS cantidad, lp.fecha_vencimiento AS vencimiento,
       (lp.fecha_vencimiento - CURRENT_DATE) AS dias_restantes
FROM lotes_producto lp
JOIN productos pr ON pr.id = lp.producto_id
WHERE lp.empresa_id = $1::uuid AND lp.activo = true
  AND lp.cantidad_disponible > 0
  AND lp.fecha_vencimiento BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '30 days'
ORDER BY lp.fecha_vencimiento ASC
    `.trim(),
  },

  // ── 6. CAJA / TESORERÍA ───────────────────────────────────────────────────
  {
    area: 'Caja / Tesorería',
    tablas: `
── sesiones_caja ───────────────────────────────────────────
Sesiones de apertura/cierre de caja.
  empresa_id       UUID
  caja_id          UUID  FK → cajas
  fecha_apertura   TIMESTAMPTZ
  fecha_cierre     TIMESTAMPTZ
  total_ingresos   DECIMAL
  total_egresos    DECIMAL
  diferencia       DECIMAL  ← diferencia entre sistema y contado
  estado           VARCHAR

── movimiento_cajas ────────────────────────────────────────
Movimientos de dinero en caja.
  empresa_id       UUID
  sesion_caja_id   UUID  FK → sesiones_caja
  tipo             VARCHAR  ← 'ingreso','egreso'
  monto            DECIMAL
  concepto         VARCHAR
  fecha            TIMESTAMP

── cajas ───────────────────────────────────────────────────
  empresa_id       UUID
  descripcion      VARCHAR  ← nombre de la caja
  activo           BOOLEAN

── arqueo_cajas ────────────────────────────────────────────
  empresa_id       UUID
  sesion_caja_id   UUID  FK → sesiones_caja
  total_sistema    DECIMAL
  total_contado    DECIMAL
  diferencia       DECIMAL
  fecha_arqueo     TIMESTAMP

── gastos ──────────────────────────────────────────────────
Gastos operativos.
  empresa_id       UUID
  concepto         VARCHAR
  monto            DECIMAL
  fecha            TIMESTAMP
  categoria        VARCHAR
    `.trim(),
    ejemplos: `
-- Ingresos vs egresos de caja por día esta semana:
SELECT DATE(mc.fecha) AS dia,
       SUM(CASE WHEN mc.tipo = 'ingreso' THEN mc.monto ELSE 0 END) AS ingresos,
       SUM(CASE WHEN mc.tipo = 'egreso'  THEN mc.monto ELSE 0 END) AS egresos
FROM movimiento_cajas mc
WHERE mc.empresa_id = $1::uuid
  AND mc.fecha >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY DATE(mc.fecha) ORDER BY dia

-- Gastos por categoría del mes:
SELECT categoria, SUM(monto) AS total
FROM gastos
WHERE empresa_id = $1::uuid
  AND DATE_TRUNC('month', fecha) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY categoria ORDER BY total DESC
    `.trim(),
  },

  // ── 7. PEDIDOS / MAYORISTAS ───────────────────────────────────────────────
  {
    area: 'Pedidos / Mayoristas',
    tablas: `
── pedidos ─────────────────────────────────────────────────
Pedidos mayoristas.
  empresa_id       UUID
  cliente_id       UUID  FK → clientes
  fecha_pedido     TIMESTAMP
  total_pedido     DECIMAL
  estado           VARCHAR  ← 'pendiente','confirmado','entregado','cancelado'

── pedido_detalle ──────────────────────────────────────────
  pedido_id        UUID  FK → pedidos
  producto_id      UUID  FK → productos
  cantidad         DECIMAL
  precio_unitario  DECIMAL
  total_item       DECIMAL
    `.trim(),
  },

  // ── 8. COBRANZAS / RUTAS ──────────────────────────────────────────────────
  {
    area: 'Rutas / Zonas de Cobranza',
    tablas: `
── rutas_cobranza ──────────────────────────────────────────
  empresa_id       UUID
  cobrador_id      UUID  FK → vendedores_cobradores
  descripcion      VARCHAR
  activo           BOOLEAN

── zonas_cobranza ──────────────────────────────────────────
  empresa_id       UUID
  descripcion      VARCHAR

── vendedores_cobradores ───────────────────────────────────
Vendedores y cobradores.
  empresa_id       UUID
  nombre           VARCHAR
  tipo             VARCHAR  ← 'vendedor','cobrador','ambos'
  activo           BOOLEAN
    `.trim(),
  },

  // ── 9. TESORERÍA Y BANCOS ─────────────────────────────────────────────────
  {
    area: 'Tesorería y Bancos',
    tablas: `
── tes_cuentas ─────────────────────────────────────────────
Cuentas bancarias y de efectivo registradas.
  empresa_id    UUID  ← FILTRAR SIEMPRE
  nombre        VARCHAR  ← nombre de la cuenta / banco
  tipo          VARCHAR  ← 'banco','efectivo','caja_chica'
  moneda        VARCHAR  ← 'PYG','USD','BRL'
  saldo_actual  DECIMAL  ← saldo actual de la cuenta ← USAR ESTO
  activo        BOOLEAN

── tes_movimientos ─────────────────────────────────────────
Movimientos de tesorería (ingresos/egresos en cuentas bancarias).
  empresa_id    UUID
  cuenta_id     UUID  FK → tes_cuentas
  tipo          VARCHAR  ← 'INGRESO','EGRESO'
  estado        VARCHAR  ← 'PENDIENTE','PROCESADO','ANULADO'
  fecha         DATE
  monto         DECIMAL
  descripcion   VARCHAR
  origen_tipo   VARCHAR  ← 'COBRO','CHEQUE','TRANSFERENCIA','MANUAL'
  categoria_id  UUID  FK → tes_categorias

── tes_cheques ─────────────────────────────────────────────
Cheques recibidos y emitidos.
  empresa_id      UUID
  tipo            VARCHAR  ← 'RECIBIDO','EMITIDO'
  estado          VARCHAR  ← 'EN_CARTERA','DEPOSITADO','DEBITADO','RECHAZADO','DEVUELTO','ANULADO'
  banco_emisor    VARCHAR
  numero_cheque   VARCHAR
  monto           DECIMAL
  fecha_emision   DATE
  fecha_vencimiento DATE  ← cuándo vence el cheque (cobro/pago)
  cuenta_id       UUID  FK → tes_cuentas  (cuenta donde se depositó/debitó)

── tes_transferencias ──────────────────────────────────────
Transferencias entre cuentas propias.
  empresa_id      UUID
  cuenta_origen_id  UUID  FK → tes_cuentas
  cuenta_destino_id UUID  FK → tes_cuentas
  monto           DECIMAL
  fecha           DATE
  estado          VARCHAR  ← 'PENDIENTE','PROCESADA','ANULADA'

── tes_categorias ──────────────────────────────────────────
Categorías de movimientos de tesorería.
  empresa_id   UUID
  nombre       VARCHAR
  tipo         VARCHAR  ← 'INGRESO','EGRESO'
  sistema      BOOLEAN  ← true = categoría del sistema (no editable)

── tes_extracto_bancario ───────────────────────────────────
Extractos bancarios importados para conciliación.
  empresa_id   UUID
  cuenta_id    UUID  FK → tes_cuentas
  fecha_desde  DATE
  fecha_hasta  DATE
  saldo_inicial DECIMAL
  saldo_final  DECIMAL
  estado       tes_extracto_estado  ← 'borrador','procesado','conciliado'

── tes_extracto_lineas ─────────────────────────────────────
Líneas del extracto bancario (una por transacción del banco).
  extracto_id     UUID  FK → tes_extracto_bancario
  fecha           DATE
  descripcion     VARCHAR
  monto           DECIMAL
  tipo            VARCHAR  ← 'CREDITO','DEBITO'
  conciliado      BOOLEAN  ← true si ya fue conciliada con un movimiento
  movimiento_id   UUID  FK → tes_movimientos (NULL si sin conciliar)
  sin_correspondencia BOOLEAN  ← true si el usuario marcó que no tiene movimiento en el ERP
    `.trim(),
    ejemplos: `
-- Posición de caja actual (saldo por cuenta):
SELECT tc.nombre AS cuenta, tc.tipo, tc.moneda, tc.saldo_actual AS saldo
FROM tes_cuentas tc
WHERE tc.empresa_id = $1::uuid AND tc.activo = true
ORDER BY tc.saldo_actual DESC

-- Cheques EN CARTERA próximos a vencer (7 días):
SELECT ch.numero_cheque, ch.banco_emisor, ch.monto,
       ch.fecha_vencimiento AS vencimiento,
       (ch.fecha_vencimiento - CURRENT_DATE) AS dias_restantes
FROM tes_cheques ch
WHERE ch.empresa_id = $1::uuid
  AND ch.tipo = 'RECIBIDO'
  AND ch.estado = 'EN_CARTERA'
  AND ch.fecha_vencimiento BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '7 days'
ORDER BY ch.fecha_vencimiento ASC

-- Flujo de caja por cuenta este mes:
SELECT tc.nombre AS cuenta,
       SUM(CASE WHEN tm.tipo = 'INGRESO' THEN tm.monto ELSE 0 END) AS ingresos,
       SUM(CASE WHEN tm.tipo = 'EGRESO'  THEN tm.monto ELSE 0 END) AS egresos
FROM tes_movimientos tm
JOIN tes_cuentas tc ON tc.id = tm.cuenta_id
WHERE tm.empresa_id = $1::uuid
  AND tm.estado = 'PROCESADO'
  AND DATE_TRUNC('month', tm.fecha) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY tc.nombre
ORDER BY (SUM(CASE WHEN tm.tipo = 'INGRESO' THEN tm.monto ELSE 0 END)
         - SUM(CASE WHEN tm.tipo = 'EGRESO' THEN tm.monto ELSE 0 END)) DESC

-- Cheques emitidos pendientes de débito:
SELECT ch.numero_cheque, ch.banco_emisor, ch.monto, ch.fecha_vencimiento
FROM tes_cheques ch
WHERE ch.empresa_id = $1::uuid AND ch.tipo = 'EMITIDO' AND ch.estado = 'EN_CARTERA'
ORDER BY ch.fecha_vencimiento ASC
    `.trim(),
  },

  // ── 10. CONTABILIDAD ──────────────────────────────────────────────────────
  {
    area: 'Contabilidad',
    tablas: `
── cont_plan_cuentas ───────────────────────────────────────
Plan de cuentas contable (árbol jerárquico).
  empresa_id         UUID
  codigo             VARCHAR  ← código contable (ej: '1.01.001') ← USAR COMO IDENTIFICADOR
  descripcion        VARCHAR  ← nombre de la cuenta
  tipo               VARCHAR  ← 'activo','pasivo','patrimonio','ingreso','egreso'
  naturaleza         VARCHAR  ← 'DEUDOR','ACREEDOR'
  nivel              INT      ← nivel en árbol (1=raíz, 2=grupo, 3=subcuenta, 4=cuenta movible)
  cuenta_padre_id    UUID  FK → cont_plan_cuentas (cuenta padre)
  acepta_movimientos BOOLEAN  ← true = puede recibir asientos (cuentas hoja)
  active             BOOLEAN

── cont_ejercicios ─────────────────────────────────────────
Ejercicios contables anuales.
  empresa_id   UUID
  anio         INT      ← año del ejercicio
  fecha_inicio DATE
  fecha_fin    DATE
  estado       VARCHAR  ← 'ABIERTO','CERRADO'

── cont_periodos ───────────────────────────────────────────
Períodos mensuales dentro de un ejercicio.
  ejercicio_id UUID  FK → cont_ejercicios
  empresa_id   UUID
  numero       INT      ← número de mes (1-12)
  estado       VARCHAR  ← 'ABIERTO','CERRADO'

── cont_asientos ───────────────────────────────────────────
Cabecera de asientos contables.
  empresa_id      UUID  ← FILTRAR SIEMPRE
  periodo_id      UUID  FK → cont_periodos
  numero          INT      ← número de asiento ← USAR COMO IDENTIFICADOR
  fecha           DATE
  glosa           VARCHAR  ← descripción del asiento
  estado          VARCHAR  ← 'BORRADOR','CONFIRMADO','REVERTIDO'
  total_debe_pyg  DECIMAL  ← total del debe en PYG
  total_haber_pyg DECIMAL  ← total del haber en PYG (debe = haber si cuadra)

── cont_asientos_det ───────────────────────────────────────
Líneas de asiento (partida doble).
  asiento_id    UUID  FK → cont_asientos
  cuenta_id     UUID  FK → cont_plan_cuentas
  descripcion   VARCHAR  ← glosa de la línea
  debe_pyg      DECIMAL  ← importe al debe en PYG (0 si es haber)
  haber_pyg     DECIMAL  ← importe al haber en PYG (0 si es debe)
    `.trim(),
    ejemplos: `
-- Balance de comprobación del mes actual:
SELECT pc.codigo, pc.descripcion AS cuenta,
       SUM(ad.debe_pyg)  AS total_debe,
       SUM(ad.haber_pyg) AS total_haber,
       SUM(ad.debe_pyg) - SUM(ad.haber_pyg) AS saldo
FROM cont_asientos_det ad
JOIN cont_asientos ac ON ac.id = ad.asiento_id
JOIN cont_plan_cuentas pc ON pc.id = ad.cuenta_id
JOIN cont_periodos cp ON cp.id = ac.periodo_id
WHERE ac.empresa_id = $1::uuid
  AND ac.estado = 'CONFIRMADO'
  AND DATE_TRUNC('month', ac.fecha) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY pc.codigo, pc.descripcion
ORDER BY pc.codigo

-- Último asiento contable con sus líneas:
SELECT ac.numero AS asiento, TO_CHAR(ac.fecha,'DD/MM/YYYY') AS fecha,
       ac.glosa, pc.codigo, pc.descripcion AS cuenta,
       ad.debe_pyg AS debe, ad.haber_pyg AS haber
FROM cont_asientos_det ad
JOIN cont_asientos ac ON ac.id = ad.asiento_id
JOIN cont_plan_cuentas pc ON pc.id = ad.cuenta_id
WHERE ac.empresa_id = $1::uuid AND ac.estado = 'CONFIRMADO'
ORDER BY ac.fecha DESC, ac.numero DESC, ad.orden ASC
LIMIT 20

-- Asientos del mes por estado:
SELECT estado, COUNT(*) AS cantidad,
       SUM(total_debe_pyg) AS total_movido
FROM cont_asientos
WHERE empresa_id = $1::uuid
  AND DATE_TRUNC('month', fecha) = DATE_TRUNC('month', CURRENT_DATE)
GROUP BY estado

-- Movimientos de una cuenta contable (buscar por código):
SELECT TO_CHAR(ac.fecha,'DD/MM/YYYY') AS fecha, ac.numero AS asiento,
       ac.glosa, ad.debe_pyg AS debe, ad.haber_pyg AS haber
FROM cont_asientos_det ad
JOIN cont_asientos ac ON ac.id = ad.asiento_id
JOIN cont_plan_cuentas pc ON pc.id = ad.cuenta_id
WHERE ac.empresa_id = $1::uuid AND ac.estado = 'CONFIRMADO'
  AND pc.codigo LIKE '1.01%'
ORDER BY ac.fecha DESC
LIMIT 50
    `.trim(),
  },

  // ── 11. RRHH / PRESENTISMO ───────────────────────────────────────────────
  {
    area: 'RRHH / Presentismo',
    tablas: `
── rrhh_relojes_marcadores ──────────────────────────
Configuración de relojes biométricos / marcadores.
  id (uuid), empresa_id (uuid), sucursal_id (uuid?), nombre (varchar)
  tipo_conexion (enum: API | PLANILLA)
  url_base (varchar?), tipo_auth (enum?: NONE|BASIC|BEARER|API_KEY)
  intervalo_polling_min (int?), activo (bool)
  ultima_sincronizacion (timestamp?), creado_por (uuid?)
  created_at, updated_at

── rrhh_marcaciones_raw ─────────────────────────────
Marcaciones tal como llegan del reloj / planilla — inmutables.
  id (uuid), empresa_id (uuid), reloj_id (uuid?)
  fuente (enum: API | PLANILLA)
  documento_raw (varchar), empleado_id (uuid?, NULL si no matchea)
  timestamp_marcacion (timestamp)
  tipo_marcacion (enum?: ENTRADA | SALIDA)
  estado_importacion (enum: PENDIENTE|PROCESADO|DUPLICADO|ERROR)
  error_descripcion (text?), batch_importacion_id (uuid)

── rrhh_marcaciones_limpias ─────────────────────────
Una fila por empleado/día tras la deduplicación.
  id (uuid), empresa_id (uuid), empleado_id (uuid), fecha (date)
  hora_entrada (time?), hora_salida (time?)
  turno_id (uuid?), procesado_novedades (bool)
  marcacion_raw_entrada_id (uuid?), marcacion_raw_salida_id (uuid?)
UNIQUE (empleado_id, fecha)

── rrhh_turnos + rrhh_turnos_bloques ────────────────
Turnos laborales (SIMPLE u CORTADO) con uno o más bloques.
  rrhh_turnos: id, empresa_id, nombre, tipo (enum: SIMPLE|CORTADO),
    lunes..domingo (bool), activo (bool)
  rrhh_turnos_bloques: id, turno_id, orden (int 1..N), hora_entrada, hora_salida

── rrhh_empleados_turnos ────────────────────────────
Asignación de turno a empleado.
  id, empresa_id, empleado_id, turno_id
  tipo_asignacion (enum: PERPETUA | OCASIONAL)
  fecha_inicio (date?), fecha_fin (date?)
  prioridad (int, menor = mayor precedencia)
  observacion (text?)

── rrhh_permisos_presentismo ────────────────────────
Permisos individuales por día.
  id, empresa_id, empleado_id, fecha
  tipo_permiso (enum: JORNADA_COMPLETA | TURNO | SALIDA_ANTICIPADA | LLEGADA_TARDIA | AUSENCIA_TURNO)
  turno_bloque_orden (int?), hora_desde (time?), hora_hasta (time?)
  minutos_tolerados (int?), motivo (text)
  tipo_justificacion (enum: MEDICA|PERSONAL|FUERZA_MAYOR|INSTITUCIONAL|CLIMA|OTRO)
  afecta_descuento (bool), estado (enum: PENDIENTE|APROBADO|RECHAZADO)
  aprobado_por (uuid?), fecha_aprobacion (timestamp?)

── rrhh_presentismo_tolerancias_especiales ──────────
Tolerancias por fecha que prevalecen sobre el parámetro global.
  id, empresa_id, fecha
  tolerancia_entrada_min (int), tolerancia_salida_min (int)
  motivo (text)
  aplica_a (enum: TODOS|SUCURSAL|DEPARTAMENTO), aplica_a_id (uuid?)

── rrhh_marcaciones_log_correcciones ────────────────
Bitácora INMUTABLE de la deduplicación.
  id, empresa_id, empleado_id, fecha, batch_importacion_id
  cantidad_marcaciones_raw (int), cantidad_duplicados_descartados (int)
  timestamp_entrada_seleccionado, timestamp_salida_seleccionado
  timestamps_descartados (jsonb)
  motivo_correccion (enum: DUPLICADO_ENTRADA|DUPLICADO_SALIDA|DUPLICADO_AMBOS)
  procesado_en (timestamp), procesado_por (uuid?)

── rrhh_asistencia_novedades (extendida en M19) ─────
Novedades de asistencia y presentismo.
  ...campos legacy + presentismo:
  turno_id (uuid?), turno_bloque_orden (int?)
  hora_esperada (time?), hora_real (time?), tolerancia_aplicada_min (int?)
  permiso_id (uuid?), generado_automatico (bool)
  estado (enum: BORRADOR | CONFIRMADA | ANULADA)
  tipo_novedad (enum: AUSENCIA | TARDANZA | SALIDA_ANTICIPADA |
                       AUSENCIA_JORNADA | AUSENCIA_BLOQUE |
                       AUSENCIA_JUSTIFICADA | TARDANZA_JUSTIFICADA)
  impacta_liquidacion (bool), liquidacion_id (uuid?)
`.trim(),
    ejemplos: `
-- Tardanzas confirmadas del mes en curso por empleado:
SELECT
  e.numero_empleado AS "Nro",
  e.nombres || ' ' || e.apellidos AS "Empleado",
  COUNT(*)                              AS "Tardanzas",
  COALESCE(SUM(n.minutos_tardanza), 0)  AS "Min totales"
FROM rrhh_asistencia_novedades n
JOIN rrhh_empleados e ON e.id = n.empleado_id
WHERE n.empresa_id = $1::uuid
  AND n.tipo_novedad IN ('TARDANZA')
  AND n.estado = 'CONFIRMADA'
  AND n.fecha >= DATE_TRUNC('month', NOW())::date
GROUP BY e.numero_empleado, e.nombres, e.apellidos
ORDER BY "Min totales" DESC
LIMIT 50;

-- Ausencias del mes por sucursal:
SELECT
  s.descripcion          AS "Sucursal",
  COUNT(*) FILTER (WHERE n.tipo_novedad IN ('AUSENCIA','AUSENCIA_JORNADA','AUSENCIA_BLOQUE')) AS "Ausencias",
  COUNT(*) FILTER (WHERE n.tipo_novedad = 'AUSENCIA_JUSTIFICADA') AS "Justificadas"
FROM rrhh_asistencia_novedades n
JOIN rrhh_empleados e ON e.id = n.empleado_id
JOIN empresas_sucursales s ON s.id = e.sucursal_id
WHERE n.empresa_id = $1::uuid
  AND n.estado IN ('CONFIRMADA','BORRADOR')
  AND n.fecha >= DATE_TRUNC('month', NOW())::date
GROUP BY s.descripcion
ORDER BY "Ausencias" DESC;

-- Marcaciones con duplicados detectados en los últimos 30 días:
SELECT
  TO_CHAR(l.fecha, 'DD/MM/YYYY')                     AS "Fecha",
  e.nombres || ' ' || e.apellidos                    AS "Empleado",
  l.cantidad_marcaciones_raw                         AS "Recibidas",
  l.cantidad_duplicados_descartados                  AS "Descartadas",
  l.motivo_correccion::text                          AS "Motivo"
FROM rrhh_marcaciones_log_correcciones l
JOIN rrhh_empleados e ON e.id = l.empleado_id
WHERE l.empresa_id = $1::uuid
  AND l.fecha >= NOW()::date - INTERVAL '30 days'
ORDER BY l.fecha DESC, l.procesado_en DESC
LIMIT 200;

-- Empleados con asignación de turno PERPETUA vigente:
SELECT
  e.numero_empleado AS "Nro",
  e.nombres || ' ' || e.apellidos AS "Empleado",
  t.nombre AS "Turno",
  t.tipo::text AS "Tipo"
FROM rrhh_empleados_turnos a
JOIN rrhh_empleados e ON e.id = a.empleado_id
JOIN rrhh_turnos t    ON t.id = a.turno_id
WHERE a.empresa_id = $1::uuid
  AND a.tipo_asignacion = 'PERPETUA'
  AND (a.fecha_inicio IS NULL OR a.fecha_inicio <= NOW()::date)
  AND (a.fecha_fin    IS NULL OR a.fecha_fin    >= NOW()::date)
ORDER BY e.apellidos, e.nombres;
`.trim(),
  },

  // ── 12. RRHH / LEGAJOS ───────────────────────────────────────────────────
  {
    area: 'RRHH / Legajos',
    tablas: `
── rrhh_legajo_tipos_documento ──────────────────────
Catálogo de tipos de documento (sistema + personalizados por empresa).
  id (uuid), empresa_id (uuid?, NULL = tipo del sistema)
  codigo (varchar 30), nombre (varchar 150), descripcion (text?)
  admite_vencimiento (bool), vencimiento_obligatorio (bool)
  es_obligatorio (bool), dias_alerta_vencimiento (int?)
  permite_multiples (bool), es_del_sistema (bool), activo (bool)
Códigos del sistema: CI, CONTRATO, ALTA_IPS, FOTO_PERFIL, CERTIFICADO_MED,
  TITULO_HABILITANTE, ANTECEDENTE_POL, ANTECEDENTE_JUD, RUC,
  MODIF_CONTRATO, RECIBO_SUELDO, CERT_BANCARIO, DESVINCULACION, OTRO.

── rrhh_legajo_documentos ───────────────────────────
Documentos del legajo de cada funcionario. Metadata en BD, contenido en DO Spaces.
  id (uuid), empresa_id (uuid), empleado_id (uuid), tipo_documento_id (uuid)
  nombre_original (varchar 255), nombre_storage (varchar 500), mime_type (varchar)
  extension (varchar 10), tamanio_bytes (bigint), hash_sha256 (char 64)
  fecha_documento (date?), fecha_vencimiento (date?)
  estado_vencimiento (enum: SIN_VENCIMIENTO|VIGENTE|POR_VENCER|VENCIDO)
  version (int), documento_previo_id (uuid?)
  estado (enum: ACTIVO|REEMPLAZADO|ANULADO)
  subido_por (uuid), subido_en (timestamp)
  anulado_por (uuid?), anulado_en (timestamp?), motivo_anulacion (text?)

── rrhh_legajo_alertas_vencimiento ──────────────────
Bitácora de alertas enviadas — evita duplicados.
  id (uuid), empresa_id (uuid), documento_id (uuid), empleado_id (uuid)
  dias_para_vencer (int), canal (enum: EMAIL|NOTIFICACION_INTERNA|AMBOS)
  destinatarios (jsonb), estado_envio (enum: ENVIADO|ERROR)
  enviado_en (timestamp), error_detalle (text?)
UNIQUE (documento_id, dias_para_vencer, canal)

NOTAS DE NEGOCIO:
- Documentos ANULADOS / REEMPLAZADOS nunca se borran de la BD ni del storage.
  Solo SUPER_ADMIN y AUDITOR pueden verlos.
- Tipos del sistema (es_del_sistema=true) no se editan ni borran.
- Los archivos viven en DigitalOcean Spaces con ACL privada; el acceso
  se hace siempre por URL firmada con expiración (default 15 min).
`.trim(),
    ejemplos: `
-- Documentos vencidos del mes actual por funcionario:
SELECT
  e.numero_empleado                        AS "Nro",
  e.nombres || ' ' || e.apellidos          AS "Empleado",
  t.nombre                                 AS "Tipo",
  TO_CHAR(d.fecha_vencimiento, 'DD/MM/YYYY') AS "Vencimiento",
  d.estado_vencimiento::text               AS "Estado"
FROM rrhh_legajo_documentos d
JOIN rrhh_empleados e               ON e.id = d.empleado_id
JOIN rrhh_legajo_tipos_documento t  ON t.id = d.tipo_documento_id
WHERE d.empresa_id = $1::uuid
  AND d.estado = 'ACTIVO'
  AND d.estado_vencimiento = 'VENCIDO'
  AND d.fecha_vencimiento >= DATE_TRUNC('month', NOW())::date
ORDER BY d.fecha_vencimiento ASC
LIMIT 200;

-- Legajos incompletos: empleados activos sin algún tipo obligatorio cargado:
SELECT
  e.numero_empleado                AS "Nro",
  e.nombres || ' ' || e.apellidos  AS "Empleado",
  t.codigo                         AS "Tipo faltante",
  t.nombre                         AS "Descripción"
FROM rrhh_empleados e
CROSS JOIN rrhh_legajo_tipos_documento t
LEFT JOIN rrhh_legajo_documentos d
  ON d.empleado_id      = e.id
 AND d.tipo_documento_id = t.id
 AND d.estado            = 'ACTIVO'
WHERE e.empresa_id = $1::uuid
  AND e.estado     = 'ACTIVO'
  AND t.es_obligatorio = true
  AND t.activo         = true
  AND (t.empresa_id IS NULL OR t.empresa_id = e.empresa_id)
  AND d.id IS NULL
ORDER BY e.apellidos, e.nombres, t.codigo;

-- Cantidad de documentos cargados por tipo (últimos 6 meses):
SELECT
  t.codigo                     AS "Código",
  t.nombre                     AS "Tipo",
  COUNT(*)                     AS "Documentos",
  COUNT(*) FILTER (WHERE d.estado = 'ACTIVO')      AS "Activos",
  COUNT(*) FILTER (WHERE d.estado = 'REEMPLAZADO') AS "Reemplazados",
  COUNT(*) FILTER (WHERE d.estado = 'ANULADO')     AS "Anulados"
FROM rrhh_legajo_documentos d
JOIN rrhh_legajo_tipos_documento t ON t.id = d.tipo_documento_id
WHERE d.empresa_id = $1::uuid
  AND d.subido_en >= NOW() - INTERVAL '6 months'
GROUP BY t.codigo, t.nombre
ORDER BY "Documentos" DESC;

-- Top empleados con más versiones de documentos (reemplazos):
SELECT
  e.numero_empleado                AS "Nro",
  e.nombres || ' ' || e.apellidos  AS "Empleado",
  COUNT(*)                         AS "Versiones"
FROM rrhh_legajo_documentos d
JOIN rrhh_empleados e ON e.id = d.empleado_id
WHERE d.empresa_id = $1::uuid
  AND d.estado IN ('ACTIVO','REEMPLAZADO')
GROUP BY e.numero_empleado, e.nombres, e.apellidos
HAVING COUNT(*) > 1
ORDER BY "Versiones" DESC
LIMIT 50;

-- Alertas de vencimiento enviadas la última semana:
SELECT
  TO_CHAR(a.enviado_en, 'DD/MM/YYYY HH24:MI') AS "Enviado",
  e.nombres || ' ' || e.apellidos             AS "Empleado",
  t.codigo                                    AS "Tipo",
  a.dias_para_vencer                          AS "Días",
  a.canal::text                               AS "Canal",
  a.estado_envio::text                        AS "Estado"
FROM rrhh_legajo_alertas_vencimiento a
JOIN rrhh_legajo_documentos d  ON d.id = a.documento_id
JOIN rrhh_legajo_tipos_documento t ON t.id = d.tipo_documento_id
JOIN rrhh_empleados e          ON e.id = a.empleado_id
WHERE a.empresa_id = $1::uuid
  AND a.enviado_en >= NOW() - INTERVAL '7 days'
ORDER BY a.enviado_en DESC
LIMIT 200;
`.trim(),
  },
];

// ─── MÓDULO RRHH / NÓMINA ────────────────────────────────────────────────────
// Se inyecta dinámicamente sólo cuando la empresa tiene el módulo activo
// y el usuario tiene permiso (ver RrhhContextService).
// ────────────────────────────────────────────────────────────────────────────
const RRHH_MODULE: SchemaModule = {
  area: 'RRHH / Nómina',
  tablas: `
── rrhh_empleados ──────────────────────────────────────────
Empleados de la empresa.
  empresa_id        UUID  ← FILTRAR SIEMPRE
  numero_empleado   VARCHAR  ← código visible del empleado
  nombres           VARCHAR
  apellidos         VARCHAR
  cedula_identidad  VARCHAR
  sucursal_id       UUID  FK → empresas_sucursales
  departamento_id   UUID  FK → rrhh_departamentos
  cargo_id          UUID  FK → rrhh_cargos
  centro_costo_id   UUID  FK → cont_centros_costo (centro de costo unificado con Contabilidad)
  tipo_empleado_id  UUID  FK → rrhh_tipos_empleado
  fecha_ingreso     DATE
  fecha_egreso      DATE     ← NULL si activo
  salario_base      DECIMAL  ← SENSIBLE (ver regla de privacidad)
  tipo_salario      VARCHAR  ← 'MENSUAL','JORNAL','POR_HORA'
  numero_ips        VARCHAR
  aporta_ips        BOOLEAN  ← DERIVADO de regimen_ips (false sólo si regimen_ips='EXENTO')
  regimen_ips       VARCHAR  ← 'GENERAL','MICROEMPRESA_80','DOMESTICO','EXENTO'
  estado            VARCHAR  ← 'ACTIVO','EGRESADO','SUSPENDIDO'

── rrhh_cargos / rrhh_departamentos / rrhh_tipos_empleado
Catálogos. Todos tienen: id, empresa_id, nombre/descripcion, activo.
(Los centros de costo viven en cont_centros_costo, compartidos con Contabilidad.)

── rrhh_conceptos_liquidacion ──────────────────────────────
Conceptos que componen la liquidación.
  empresa_id        UUID
  codigo            VARCHAR  ← código visible (ej: 'SALARIO','IPS_OBRERO')
  nombre            VARCHAR
  tipo              VARCHAR  ← 'INGRESO','EGRESO'
  categoria         VARCHAR  ← 'HABITUAL','EVENTUAL','LEGAL'
  afecta_base_ips   BOOLEAN
  afecta_base_aguinaldo BOOLEAN
  activo            BOOLEAN

── rrhh_liquidaciones_cabecera ─────────────────────────────
Cabecera de una liquidación (quincenal o mensual).
  empresa_id        UUID
  sucursal_id       UUID
  periodo_anio      INT
  periodo_mes       INT      ← 1-12
  quincena          INT      ← 1 o 2 (NULL si es MENSUAL)
  tipo              VARCHAR  ← 'MENSUAL','QUINCENAL'
  estado            VARCHAR  ← 'BORRADOR','CALCULADA','CERRADA','ANULADA'
  total_ingresos    DECIMAL
  total_egresos     DECIMAL
  total_neto        DECIMAL
  total_ips_obrero  DECIMAL
  total_ips_patronal DECIMAL
  total_ips_admin   DECIMAL
  cantidad_empleados INT
  fecha_cierre      TIMESTAMP

── rrhh_liquidaciones_detalle ──────────────────────────────
Detalle por empleado y concepto.
  liquidacion_id   UUID  FK → rrhh_liquidaciones_cabecera
  empleado_id      UUID  FK → rrhh_empleados ← SENSIBLE para reportes individuales
  concepto_id      UUID  FK → rrhh_conceptos_liquidacion
  tipo             VARCHAR  ← 'INGRESO','EGRESO'
  monto            DECIMAL  ← SENSIBLE a nivel individual

── rrhh_liq_empleado_resumen ───────────────────────────────
Resumen por empleado en una liquidación (recibo de pago).
  liquidacion_id   UUID
  empleado_id      UUID
  salario_bruto    DECIMAL  ← SENSIBLE
  total_ingresos   DECIMAL  ← SENSIBLE
  total_egresos    DECIMAL  ← SENSIBLE
  base_ips         DECIMAL
  ips_obrero       DECIMAL
  neto_a_pagar     DECIMAL  ← SENSIBLE

── rrhh_anticipos_salario ──────────────────────────────────
  empresa_id, empleado_id, fecha_solicitud, monto, periodo_anio, periodo_mes,
  estado ('PENDIENTE','APROBADO','APLICADO','ANULADO')

── rrhh_prestamos ──────────────────────────────────────────
  empresa_id, empleado_id, fecha_otorgamiento, monto_total,
  cantidad_cuotas, monto_cuota, saldo_pendiente,
  estado ('ACTIVO','PAGADO','ANULADO')

── rrhh_vacaciones_solicitudes / rrhh_vacaciones_saldo ─────
Solicitudes y saldos de vacaciones por empleado/año.
  rrhh_vacaciones_saldo: anio, dias_correspondidos, dias_tomados, dias_pendientes
  rrhh_vacaciones_solicitudes: fecha_inicio, fecha_fin, estado, dias_habiles

── rrhh_asistencia_novedades ───────────────────────────────
Faltas, tardanzas, justificaciones, horas extras.
  empleado_id, fecha, tipo_novedad, minutos_tardanza, justificado

── rrhh_reportes_ips / rrhh_reportes_ips_det ───────────────
Reportes mensuales a IPS (declaración).
  periodo_anio, periodo_mes, total_aporte_obrero, total_aporte_patronal,
  total_a_depositar, estado

── rrhh_desvinculaciones ───────────────────────────────────
Cálculos de liquidación final por desvinculación.
  empleado_id, tipo_desvinculacion, fecha_ultimo_dia,
  anios_antiguedad, monto_indemnizacion, monto_preaviso,
  aguinaldo_proporcional, monto_vacaciones_no_gozadas

── rrhh_parametros_sistema ─────────────────────────────────
Parámetros configurables (salario mínimo, % IPS, etc.).
  clave, valor, tipo_dato, vigencia_desde, vigencia_hasta
  `.trim(),
  ejemplos: `
-- Nómina total del mes en curso (líquido a pagar):
SELECT lc.periodo_anio AS anio, lc.periodo_mes AS mes,
       SUM(lc.total_neto) AS neto_a_pagar,
       SUM(lc.total_ips_obrero + lc.total_ips_patronal + lc.total_ips_admin) AS ips_total,
       SUM(lc.cantidad_empleados) AS empleados
FROM rrhh_liquidaciones_cabecera lc
WHERE lc.empresa_id = $1::uuid
  AND lc.estado IN ('CALCULADA','CERRADA')
  AND lc.periodo_anio = EXTRACT(YEAR FROM CURRENT_DATE)
  AND lc.periodo_mes  = EXTRACT(MONTH FROM CURRENT_DATE)
GROUP BY lc.periodo_anio, lc.periodo_mes

-- Top conceptos de egreso del último mes cerrado (agregado, sin nombres):
SELECT c.codigo, c.nombre AS concepto, SUM(ld.monto) AS total
FROM rrhh_liquidaciones_detalle ld
JOIN rrhh_liquidaciones_cabecera lc ON lc.id = ld.liquidacion_id
JOIN rrhh_conceptos_liquidacion c   ON c.id  = ld.concepto_id
WHERE lc.empresa_id = $1::uuid
  AND lc.estado = 'CERRADA'
  AND ld.tipo = 'EGRESO'
  AND DATE_TRUNC('month', MAKE_DATE(lc.periodo_anio, lc.periodo_mes, 1))
      = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')
GROUP BY c.codigo, c.nombre
ORDER BY total DESC LIMIT 10

-- Empleados activos por sector (departamento):
SELECT d.nombre AS departamento, COUNT(*) AS empleados
FROM rrhh_empleados e
JOIN rrhh_departamentos d ON d.id = e.departamento_id
WHERE e.empresa_id = $1::uuid AND e.estado = 'ACTIVO'
GROUP BY d.nombre ORDER BY empleados DESC

-- Préstamos vigentes (agregado):
SELECT COUNT(*) AS cantidad,
       SUM(saldo_pendiente) AS saldo_total
FROM rrhh_prestamos
WHERE empresa_id = $1::uuid AND estado = 'ACTIVO'

-- Vacaciones pendientes de tomar por año:
SELECT vs.anio, SUM(vs.dias_pendientes) AS dias_pendientes
FROM rrhh_vacaciones_saldo vs
WHERE vs.empresa_id = $1::uuid AND vs.dias_pendientes > 0
GROUP BY vs.anio ORDER BY vs.anio DESC

-- Costo IPS del último mes cerrado:
SELECT periodo_anio AS anio, periodo_mes AS mes,
       SUM(total_aporte_obrero)   AS obrero,
       SUM(total_aporte_patronal) AS patronal,
       SUM(total_a_depositar)     AS total_a_depositar
FROM rrhh_reportes_ips
WHERE empresa_id = $1::uuid AND estado != 'ANULADO'
GROUP BY periodo_anio, periodo_mes
ORDER BY anio DESC, mes DESC LIMIT 1

-- Anticipos pendientes de aplicar:
SELECT a.periodo_anio AS anio, a.periodo_mes AS mes,
       COUNT(*) AS cantidad, SUM(a.monto) AS total
FROM rrhh_anticipos_salario a
WHERE a.empresa_id = $1::uuid AND a.estado = 'APROBADO'
GROUP BY a.periodo_anio, a.periodo_mes
ORDER BY anio DESC, mes DESC

-- Anticipos otorgados/aplicados del mes en curso (cantidad y monto):
SELECT COUNT(*) AS cantidad, COALESCE(SUM(a.monto), 0) AS total
FROM rrhh_anticipos_salario a
WHERE a.empresa_id = $1::uuid
  AND a.estado IN ('APROBADO','APLICADO')
  AND a.periodo_anio = EXTRACT(YEAR  FROM CURRENT_DATE)
  AND a.periodo_mes  = EXTRACT(MONTH FROM CURRENT_DATE)
  `.trim(),
};

// ─── REGLAS DE PRIVACIDAD RRHH ───────────────────────────────────────────────
const RRHH_PRIVACY_RULES = `
REGLAS DE PRIVACIDAD ESPECIALES PARA RRHH/NÓMINA:
- Nunca devuelvas el nombre, apellido, cedula_identidad o numero_empleado
  de un empleado en columnas individuales SI el usuario sólo tiene permiso
  AGREGADO. Bajo permiso agregado solo permitido: COUNT, SUM, AVG, MIN, MAX,
  agrupados por concepto / departamento / cargo / periodo. NUNCA agrupar
  por empleado_id ni por nombres+apellidos.
- Bajo permiso SENSIBLE (acceso a salarios), sí podés mostrar nombre + apellido
  del empleado y montos individuales (recibos, liquidación detallada).
- En ambos modos: NUNCA incluyas empresa_id, empleado_id, liquidacion_id,
  concepto_id ni otros UUID en el SELECT final.
`.trim();


// ─── BUILDER ─────────────────────────────────────────────────────────────────
const SEP = '═'.repeat(59);

function renderModule(m: SchemaModule): string {
  let block = `${SEP}\nÁREA: ${m.area.toUpperCase()}\n${SEP}\n\n${m.tablas}`;
  if (m.ejemplos) block += `\n\nEJEMPLOS:\n${m.ejemplos}`;
  return block;
}

function buildSchemaContext(extraModules: SchemaModule[] = [], extraRules = ''): string {
  const modulosStr = [...SCHEMA_MODULES, ...extraModules].map(renderModule).join('\n\n');
  const partes = [BASE_INSTRUCTIONS];
  if (extraRules) partes.push('', extraRules);
  partes.push('', SEP, 'JOINS COMUNES', SEP, COMMON_JOINS, '', modulosStr);
  return partes.join('\n');
}

/**
 * Contexto base (sin RRHH). Se mantiene como export para compatibilidad
 * con callers que no necesitan filtros dinámicos por empresa/usuario.
 */
export const SCHEMA_CONTEXT = buildSchemaContext();

export interface BuildContextOptions {
  /** Incluir bloque RRHH (la empresa tiene el módulo + el usuario tiene permiso) */
  includeRRHH?: boolean;
  /** Si el usuario puede ver salarios/individuales. Si false, sólo agregados. */
  includeRRHHSalaries?: boolean;
  /** Dominios AI activos para la empresa (limita sugerencias y respuestas). */
  activeDomains?: string[];
}

/**
 * Construye el SCHEMA_CONTEXT con los módulos extra que correspondan
 * según los permisos de quien hace la consulta.
 */
export function buildSchemaContextFor(opts: BuildContextOptions = {}): string {
  const extras: SchemaModule[] = [];
  const reglas: string[] = [];
  if (opts.includeRRHH) {
    extras.push(RRHH_MODULE);
    if (!opts.includeRRHHSalaries) {
      reglas.push(
        RRHH_PRIVACY_RULES,
        'IMPORTANTE: el usuario actual NO tiene permiso para consultar montos individuales de nómina. Solo respondé consultas agregadas (sin nombres ni IDs de empleados).',
      );
    } else {
      reglas.push(RRHH_PRIVACY_RULES);
    }
  }
  if (opts.activeDomains && opts.activeDomains.length > 0) {
    reglas.push(
      `DOMINIOS ACTIVOS DE LA EMPRESA: ${opts.activeDomains.join(', ')}. ` +
      `Solo respondé preguntas sobre estos dominios. Si te preguntan por dominios fuera de esa lista, ` +
      `respondé que el módulo no está activo en la suscripción. Cuando sugieras preguntas de seguimiento, ` +
      `limitalas EXCLUSIVAMENTE a los dominios activos listados.`
    );
  }
  return buildSchemaContext(extras, reglas.join('\n\n'));
}
