'use strict'

const { Op } = require('sequelize');
const Usuario = require('../models/usuario');
const Capacitacion = require('../models/capacitacion');
// const CapacitacionProgramada = require('../models/capacitacion_programada'); // TODO: descomentar cuando exista tabla CAPACITACION_PROGRAMADA
const Permiso = require('../models/permiso');
const Tipo_Permiso = require('../models/tipo_permiso');
const Historial_Empresa = require('../models/historial_empresa');
const Cargo = require('../models/cargo');
const Area = require('../models/area');

// ─────────────────────────────────────────────────────────────────────────────
// Constantes de meses
// ─────────────────────────────────────────────────────────────────────────────
const NOMBRES_MESES = [
    'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio',
    'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'
];

const LABELS_MESES = [
    'ENERO', 'FEBRERO', 'MARZO', 'ABRIL', 'MAYO', 'JUNIO',
    'JULIO', 'AGOSTO', 'SEPTIEMBRE', 'OCTUBRE', 'NOVIEMBRE', 'DICIEMBRE'
];

const TRIMESTRES = ['Q1', 'Q1', 'Q1', 'Q2', 'Q2', 'Q2', 'Q3', 'Q3', 'Q3', 'Q4', 'Q4', 'Q4'];

// ─────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// HELPERS de fecha — evitan desfase UTC-5 (Ecuador)
// ─────────────────────────────────────────────────────────────────────────────
function parseFecha(valor) {
    if (!valor) return null;
    // Si es Date (Sequelize devuelve DATE a medianoche UTC) → usar componentes UTC
    if (valor instanceof Date) {
        return new Date(valor.getUTCFullYear(), valor.getUTCMonth(), valor.getUTCDate());
    }
    // Si es string 'YYYY-MM-DD' lo parsea como LOCAL para evitar desfase UTC-5
    const s = String(valor).split('T')[0];
    const [y, m, d] = s.split('-').map(Number);
    return new Date(y, m - 1, d);
}
function formatFecha(date) {
    if (!date) return '-';
    const y = date.getFullYear();
    const m = String(date.getMonth() + 1).padStart(2, '0');
    const d = String(date.getDate()).padStart(2, '0');
    return `${y}-${m}-${d}`;
}

// ─────────────────────────────────────────────────────────────────────────────
// HELPER: devuelve el historial activo más reciente de un usuario
// ─────────────────────────────────────────────────────────────────────────────
function obtenerHistorialActivo(historiales) {
    if (!historiales || historiales.length === 0) return null;
    const activos = historiales.filter(h => h.ESTADO === true || h.ESTADO === 1);
    if (activos.length === 0) return historiales[historiales.length - 1];
    activos.sort((a, b) => new Date(b.FECHA_INGRESO) - new Date(a.FECHA_INGRESO));
    return activos[0];
}

// ─────────────────────────────────────────────────────────────────────────────
// 1. MATRIZ DE CAPACITACIÓN
// POST /api/matrices-capacitacion
// Body: { anio: 2025 }
// Respuesta: array de 12 filas mensuales con capProgramadas, capRealizadas,
//            partEsperados, partAsistentes, cumplimiento, participacion,
//            icp, calificacion, tendencia, lci, meta, lcs
// ─────────────────────────────────────────────────────────────────────────────
const ICP_LCI  = 0.6;   // 60%
const ICP_META = 0.8;   // 80%
const ICP_LCS  = 1.0;   // 100%

async function matrizCapacitacion(req, res) {
    try {
        const anio = req.body.anio || new Date().getFullYear();
        const fechaInicio = `${anio}-01-01`;
        const fechaFin = `${anio}-12-31`;

        const capacitaciones = await Capacitacion.findAll({
            where: {
                FECHA_INICIO: { [Op.between]: [fechaInicio, fechaFin] }
            }
        });

        // TODO: descomentar cuando exista tabla CAPACITACION_PROGRAMADA
        // const programadas = await CapacitacionProgramada.findAll({
        //     where: { ANIO: anio }
        // });

        const calificaciones = [];

        const mesesData = NOMBRES_MESES.map((_, idx) => {
            const mesNum = idx + 1;

            // TODO: reemplazar con KPIs de CAPACITACION_PROGRAMADA cuando exista la tabla
            const capProgramadas    = 0;
            const capRealizadas     = 0;
            const partEsperados     = 0;
            const horasPlanificadas = 0;
            const horasEjecutadas   = 0;

            // Asistentes reales — desde CAPACITACION (registros por empleado)
            const del_mes_realizados = capacitaciones.filter(c => {
                const fecha = c.FECHA_INICIO || c.FECHA_REGISTRO;
                if (!fecha) return false;
                return (new Date(fecha).getMonth() + 1) === mesNum;
            }).filter(c => !c.ESTADO || c.ESTADO === 'REALIZADA');
            const partAsistentes = del_mes_realizados.length;

            const sinPlanificacion = capProgramadas === 0;

            const cumplimiento = capProgramadas > 0
                ? Math.round((capRealizadas / capProgramadas) * 100)
                : null;
            const participacion = partEsperados > 0
                ? Math.round((partAsistentes / partEsperados) * 100)
                : null;

            // ICP decimal (0-1); calificacion = clamp(10*(icp-LCI)/(LCS-LCI), 0, 10)
            const icp = capProgramadas > 0 ? capRealizadas / capProgramadas : null;
            const calificacion = icp !== null
                ? parseFloat(Math.max(0, Math.min(10, 10 * (icp - ICP_LCI) / (ICP_LCS - ICP_LCI))).toFixed(2))
                : null;

            if (calificacion !== null) calificaciones.push(calificacion);
            const promCalif = calificaciones.length > 0
                ? calificaciones.reduce((a, b) => a + b, 0) / calificaciones.length
                : 0;
            const tendencia = calificacion === null ? '=' : calificacion > promCalif ? '+' : calificacion < promCalif ? '-' : '=';

            return {
                mes: mesNum,
                periodo: LABELS_MESES[idx],
                capProgramadas,
                capRealizadas,
                partEsperados,
                partAsistentes,
                horasPlanificadas: parseFloat(horasPlanificadas.toFixed(2)),
                horasEjecutadas: parseFloat(horasEjecutadas.toFixed(2)),
                cumplimiento,
                participacion,
                icp: icp !== null ? parseFloat(icp.toFixed(4)) : null,
                calificacion,
                tendencia,
                sinPlanificacion,
                lci: ICP_LCI * 100,
                meta: ICP_META * 100,
                lcs: ICP_LCS * 100
            };
        });

        return res.status(200).send({ data: mesesData });

    } catch (e) {
        console.log('Error matrizCapacitacion:', e);
        return res.status(500).send({ message: e.message });
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// 2. MATRIZ DE AUSENTISMO
// POST /api/matrices-ausentismo
// Body: { anio: 2025, palabra: '', estado: null }
// Respuesta: { data: { empleados: [...], resumenMensual: [...] } }
// ─────────────────────────────────────────────────────────────────────────────

// LCI/META/LCS del indicador (según Excel de referencia)
const IAL_LCI  = 0.01; // 1%
const IAL_META = 0.03; // 3%
const IAL_LCS  = 0.04; // 4%

function calcularDiasLaborables(anio, mes) {
    let count = 0;
    const diasEnMes = new Date(anio, mes, 0).getDate();
    for (let d = 1; d <= diasEnMes; d++) {
        const dow = new Date(anio, mes - 1, d).getDay();
        if (dow !== 0 && dow !== 6) count++;
    }
    return count;
}

async function matrizAusentismo(req, res) {
    try {
        const buscar  = req.body.palabra || '';
        const anio    = req.body.anio    || new Date().getFullYear();
        const estado  = req.body.estado;

        let estadoCondicion;
        if (estado !== null && estado !== undefined) {
            estadoCondicion = { ESTADO: estado };
        } else {
            // Para la matriz TTHH incluir empleados activos e inactivos
            estadoCondicion = { ESTADO: { [Op.gte]: 0 } };
        }

        const fechaInicio = `${anio}-01-01`;
        const fechaFin    = `${anio}-12-31`;

        // Contar empleados activos para calcular horas laborables
        const totalActivos = await Usuario.count({ where: { ESTADO: 1 } });

        const usuarios = await Usuario.findAll({
            where: {
                [Op.and]: [
                    estadoCondicion,
                    buscar ? {
                        [Op.or]: [
                            { NOMBRES:        { [Op.like]: `%${buscar}%` } },
                            { APELLIDOS:      { [Op.like]: `%${buscar}%` } },
                            { IDENTIFICACION: { [Op.like]: `%${buscar}%` } }
                        ]
                    } : {}
                ]
            },
            include: [
                {
                    model: Permiso,
                    as: 'PERMISO',
                    required: true,
                    where: {
                        FECHA_INICIO_PERMISO: { [Op.between]: [fechaInicio, fechaFin] },
                        CARGO_VACACIONES: { [Op.ne]: 1 }  // solo excluir cargos a vacaciones
                    },
                    include: [{ model: Tipo_Permiso, as: 'TIPO_PERMISO', required: false }]
                },
                {
                    model: Historial_Empresa,
                    as: 'HISTORIAL_EMPRESA',
                    required: false,
                    include: [{ model: Cargo, required: false, include: [{ model: Area, required: false }] }]
                }
            ],
            order: [['APELLIDOS', 'ASC'], ['NOMBRES', 'ASC']]
        });

        // Inicializar acumulados por mes para el resumen
        const acumMes = {};
        for (let m = 1; m <= 12; m++) {
            acumMes[m] = { diasAcum: 0, horasAcum: 0 };
        }

        // ── Construir filas por empleado ──────────────────────────────────────
        const empleados = usuarios.map(u => {
            const historialActivo = obtenerHistorialActivo(u.HISTORIAL_EMPRESA);
            const cargo = historialActivo && historialActivo.CARGO ? historialActivo.CARGO.DESCRIPCION : '';
            const area  = historialActivo && historialActivo.CARGO && historialActivo.CARGO.AREA
                ? historialActivo.CARGO.AREA.DESCRIPCION : '';

            // Inicializar meses con nombre como clave
            const meses = {};
            NOMBRES_MESES.forEach(nombre => {
                meses[nombre] = { dias: 0, horas: 0, motivo: '-', motivoHoras: '-', permisoIds: [] };
            });

            (u.PERMISO || []).forEach(p => {
                if (!p.FECHA_INICIO_PERMISO) return;
                const mesIdx = new Date(p.FECHA_INICIO_PERMISO).getMonth();
                const mesKey = NOMBRES_MESES[mesIdx];
                const mesNum = mesIdx + 1;
                if (!meses[mesKey]) return;

                const tiempo = parseFloat(p.TIEMPO_SOLICITADO_HORAS || 0);
                // Separar días vs horas según TIEMPO_SOLICITADO_HORAS
                // >= 8h → se trata como días completos; < 8h → horas parciales
                const dias  = tiempo >= 8 ? parseFloat((tiempo / 8).toFixed(2)) : 0;
                const horas = tiempo >  0 && tiempo < 8 ? tiempo : 0;
                const motivo = p.TIPO_PERMISO ? (p.TIPO_PERMISO.DETALLE_TIPO_PERMISO || '-') : '-';

                meses[mesKey].dias  += dias;
                meses[mesKey].horas += horas;
                if (p.ID_PERMISO) meses[mesKey].permisoIds.push(p.ID_PERMISO);
                if (dias  > 0 && meses[mesKey].motivo     === '-') meses[mesKey].motivo     = motivo;
                if (horas > 0 && meses[mesKey].motivoHoras === '-') meses[mesKey].motivoHoras = motivo;

                // Acumular para el resumen mensual
                acumMes[mesNum].diasAcum  += dias;
                acumMes[mesNum].horasAcum += horas;
            });

            return {
                cedula:  u.IDENTIFICACION,
                nombres: `${u.NOMBRES} ${u.APELLIDOS}`,
                area,
                cargo,
                estado:  u.ESTADO,
                ...meses
            };
        });

        // ── Resumen mensual con cálculo de IAL ───────────────────────────────
        const calificaciones = [];
        const resumenMensual = NOMBRES_MESES.map((_, idx) => {
            const m = idx + 1;
            const diasLaborables   = calcularDiasLaborables(anio, m);
            const horasLaborables  = diasLaborables * 8 * totalActivos;
            const horasPerdidas    = parseFloat(
                ((acumMes[m].diasAcum * 8) + acumMes[m].horasAcum).toFixed(2)
            );
            const ial = horasLaborables > 0
                ? parseFloat((horasPerdidas / horasLaborables).toFixed(6))
                : 0;
            const ialPct = parseFloat((ial * 100).toFixed(4));
            const calificacion = horasLaborables > 0
                ? parseFloat(Math.max(0, Math.min(10, 10 * (ial - IAL_LCI) / (IAL_LCS - IAL_LCI))).toFixed(2))
                : 0;
            calificaciones.push(calificacion);
            const promCalif = calificaciones.reduce((a, b) => a + b, 0) / calificaciones.length;
            const tendencia = calificacion > promCalif ? '+' : calificacion < promCalif ? '-' : '=';

            return {
                mes:            m,
                periodo:        LABELS_MESES[idx],
                diasLaborables,
                horasLaborables,
                horasPerdidas,
                ial,
                ialPct,
                calificacion,
                tendencia,
                lci:  IAL_LCI  * 100,
                meta: IAL_META * 100,
                lcs:  IAL_LCS  * 100
            };
        });

        return res.status(200).send({ data: { empleados, resumenMensual } });

    } catch (e) {
        console.log('Error matrizAusentismo:', e);
        return res.status(500).send({ message: e.message });
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// 3. MATRIZ DE ROTACIÓN
// POST /api/matrices-rotacion
// Body: { anio: 2025 }
// Respuesta: array de 12 filas mensuales con nEmpleadosInicio, nTotal,
//            nEmpleadosFinal, promedio, nSalidas, indiceRotacion, periodo, trimestre,
//            calificacion, tendencia, lci, meta, lcs
// IRP = nSalidas / (nTotal + nFinal/2) * 100  (fórmula Excel DETALLE ROTACIÓN)
// LCI=1%, META=2%, LCS=3% (por despido intempestivo)
// ─────────────────────────────────────────────────────────────────────────────
const IRP_LCI  = 0.01;  // 1%
const IRP_META = 0.02;  // 2%
const IRP_LCS  = 0.03;  // 3%

// ── Clasificación de motivos de salida (ajustar según datos reales de la empresa) ──
const MOTIVOS_VOLUNTARIOS = [
    'renuncia', 'renuncia voluntaria', 'jubilacion', 'jubilación',
    'mutuo acuerdo', 'terminación de mutuo acuerdo', 'terminacion de mutuo acuerdo',
    'retiro voluntario'
];
const MOTIVOS_INVOLUNTARIOS = [
    'despido', 'despido intempestivo', 'visto bueno', 'visto bueno empleador',
    'fin de contrato', 'termino de contrato', 'término de contrato',
    'periodo de prueba', 'período de prueba', 'reduccion de personal',
    'reducción de personal', 'liquidacion', 'liquidación', 'cierre'
];

function clasificarMotivo(motivo) {
    if (!motivo) return 'sin_clasificar';
    const m = motivo.toLowerCase().trim();
    if (MOTIVOS_VOLUNTARIOS.some(v => m.includes(v))) return 'voluntaria';
    if (MOTIVOS_INVOLUNTARIOS.some(v => m.includes(v))) return 'involuntaria';
    return 'sin_clasificar';
}

// Usa TIPO_SALIDA si está disponible; si no, clasifica por MOTIVO_SALIDA
function clasificarHistorial(historial) {
    if (historial.TIPO_SALIDA === 'Voluntaria')   return 'voluntaria';
    if (historial.TIPO_SALIDA === 'Involuntaria') return 'involuntaria';
    return clasificarMotivo(historial.MOTIVO_SALIDA);
}

async function matrizRotacion(req, res) {
    try {
        const anio = req.body.anio || new Date().getFullYear();
        if (anio < 2000 || anio > 2100) {
            return res.status(400).send({ message: 'Año fuera de rango permitido (2000-2100)' });
        }
        const fechaInicio = `${anio}-01-01`;
        const fechaFin = `${anio}-12-31`;

        // Historiales con movimiento en el año
        const historiales = await Historial_Empresa.findAll({
            where: {
                [Op.or]: [
                    { FECHA_INGRESO: { [Op.between]: [fechaInicio, fechaFin] } },
                    { FECHA_SALIDA: { [Op.between]: [fechaInicio, fechaFin] } }
                ]
            },
            include: [
                { model: Usuario, as: 'USUARIO', required: false },
                {
                    model: Cargo,
                    required: false,
                    include: [{ model: Area, required: false }]
                }
            ],
            order: [['FECHA_INGRESO', 'ASC']]
        });

        // Empleados activos al comienzo del año
        const activosInicioAnio = await Historial_Empresa.count({
            where: {
                FECHA_INGRESO: { [Op.lt]: fechaInicio },
                [Op.or]: [
                    { FECHA_SALIDA: null },
                    { FECHA_SALIDA: '' },
                    { FECHA_SALIDA: { [Op.gte]: fechaInicio } }
                ]
            }
        });

        const meses = [];
        const calificacionesRot = [];
        let empleadosInicioAcumulado = activosInicioAnio;

        // Acumulados anuales
        let acumSalidas = 0;
        let acumIngresos = 0;
        let acumSalidasVoluntarias = 0;
        let acumSalidasInvoluntarias = 0;
        let sumPromedios = 0;
        let mesesConPromedio = 0;

        for (let m = 1; m <= 12; m++) {
            const mesPad = String(m).padStart(2, '0');
            const inicioMes = `${anio}-${mesPad}-01`;
            const ultimoDia = new Date(anio, m, 0).getDate();
            const finMes = `${anio}-${mesPad}-${String(ultimoDia).padStart(2, '0')}`;

            const entradas = historiales.filter(h =>
                h.FECHA_INGRESO && h.FECHA_INGRESO >= inicioMes && h.FECHA_INGRESO <= finMes
            );
            const salidas = historiales.filter(h =>
                h.FECHA_SALIDA && h.FECHA_SALIDA >= inicioMes && h.FECHA_SALIDA <= finMes
            );

            const numEntradas = entradas.length;
            const numSalidas  = salidas.length;
            const nEmpleadosFinal = empleadosInicioAcumulado + numEntradas - numSalidas;

            // FÓRMULA OFICIAL: promedio = (inicio + final) / 2
            const promedio = (empleadosInicioAcumulado + nEmpleadosFinal) / 2;
            const advertencia = promedio === 0;

            const indiceRotacion = promedio > 0
                ? parseFloat(((numSalidas / promedio) * 100).toFixed(4))
                : 0;

            // Calificación: clamp(10*(IRP - LCI)/(LCS - LCI), 0, 10)
            const irpDecimal = indiceRotacion / 100;
            const calificacion = promedio > 0
                ? parseFloat(Math.max(0, Math.min(10, 10 * (irpDecimal - IRP_LCI) / (IRP_LCS - IRP_LCI))).toFixed(2))
                : 0;

            calificacionesRot.push(calificacion);
            const promCalif = calificacionesRot.reduce((a, b) => a + b, 0) / calificacionesRot.length;
            const tendencia = calificacion > promCalif ? '+' : calificacion < promCalif ? '-' : '=';

            // Clasificación voluntaria/involuntaria (usa TIPO_SALIDA directo o fallback por motivo)
            let nVoluntarias = 0;
            let nInvoluntarias = 0;
            let nSinClasificar = 0;
            salidas.forEach(h => {
                const tipo = clasificarHistorial(h);
                if (tipo === 'voluntaria')   nVoluntarias++;
                else if (tipo === 'involuntaria') nInvoluntarias++;
                else nSinClasificar++;
            });

            // Motivo más frecuente y área más afectada
            const motivosSalida = salidas.map(h => h.MOTIVO_SALIDA).filter(Boolean);
            const motivoSalida = motivosSalida.length > 0 ? motivosSalida[0] : '-';
            const areas = salidas
                .map(h => h.CARGO && h.CARGO.AREA ? h.CARGO.AREA.DESCRIPCION : '')
                .filter(Boolean);
            const areaAfectada = areas.length > 0 ? areas[0] : '-';

            // Detalle de salidas del mes
            const detalleSalidas = salidas.map(h => ({
                nombres: h.USUARIO ? `${h.USUARIO.NOMBRES} ${h.USUARIO.APELLIDOS}` : '-',
                area: h.CARGO && h.CARGO.AREA ? h.CARGO.AREA.DESCRIPCION : '-',
                cargo: h.CARGO ? h.CARGO.DESCRIPCION : '-',
                motivoSalida: h.MOTIVO_SALIDA || '-',
                tipoSalida: clasificarHistorial(h),
                tipoSalidaOriginal: h.TIPO_SALIDA || null,
                fechaSalida: h.FECHA_SALIDA || '-'
            }));

            // Acumular anuales
            acumSalidas  += numSalidas;
            acumIngresos += numEntradas;
            acumSalidasVoluntarias   += nVoluntarias;
            acumSalidasInvoluntarias += nInvoluntarias;
            if (promedio > 0) { sumPromedios += promedio; mesesConPromedio++; }

            meses.push({
                mes: m,
                periodo: LABELS_MESES[m - 1],
                trimestre: TRIMESTRES[m - 1],
                nEmpleadosInicio: empleadosInicioAcumulado,
                nIngresos: numEntradas,
                nSalidas: numSalidas,
                nEmpleadosFinal,
                promedio: parseFloat(promedio.toFixed(2)),
                indiceRotacion,
                calificacion,
                tendencia,
                advertencia,
                nVoluntarias,
                nInvoluntarias,
                nSinClasificar,
                motivoSalida,
                areaAfectada,
                detalleSalidas,
                lci:  IRP_LCI  * 100,
                meta: IRP_META * 100,
                lcs:  IRP_LCS  * 100
            });

            empleadosInicioAcumulado = nEmpleadosFinal;
        }

        // Resumen anual
        const promedioEmpleadosAnual = mesesConPromedio > 0
            ? parseFloat((sumPromedios / mesesConPromedio).toFixed(2))
            : 0;
        const irpAcumulado = promedioEmpleadosAnual > 0
            ? parseFloat(((acumSalidas / promedioEmpleadosAnual) * 100).toFixed(4))
            : 0;

        const resumenAnual = {
            totalSalidas:  acumSalidas,
            totalIngresos: acumIngresos,
            salidasVoluntarias:   acumSalidasVoluntarias,
            salidasInvoluntarias: acumSalidasInvoluntarias,
            promedioEmpleados:    promedioEmpleadosAnual,
            irpAcumulado
        };

        return res.status(200).send({ data: meses, resumenAnual });

    } catch (e) {
        console.log('Error matrizRotacion:', e);
        return res.status(500).send({ message: e.message });
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// 4. MATRIZ DE VACACIONES
// POST /api/matrices-vacaciones
// Body: { empleado: '', area: '', estado: null, fechaConciliacion: '2025-12-31' }
// Respuesta: array de empleados con aniosVacaciones: [{ corte, dias, tomado, pendientes, valor }]
// ─────────────────────────────────────────────────────────────────────────────
async function matrizVacaciones(req, res) {
    try {
        const buscar = req.body.palabra || req.body.empleado || '';
        const areaFiltro = req.body.area || '';
        const estado = req.body.estado;
        const fechaConciliacionStr = req.body.fechaConciliacion || null;
        const fechaConciliacion = fechaConciliacionStr
            ? parseFecha(fechaConciliacionStr)
            : parseFecha(formatFecha(new Date()));

        let estadoCondicion;
        if (estado !== null && estado !== undefined) {
            estadoCondicion = { ESTADO: estado };
        } else {
            estadoCondicion = { [Op.or]: [{ ESTADO: 1 }, { ESTADO: 0 }] };
        }

        const usuarios = await Usuario.findAll({
            where: {
                [Op.and]: [
                    estadoCondicion,
                    buscar ? {
                        [Op.or]: [
                            { NOMBRES: { [Op.like]: `%${buscar}%` } },
                            { APELLIDOS: { [Op.like]: `%${buscar}%` } },
                            { IDENTIFICACION: { [Op.like]: `%${buscar}%` } }
                        ]
                    } : {}
                ]
            },
            include: [
                {
                    model: Historial_Empresa,
                    as: 'HISTORIAL_EMPRESA',
                    required: true,
                    include: [
                        {
                            model: Cargo,
                            required: false,
                            include: [{ model: Area, required: false }]
                        }
                    ]
                },
                {
                    model: Permiso,
                    as: 'PERMISO',
                    required: false,
                    where: { CARGO_VACACIONES: true }
                }
            ],
            order: [['APELLIDOS', 'ASC'], ['NOMBRES', 'ASC']]
        });

        const mapearEmpleado = u => {
            const historialActivo = obtenerHistorialActivo(u.HISTORIAL_EMPRESA);
            const cargoDesc = historialActivo && historialActivo.CARGO
                ? historialActivo.CARGO.DESCRIPCION
                : '';
            const areaDesc = historialActivo && historialActivo.CARGO && historialActivo.CARGO.AREA
                ? historialActivo.CARGO.AREA.DESCRIPCION
                : '';

            // Filtro por área (post-query)
            if (areaFiltro && areaDesc !== areaFiltro) return null;

            const salario   = parseFloat(historialActivo && historialActivo.SALARIO ? historialActivo.SALARIO : 0) || 0;
            const valorDia  = salario > 0 ? parseFloat((salario / 30).toFixed(4)) : null;

            // Primer historial por fecha de ingreso = inicio de relación laboral
            const todosHistoriales = [...(u.HISTORIAL_EMPRESA || [])];
            todosHistoriales.sort((a, b) => new Date(a.FECHA_INGRESO) - new Date(b.FECHA_INGRESO));
            const primerHistorial = todosHistoriales[0];

            if (!primerHistorial) {
                return {
                    cedula: u.IDENTIFICACION,
                    nombres: `${u.NOMBRES} ${u.APELLIDOS}`,
                    area: areaDesc,
                    cargo: cargoDesc,
                    estado: u.ESTADO ? 'ACTIVO' : 'INACTIVO',
                    fechaIngreso: null,
                    aniosVacaciones: [],
                    totalDiasCausados: 0, totalDiasTomados: 0,
                    totalDiasPendientes: 0, totalValor: null
                };
            }

            const fechaIngreso = parseFecha(primerHistorial.FECHA_INGRESO);

            const permisosVacaciones = u.PERMISO || [];
            const aniosVacaciones = [];
            let anioN = 1;
            let totalDiasCausados = 0;
            let totalDiasTomados = 0;

            while (true) {
                const inicioPeriodo = new Date(fechaIngreso);
                inicioPeriodo.setFullYear(inicioPeriodo.getFullYear() + (anioN - 1));

                const finPeriodoTeorico = new Date(fechaIngreso);
                finPeriodoTeorico.setFullYear(finPeriodoTeorico.getFullYear() + anioN);

                const fechaCorte = finPeriodoTeorico <= fechaConciliacion
                    ? finPeriodoTeorico
                    : new Date(fechaConciliacion);

                if (inicioPeriodo >= fechaConciliacion) break;

                const diffMs = fechaCorte.getTime() - inicioPeriodo.getTime();
                const diffDias = diffMs / (1000 * 60 * 60 * 24);

                // Días causados según ley laboral ecuatoriana:
                // Años 1-5: 15 días; año N>=6: 15 + (N-5) días adicionales (máx 30)
                const diasBase = anioN <= 5 ? 15 : Math.min(30, 15 + (anioN - 5));
                const dias = Math.round(diffDias) >= 365
                    ? diasBase
                    : parseFloat(((diffDias / 365) * diasBase).toFixed(4));

                const inicioStr = formatFecha(inicioPeriodo);
                const corteStr  = formatFecha(fechaCorte);

                // Días tomados: permisos con CARGO_VACACIONES dentro del período
                let tomado = 0;
                permisosVacaciones.forEach(p => {
                    const fechaPerm = p.FECHA_INICIO_PERMISO;
                    if (fechaPerm && fechaPerm >= inicioStr && fechaPerm <= corteStr) {
                        tomado += p.DIAS_CARGO_VACACIONES || 0;
                    }
                });

                const pendientes = parseFloat((dias - tomado).toFixed(4));
                const valor = valorDia !== null
                    ? parseFloat((pendientes * valorDia).toFixed(2))
                    : null;

                aniosVacaciones.push({ anio: anioN, corte: corteStr, dias, tomado, pendientes, valor });

                totalDiasCausados += dias;
                totalDiasTomados += tomado;

                if (fechaCorte >= fechaConciliacion) break;
                anioN++;
                if (anioN > 50) break;
            }

            const totalDiasPendientes = parseFloat((totalDiasCausados - totalDiasTomados).toFixed(4));
            const totalValor = valorDia !== null
                ? parseFloat((totalDiasPendientes * valorDia).toFixed(2))
                : null;

            return {
                cedula: u.IDENTIFICACION,
                nombres: `${u.NOMBRES} ${u.APELLIDOS}`,
                area: areaDesc,
                cargo: cargoDesc,
                estado: u.ESTADO ? 'ACTIVO' : 'INACTIVO',
                fechaIngreso: primerHistorial.FECHA_INGRESO,
                aniosVacaciones,
                totalDiasCausados: parseFloat(totalDiasCausados.toFixed(4)),
                totalDiasTomados,
                totalDiasPendientes,
                totalValor
            };
        };

        const todos = usuarios.map(mapearEmpleado).filter(Boolean);
        const activos   = todos.filter(e => e.estado === 'ACTIVO');
        const inactivos = todos.filter(e => e.estado === 'INACTIVO');

        return res.status(200).send({ data: { activos, inactivos } });

    } catch (e) {
        console.log('Error matrizVacaciones:', e);
        return res.status(500).send({ message: e.message });
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Exports
// ─────────────────────────────────────────────────────────────────────────────
module.exports = {
    matrizCapacitacion,
    matrizAusentismo,
    matrizRotacion,
    matrizVacaciones
};
