<?php
include_once('nomina_bcv_service.php');
if (!function_exists('h')) {
    function h($s) {
        return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
    }
}

if (!function_exists('nomina_normalize_decimal')) {
    function nomina_normalize_decimal($value, $default = 0.0) {
        if ($value === null || $value === '') {
            return (float)$default;
        }
        if (is_string($value)) {
            $value = str_replace([' ', ','], ['', '.'], trim($value));
        }
        return is_numeric($value) ? (float)$value : (float)$default;
    }
}

if (!function_exists('nomina_date_to_iso')) {
    function nomina_date_to_iso($value) {
        $value = trim((string)$value);
        if ($value === '') {
            return '';
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return $value;
        }
        if (preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $value, $m)) {
            return $m[3] . '-' . $m[2] . '-' . $m[1];
        }
        return '';
    }
}

if (!function_exists('nomina_table_has_column')) {
    function nomina_table_has_column($conexion, $table, $column) {
        $table = preg_replace('/[^a-zA-Z0-9_]/', '', (string)$table);
        $column = preg_replace('/[^a-zA-Z0-9_]/', '', (string)$column);
        if ($table === '' || $column === '') {
            return false;
        }
        $sql = "SHOW COLUMNS FROM `{$table}` LIKE '{$column}'";
        $res = mysqli_query($conexion, $sql);
        return $res && mysqli_num_rows($res) > 0;
    }
}


if (!function_exists('nomina_ensure_cargos_schema')) {
    function nomina_ensure_cargos_schema($conexion) {
        if (!nomina_table_has_column($conexion, 'cargos', 'moneda_salario')) {
            @mysqli_query($conexion, "ALTER TABLE cargos ADD COLUMN moneda_salario VARCHAR(8) NOT NULL DEFAULT 'VES' AFTER salario");
        }
        return nomina_table_has_column($conexion, 'cargos', 'moneda_salario');
    }
}


if (!function_exists('nomina_ensure_novedades_horas_schema')) {
    function nomina_ensure_novedades_horas_schema($conexion) {
        $changes = [];
        $errors = [];

        $afterRetardo = nomina_table_has_column($conexion, 'novedades_periodo', 'horas_retardo_periodo') ? ' AFTER horas_retardo_periodo' : '';
        if (!nomina_table_has_column($conexion, 'novedades_periodo', 'he_diurna')) {
            $sql = "ALTER TABLE novedades_periodo ADD COLUMN he_diurna DECIMAL(10,2) NOT NULL DEFAULT 0.00" . $afterRetardo;
            if (@mysqli_query($conexion, $sql)) { $changes[] = 'he_diurna'; }
            else { $errors[] = 'No se pudo crear la columna he_diurna en novedades_periodo: ' . mysqli_error($conexion); }
        }
        if (!nomina_table_has_column($conexion, 'novedades_periodo', 'he_nocturna')) {
            $after = nomina_table_has_column($conexion, 'novedades_periodo', 'he_diurna') ? ' AFTER he_diurna' : '';
            $sql = "ALTER TABLE novedades_periodo ADD COLUMN he_nocturna DECIMAL(10,2) NOT NULL DEFAULT 0.00" . $after;
            if (@mysqli_query($conexion, $sql)) { $changes[] = 'he_nocturna'; }
            else { $errors[] = 'No se pudo crear la columna he_nocturna en novedades_periodo: ' . mysqli_error($conexion); }
        }
        if (!nomina_table_has_column($conexion, 'novedades_periodo', 'horas_nocturnas_ordinarias')) {
            $after = nomina_table_has_column($conexion, 'novedades_periodo', 'he_nocturna') ? ' AFTER he_nocturna' : '';
            $sql = "ALTER TABLE novedades_periodo ADD COLUMN horas_nocturnas_ordinarias DECIMAL(10,2) NOT NULL DEFAULT 0.00" . $after;
            if (@mysqli_query($conexion, $sql)) { $changes[] = 'horas_nocturnas_ordinarias'; }
            else { $errors[] = 'No se pudo crear la columna horas_nocturnas_ordinarias en novedades_periodo: ' . mysqli_error($conexion); }
        }
        if (!nomina_table_has_column($conexion, 'novedades_periodo', 'observaciones')) {
            $after = nomina_table_has_column($conexion, 'novedades_periodo', 'pago_vacaciones_bs') ? ' AFTER pago_vacaciones_bs' : '';
            $sql = "ALTER TABLE novedades_periodo ADD COLUMN observaciones TEXT NULL" . $after;
            if (@mysqli_query($conexion, $sql)) { $changes[] = 'observaciones'; }
            else { $errors[] = 'No se pudo crear la columna observaciones en novedades_periodo: ' . mysqli_error($conexion); }
        }
        return ['changes' => $changes, 'errors' => $errors];
    }
}


if (!function_exists('nomina_detect_novedades_schema')) {
    function nomina_detect_novedades_schema($conexion) {
        $map = [
            'periodo_id' => 'periodo_id',
            'persona_id' => 'persona_id',
            'dias_ausencia_periodo' => null,
            'horas_retardo_periodo' => null,
            'he_diurna' => null,
            'he_nocturna' => null,
            'horas_nocturnas_ordinarias' => null,
            'dias_ausencia_mes' => null,
            'bono_manual_bs' => null,
            'otros_descuentos_bs' => null,
            'prestamo_bs' => null,
            'pago_vacaciones_bs' => null,
            'observaciones' => null,
            'schema_name' => 'desconocido',
        ];

        $hasNew = nomina_table_has_column($conexion, 'novedades_periodo', 'dias_ausencia_no_remunerada_periodo');
        if ($hasNew) {
            $map['dias_ausencia_periodo'] = 'dias_ausencia_no_remunerada_periodo';
            $map['horas_retardo_periodo'] = 'horas_retardo_periodo';
            $map['he_diurna'] = nomina_table_has_column($conexion, 'novedades_periodo', 'he_diurna') ? 'he_diurna' : null;
            $map['he_nocturna'] = nomina_table_has_column($conexion, 'novedades_periodo', 'he_nocturna') ? 'he_nocturna' : null;
            $map['horas_nocturnas_ordinarias'] = nomina_table_has_column($conexion, 'novedades_periodo', 'horas_nocturnas_ordinarias') ? 'horas_nocturnas_ordinarias' : null;
            $map['dias_ausencia_mes'] = nomina_table_has_column($conexion, 'novedades_periodo', 'dias_ausencia_no_remunerada_mes') ? 'dias_ausencia_no_remunerada_mes' : null;
            $map['bono_manual_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'bono_extra_manual_bs') ? 'bono_extra_manual_bs' : null;
            $map['otros_descuentos_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'otros_descuentos_bs') ? 'otros_descuentos_bs' : null;
            $map['prestamo_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'prestamo_bs') ? 'prestamo_bs' : null;
            $map['pago_vacaciones_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'pago_vacaciones_bs') ? 'pago_vacaciones_bs' : null;
            $map['observaciones'] = nomina_table_has_column($conexion, 'novedades_periodo', 'observaciones') ? 'observaciones' : null;
            $map['schema_name'] = 'extendido';
            return $map;
        }

        $map['dias_ausencia_periodo'] = nomina_table_has_column($conexion, 'novedades_periodo', 'dias_ausencia_no_remunerada') ? 'dias_ausencia_no_remunerada' : null;
        $map['horas_retardo_periodo'] = nomina_table_has_column($conexion, 'novedades_periodo', 'horas_retardo') ? 'horas_retardo' : null;
        $map['he_diurna'] = nomina_table_has_column($conexion, 'novedades_periodo', 'he_diurna') ? 'he_diurna' : null;
        $map['he_nocturna'] = nomina_table_has_column($conexion, 'novedades_periodo', 'he_nocturna') ? 'he_nocturna' : null;
        $map['horas_nocturnas_ordinarias'] = nomina_table_has_column($conexion, 'novedades_periodo', 'horas_nocturnas_ordinarias') ? 'horas_nocturnas_ordinarias' : null;
        $map['bono_manual_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'bono_extra_manual_bs') ? 'bono_extra_manual_bs' : null;
        $map['otros_descuentos_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'otros_descuentos_bs') ? 'otros_descuentos_bs' : null;
        $map['prestamo_bs'] = nomina_table_has_column($conexion, 'novedades_periodo', 'prestamo_bs') ? 'prestamo_bs' : null;
        $map['observaciones'] = nomina_table_has_column($conexion, 'novedades_periodo', 'observaciones') ? 'observaciones' : null;
        $map['schema_name'] = 'base';
        return $map;
    }
}


if (!function_exists('nomina_ensure_manual_novedades_schema')) {
    function nomina_ensure_manual_novedades_schema($conexion) {
        $sql = "CREATE TABLE IF NOT EXISTS novedades_periodo_conceptos_manual (
            id INT AUTO_INCREMENT PRIMARY KEY,
            periodo_id INT NOT NULL,
            persona_id INT NOT NULL,
            concepto_id INT NOT NULL,
            valor_manual DECIMAL(18,4) NOT NULL DEFAULT 0,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_periodo_persona_concepto (periodo_id, persona_id, concepto_id),
            KEY idx_periodo_persona (periodo_id, persona_id),
            KEY idx_concepto (concepto_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
        return @mysqli_query($conexion, $sql) !== false || mysqli_errno($conexion) === 0;
    }
}

if (!function_exists('nomina_get_manual_fixed_concepts')) {
    function nomina_get_manual_fixed_concepts($conexion, $onlyActive = true) {
        $where = ["modo_calculo = 'FIJO'", "aplica_global = 0", "tipo IN ('ASIG','DED')"];
        if ($onlyActive) {
            $where[] = 'activo = 1';
        }
        $sql = 'SELECT * FROM conceptos_nomina WHERE ' . implode(' AND ', $where) . ' ORDER BY codigo ASC';
        $rows = [];
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                if (nomina_es_concepto_base($row['codigo'] ?? '')) {
                    continue;
                }
                $rows[] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('nomina_fetch_manual_concept_assignments')) {
    function nomina_fetch_manual_concept_assignments($conexion, $periodoId) {
        $periodoId = (int)$periodoId;
        nomina_ensure_manual_novedades_schema($conexion);
        $rows = [];
        $sql = "SELECT * FROM novedades_periodo_conceptos_manual WHERE periodo_id = {$periodoId}";
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $pid = (int)$row['persona_id'];
                $cid = (int)$row['concepto_id'];
                if (!isset($rows[$pid])) {
                    $rows[$pid] = [];
                }
                $rows[$pid][$cid] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('nomina_fetch_novedades_by_period')) {
    function nomina_fetch_novedades_by_period($conexion, $periodoId, array $schema) {
        $periodoId = (int)$periodoId;
        $aliases = [
            'periodo_id' => 'periodo_id',
            'persona_id' => 'persona_id',
            'dias_ausencia_periodo' => '0',
            'horas_retardo_periodo' => '0',
            'he_diurna' => '0',
            'he_nocturna' => '0',
            'horas_nocturnas_ordinarias' => '0',
            'dias_ausencia_mes' => 'NULL',
            'bono_manual_bs' => '0',
            'otros_descuentos_bs' => '0',
            'prestamo_bs' => '0',
            'pago_vacaciones_bs' => '0',
            'observaciones' => "''",
        ];
        foreach ($aliases as $logical => $fallback) {
            if (isset($schema[$logical]) && $schema[$logical]) {
                $aliases[$logical] = '`' . $schema[$logical] . '`';
            }
        }
        $sql = "SELECT id,
                    {$aliases['periodo_id']} AS periodo_id,
                    {$aliases['persona_id']} AS persona_id,
                    {$aliases['dias_ausencia_periodo']} AS dias_ausencia_periodo,
                    {$aliases['horas_retardo_periodo']} AS horas_retardo_periodo,
                    {$aliases['he_diurna']} AS he_diurna,
                    {$aliases['he_nocturna']} AS he_nocturna,
                    {$aliases['horas_nocturnas_ordinarias']} AS horas_nocturnas_ordinarias,
                    {$aliases['dias_ausencia_mes']} AS dias_ausencia_mes,
                    {$aliases['bono_manual_bs']} AS bono_manual_bs,
                    {$aliases['otros_descuentos_bs']} AS otros_descuentos_bs,
                    {$aliases['prestamo_bs']} AS prestamo_bs,
                    {$aliases['pago_vacaciones_bs']} AS pago_vacaciones_bs,
                    {$aliases['observaciones']} AS observaciones
                FROM novedades_periodo
                WHERE periodo_id = {$periodoId}";
        $rows = [];
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $rows[(int)$row['persona_id']] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('nomina_flash_set')) {
    function nomina_flash_set($type, $message) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            return;
        }
        $_SESSION['nomina_flash'] = ['type' => $type, 'message' => $message];
    }
}

if (!function_exists('nomina_flash_get')) {
    function nomina_flash_get() {
        if (session_status() !== PHP_SESSION_ACTIVE || !isset($_SESSION['nomina_flash'])) {
            return null;
        }
        $flash = $_SESSION['nomina_flash'];
        unset($_SESSION['nomina_flash']);
        return $flash;
    }
}

if (!function_exists('nomina_should_mark_ultima_quincena')) {
    function nomina_should_mark_ultima_quincena($tipo, $fechaDesde, $fechaHasta) {
        if (strtoupper((string)$tipo) !== 'QUINCENAL') {
            return false;
        }
        $desde = nomina_date_to_iso($fechaDesde);
        $hasta = nomina_date_to_iso($fechaHasta);
        if ($desde === '' || $hasta === '') {
            return false;
        }
        try {
            $d = new DateTime($desde);
            $h = new DateTime($hasta);
        } catch (Exception $e) {
            return false;
        }
        return (int)$d->format('d') >= 16 && (int)$h->format('d') >= 28 && $d->format('Y-m') === $h->format('Y-m');
    }
}

if (!function_exists('nomina_periodo_has_overlap')) {
    function nomina_periodo_has_overlap($conexion, $tipo, $fechaDesde, $fechaHasta, $excludeId = 0) {
        $tipo = mysqli_real_escape_string($conexion, strtoupper((string)$tipo));
        $fechaDesde = mysqli_real_escape_string($conexion, nomina_date_to_iso($fechaDesde));
        $fechaHasta = mysqli_real_escape_string($conexion, nomina_date_to_iso($fechaHasta));
        $excludeId = (int)$excludeId;
        if ($fechaDesde === '' || $fechaHasta === '') {
            return false;
        }
        $sql = "SELECT id, codigo FROM periodos_nomina
                WHERE tipo = '{$tipo}'
                  AND id <> {$excludeId}
                  AND NOT (fecha_hasta < '{$fechaDesde}' OR fecha_desde > '{$fechaHasta}')
                LIMIT 1";
        $res = mysqli_query($conexion, $sql);
        return $res && mysqli_num_rows($res) > 0 ? mysqli_fetch_assoc($res) : false;
    }
}

if (!function_exists('nomina_get_periodos_with_stats')) {
    function nomina_get_periodos_with_stats($conexion) {
        $sql = "SELECT p.*,
                       COUNT(nc.id) AS total_corridas,
                       MAX(nc.fecha_calculo) AS ultima_corrida,
                       SUM(CASE WHEN nc.estado='PAGADA' THEN 1 ELSE 0 END) AS corridas_pagadas,
                       SUM(CASE WHEN nc.estado IN ('CALCULADA','REVISADA','APROBADA','PAGADA','CERRADA') THEN 1 ELSE 0 END) AS corridas_firmes
                FROM periodos_nomina p
                LEFT JOIN nomina_corridas nc ON nc.periodo_id = p.id
                GROUP BY p.id
                ORDER BY p.fecha_desde DESC, p.id DESC";
        $rows = [];
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $rows[] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('nomina_get_periodo')) {
    function nomina_get_periodo($conexion, $periodoId) {
        $periodoId = (int)$periodoId;
        $sql = "SELECT p.*,
                       COUNT(nc.id) AS total_corridas,
                       MAX(nc.fecha_calculo) AS ultima_corrida,
                       SUM(CASE WHEN nc.estado='PAGADA' THEN 1 ELSE 0 END) AS corridas_pagadas,
                       SUM(CASE WHEN nc.estado IN ('CALCULADA','REVISADA','APROBADA','PAGADA','CERRADA') THEN 1 ELSE 0 END) AS corridas_firmes
                FROM periodos_nomina p
                LEFT JOIN nomina_corridas nc ON nc.periodo_id = p.id
                WHERE p.id = {$periodoId}
                GROUP BY p.id";
        $res = mysqli_query($conexion, $sql);
        return $res && mysqli_num_rows($res) ? mysqli_fetch_assoc($res) : null;
    }
}

if (!function_exists('nomina_periodo_lock_info')) {
    function nomina_periodo_lock_info($conexion, $periodoId) {
        $periodoId = (int)$periodoId;
        $sql = "SELECT COUNT(*) AS total,
                       COALESCE(SUM(CASE WHEN estado='PAGADA' THEN 1 ELSE 0 END),0) AS pagadas,
                       COALESCE(SUM(CASE WHEN estado IN ('CALCULADA','REVISADA','APROBADA','PAGADA','CERRADA') THEN 1 ELSE 0 END),0) AS firmes,
                       COALESCE(SUM(CASE WHEN estado IN ('REVISADA','APROBADA','PAGADA','CERRADA') THEN 1 ELSE 0 END),0) AS bloqueadas
                FROM nomina_corridas
                WHERE periodo_id = {$periodoId}";
        $res = mysqli_query($conexion, $sql);
        $row = $res ? mysqli_fetch_assoc($res) : ['total' => 0, 'pagadas' => 0, 'firmes' => 0, 'bloqueadas' => 0];
        return [
            'total' => (int)($row['total'] ?? 0),
            'pagadas' => (int)($row['pagadas'] ?? 0),
            'firmes' => (int)($row['firmes'] ?? 0),
            'bloqueadas' => (int)($row['bloqueadas'] ?? 0),
        ];
    }
}

if (!function_exists('nomina_fetch_active_people')) {
    function nomina_fetch_active_people($conexion) {
        $rows = [];
        nomina_ensure_cargos_schema($conexion);
        $sql = "SELECT p.id, p.nombre, p.apellido, p.cedula, p.fecha_ingreso, p.fecha_egreso, p.tipo_contrato, p.area, p.departamento, p.id_turno, p.id_cargo,
                       p.salario_base, p.moneda_salario, p.estatus_laboral,
                       c.cargo, c.salario AS salario_cargo, COALESCE(c.moneda_salario, 'VES') AS moneda_salario_cargo, c.bono_extra_usd_mensual,
                       t.nombre AS turno_nombre,
                       t.lunes_libre, t.martes_libre, t.miercoles_libre, t.jueves_libre, t.viernes_libre,
                       t.sabado_libre, t.domingo_libre, t.hora_inicio, t.hora_fin
                FROM personas p
                LEFT JOIN cargos c ON p.id_cargo = c.id_cargo
                LEFT JOIN turnos t ON p.id_turno = t.id
                WHERE IFNULL(p.estatus_laboral, 'ACTIVO') = 'ACTIVO'
                ORDER BY p.apellido, p.nombre";
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $rows[] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('nomina_get_effective_salary_bs')) {
    function nomina_get_effective_salary_bs(array $persona, $tasaBcv = 1.0) {
        $salarioBase = $persona['salario_base'] ?? null;
        $salarioCargo = $persona['salario_cargo'] ?? null;
        $usaSalarioPropio = ($salarioBase !== null && $salarioBase !== '');
        $monto = $usaSalarioPropio ? (float)$salarioBase : (($salarioCargo !== null && $salarioCargo !== '') ? (float)$salarioCargo : 0.0);
        $moneda = $usaSalarioPropio
            ? strtoupper((string)($persona['moneda_salario'] ?? 'VES'))
            : strtoupper((string)($persona['moneda_salario_cargo'] ?? 'VES'));
        if ($moneda === 'USD') {
            return $monto * (float)$tasaBcv;
        }
        return $monto;
    }
}


if (!function_exists('nomina_get_effective_salary_info')) {
    function nomina_get_effective_salary_info(array $persona) {
        $salarioBase = $persona['salario_base'] ?? null;
        $salarioCargo = $persona['salario_cargo'] ?? null;
        $usaSalarioPropio = ($salarioBase !== null && $salarioBase !== '');
        return [
            'source' => $usaSalarioPropio ? 'PERSONA' : 'CARGO',
            'amount' => $usaSalarioPropio ? (float)$salarioBase : (($salarioCargo !== null && $salarioCargo !== '') ? (float)$salarioCargo : 0.0),
            'currency' => $usaSalarioPropio ? strtoupper((string)($persona['moneda_salario'] ?? 'VES')) : strtoupper((string)($persona['moneda_salario_cargo'] ?? 'VES')),
        ];
    }
}



if (!function_exists('nomina_ensure_calendario_laboral_schema')) {
    function nomina_ensure_calendario_laboral_schema($conexion) {
        $sql = "CREATE TABLE IF NOT EXISTS nomina_calendario_laboral (
            id INT AUTO_INCREMENT PRIMARY KEY,
            fecha DATE NOT NULL,
            anio SMALLINT NOT NULL,
            nombre VARCHAR(160) NOT NULL,
            tipo VARCHAR(40) NOT NULL DEFAULT 'NACIONAL',
            aplica_nomina TINYINT(1) NOT NULL DEFAULT 1,
            aplica_banco TINYINT(1) NOT NULL DEFAULT 0,
            fuente VARCHAR(180) NULL,
            base_legal VARCHAR(255) NULL,
            observacion TEXT NULL,
            activo TINYINT(1) NOT NULL DEFAULT 1,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_fecha_tipo_nombre (fecha, tipo, nombre),
            KEY idx_fecha_activo (fecha, activo),
            KEY idx_anio (anio),
            KEY idx_aplica_nomina (aplica_nomina),
            KEY idx_aplica_banco (aplica_banco)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
        @mysqli_query($conexion, $sql);

        $sqlSnapshot = "CREATE TABLE IF NOT EXISTS nomina_corrida_calendario_snapshot (
            id INT AUTO_INCREMENT PRIMARY KEY,
            corrida_id INT NOT NULL,
            fecha DATE NOT NULL,
            nombre VARCHAR(160) NOT NULL,
            tipo VARCHAR(40) NOT NULL,
            aplica_nomina TINYINT(1) NOT NULL DEFAULT 1,
            aplica_banco TINYINT(1) NOT NULL DEFAULT 0,
            fuente VARCHAR(180) NULL,
            base_legal VARCHAR(255) NULL,
            es_domingo TINYINT(1) NOT NULL DEFAULT 0,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_corrida_fecha_nombre (corrida_id, fecha, nombre),
            KEY idx_corrida (corrida_id),
            KEY idx_fecha (fecha)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
        @mysqli_query($conexion, $sqlSnapshot);

        return true;
    }
}

if (!function_exists('nomina_calendario_easter_date')) {
    function nomina_calendario_easter_date($year) {
        $year = (int)$year;
        $a = $year % 19;
        $b = intdiv($year, 100);
        $c = $year % 100;
        $d = intdiv($b, 4);
        $e = $b % 4;
        $f = intdiv($b + 8, 25);
        $g = intdiv($b - $f + 1, 3);
        $h = (19 * $a + $b - $d - $g + 15) % 30;
        $i = intdiv($c, 4);
        $k = $c % 4;
        $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7;
        $m = intdiv($a + 11 * $h + 22 * $l, 451);
        $month = intdiv($h + $l - 7 * $m + 114, 31);
        $day = (($h + $l - 7 * $m + 114) % 31) + 1;
        return sprintf('%04d-%02d-%02d', $year, $month, $day);
    }
}

if (!function_exists('nomina_calendario_insert_or_update')) {
    function nomina_calendario_insert_or_update($conexion, array $item) {
        nomina_ensure_calendario_laboral_schema($conexion);
        $fecha = mysqli_real_escape_string($conexion, nomina_date_to_iso($item['fecha'] ?? ''));
        if ($fecha === '') { return false; }
        $anio = (int)substr($fecha, 0, 4);
        $nombre = mysqli_real_escape_string($conexion, trim((string)($item['nombre'] ?? '')));
        $tipo = mysqli_real_escape_string($conexion, strtoupper(trim((string)($item['tipo'] ?? 'NACIONAL'))));
        if ($nombre === '') { return false; }
        $aplicaNomina = !empty($item['aplica_nomina']) ? 1 : 0;
        $aplicaBanco = !empty($item['aplica_banco']) ? 1 : 0;
        $fuente = mysqli_real_escape_string($conexion, (string)($item['fuente'] ?? ''));
        $baseLegal = mysqli_real_escape_string($conexion, (string)($item['base_legal'] ?? ''));
        $observacion = mysqli_real_escape_string($conexion, (string)($item['observacion'] ?? ''));
        $activo = array_key_exists('activo', $item) ? (!empty($item['activo']) ? 1 : 0) : 1;

        $where = "fecha='{$fecha}' AND tipo='{$tipo}' AND nombre='{$nombre}'";
        $res = mysqli_query($conexion, "SELECT id FROM nomina_calendario_laboral WHERE {$where} LIMIT 1");
        if ($res && mysqli_num_rows($res) > 0) {
            $row = mysqli_fetch_assoc($res);
            $id = (int)$row['id'];
            $sql = "UPDATE nomina_calendario_laboral
                    SET anio={$anio}, aplica_nomina={$aplicaNomina}, aplica_banco={$aplicaBanco},
                        fuente=" . ($fuente === '' ? 'NULL' : "'{$fuente}'") . ",
                        base_legal=" . ($baseLegal === '' ? 'NULL' : "'{$baseLegal}'") . ",
                        observacion=" . ($observacion === '' ? 'NULL' : "'{$observacion}'") . ",
                        activo={$activo}
                    WHERE id={$id}";
            return mysqli_query($conexion, $sql);
        }

        $sql = "INSERT INTO nomina_calendario_laboral
                (fecha, anio, nombre, tipo, aplica_nomina, aplica_banco, fuente, base_legal, observacion, activo)
                VALUES ('{$fecha}', {$anio}, '{$nombre}', '{$tipo}', {$aplicaNomina}, {$aplicaBanco}, " .
                    ($fuente === '' ? 'NULL' : "'{$fuente}'") . ", " .
                    ($baseLegal === '' ? 'NULL' : "'{$baseLegal}'") . ", " .
                    ($observacion === '' ? 'NULL' : "'{$observacion}'") . ", {$activo})";
        return mysqli_query($conexion, $sql);
    }
}

if (!function_exists('nomina_seed_calendario_venezuela')) {
    function nomina_seed_calendario_venezuela($conexion, $year = null) {
        nomina_ensure_calendario_laboral_schema($conexion);
        $year = $year ? (int)$year : (int)date('Y');
        if ($year < 2000 || $year > 2100) { return false; }

        $baseLott = 'LOTTT art. 184: días hábiles y feriados';
        $baseFiestas = 'Ley de Fiestas Nacionales / calendario laboral Venezuela';
        $fuenteLaboral = 'Precarga interna RRHH Venezuela. Validar decretos nacionales, estadales y municipales vigentes.';
        $items = [
            ["fecha"=>"{$year}-01-01", "nombre"=>'Año Nuevo', "tipo"=>'NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-04-19", "nombre"=>'Movimiento Precursor de la Independencia', "tipo"=>'FIESTA_NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseFiestas, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-05-01", "nombre"=>'Día del Trabajador', "tipo"=>'NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-06-24", "nombre"=>'Batalla de Carabobo', "tipo"=>'FIESTA_NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseFiestas, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-07-05", "nombre"=>'Día de la Independencia', "tipo"=>'FIESTA_NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseFiestas, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-07-24", "nombre"=>'Natalicio del Libertador', "tipo"=>'FIESTA_NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseFiestas, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-10-12", "nombre"=>'Día de la Resistencia Indígena', "tipo"=>'FIESTA_NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseFiestas, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-12-24", "nombre"=>'Víspera de Navidad', "tipo"=>'NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-12-25", "nombre"=>'Navidad', "tipo"=>'NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral],
            ["fecha"=>"{$year}-12-31", "nombre"=>'Fin de Año', "tipo"=>'NACIONAL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral],
        ];

        $easter = nomina_calendario_easter_date($year);
        $e = new DateTime($easter);
        $carnavalLunes = clone $e; $carnavalLunes->modify('-48 days');
        $carnavalMartes = clone $e; $carnavalMartes->modify('-47 days');
        $juevesSanto = clone $e; $juevesSanto->modify('-3 days');
        $viernesSanto = clone $e; $viernesSanto->modify('-2 days');
        $items[] = ["fecha"=>$carnavalLunes->format('Y-m-d'), "nombre"=>'Lunes de Carnaval', "tipo"=>'RELIGIOSO_MOVIL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral];
        $items[] = ["fecha"=>$carnavalMartes->format('Y-m-d'), "nombre"=>'Martes de Carnaval', "tipo"=>'RELIGIOSO_MOVIL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral];
        $items[] = ["fecha"=>$juevesSanto->format('Y-m-d'), "nombre"=>'Jueves Santo', "tipo"=>'RELIGIOSO_MOVIL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral];
        $items[] = ["fecha"=>$viernesSanto->format('Y-m-d'), "nombre"=>'Viernes Santo', "tipo"=>'RELIGIOSO_MOVIL', "aplica_nomina"=>1, "aplica_banco"=>1, "base_legal"=>$baseLott, "fuente"=>$fuenteLaboral];

        // Referencias bancarias opcionales. No inciden en nómina salvo que RRHH las active.
        $bankOnly = [
            ["{$year}-01-06", 'Día de Reyes'],
            ["{$year}-03-19", 'San José'],
            ["{$year}-06-11", 'Corpus Christi'],
            ["{$year}-06-29", 'San Pedro y San Pablo'],
            ["{$year}-08-15", 'Asunción de Nuestra Señora'],
            ["{$year}-09-11", 'Virgen de Coromoto'],
            ["{$year}-11-02", 'Día de Todos los Santos'],
            ["{$year}-11-18", 'Virgen del Rosario de Chiquinquirá'],
        ];
        foreach ($bankOnly as $b) {
            $items[] = ["fecha"=>$b[0], "nombre"=>$b[1], "tipo"=>'BANCARIO_REFERENCIAL', "aplica_nomina"=>0, "aplica_banco"=>1, "base_legal"=>'Calendario bancario referencial BCV/SUDEBAN. No equivale automáticamente a feriado laboral.', "fuente"=>'BCV/SUDEBAN calendario bancario referencial'];
        }

        foreach ($items as $item) {
            nomina_calendario_insert_or_update($conexion, $item);
        }
        return true;
    }
}

if (!function_exists('nomina_get_calendario_feriados')) {
    function nomina_get_calendario_feriados($conexion, $fechaDesde, $fechaHasta, $onlyNomina = true, $includeSundays = true) {
        nomina_ensure_calendario_laboral_schema($conexion);
        $desde = nomina_date_to_iso($fechaDesde);
        $hasta = nomina_date_to_iso($fechaHasta);
        $map = [];
        if ($desde === '' || $hasta === '') { return $map; }
        $startYear = (int)substr($desde, 0, 4);
        $endYear = (int)substr($hasta, 0, 4);
        for ($y = $startYear; $y <= $endYear; $y++) {
            nomina_seed_calendario_venezuela($conexion, $y);
        }
        $whereNomina = $onlyNomina ? ' AND aplica_nomina = 1' : '';
        $sql = "SELECT * FROM nomina_calendario_laboral
                WHERE activo = 1
                  AND fecha BETWEEN '" . mysqli_real_escape_string($conexion, $desde) . "' AND '" . mysqli_real_escape_string($conexion, $hasta) . "'
                  {$whereNomina}
                ORDER BY fecha ASC, aplica_nomina DESC, nombre ASC";
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $fecha = $row['fecha'];
                if (!isset($map[$fecha])) { $map[$fecha] = []; }
                $map[$fecha][] = $row;
            }
        }
        if ($includeSundays) {
            try {
                $d = new DateTime($desde);
                $h = new DateTime($hasta);
                $end = clone $h; $end->modify('+1 day');
                foreach (new DatePeriod($d, new DateInterval('P1D'), $end) as $day) {
                    if ((int)$day->format('N') === 7) {
                        $fecha = $day->format('Y-m-d');
                        if (!isset($map[$fecha])) { $map[$fecha] = []; }
                        $map[$fecha][] = [
                            'fecha' => $fecha,
                            'nombre' => 'Domingo feriado laboral',
                            'tipo' => 'DOMINGO_LOTTT',
                            'aplica_nomina' => 1,
                            'aplica_banco' => 0,
                            'fuente' => 'LOTTT art. 184',
                            'base_legal' => 'LOTTT art. 184: los domingos son feriados a efectos laborales',
                            'activo' => 1,
                            'es_domingo' => 1,
                        ];
                    }
                }
            } catch (Exception $e) {}
        }
        return $map;
    }
}

if (!function_exists('nomina_attendance_dates')) {
    function nomina_attendance_dates($conexion, $personaId, $fechaDesde, $fechaHasta) {
        $personaId = (int)$personaId;
        $fechaDesde = mysqli_real_escape_string($conexion, nomina_date_to_iso($fechaDesde));
        $fechaHasta = mysqli_real_escape_string($conexion, nomina_date_to_iso($fechaHasta));
        $dates = [];
        if ($fechaDesde === '' || $fechaHasta === '') { return $dates; }
        $sql = "SELECT DISTINCT DATE(fecha_entrada) AS fecha
                FROM asistencia
                WHERE persona_id = {$personaId}
                  AND fecha_entrada IS NOT NULL
                  AND DATE(fecha_entrada) BETWEEN '{$fechaDesde}' AND '{$fechaHasta}'";
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                if (!empty($row['fecha'])) { $dates[$row['fecha']] = true; }
            }
        }
        return $dates;
    }
}

if (!function_exists('nomina_snapshot_calendar_for_corrida')) {
    function nomina_snapshot_calendar_for_corrida($conexion, $corridaId, array $periodo) {
        $corridaId = (int)$corridaId;
        if ($corridaId <= 0) { return false; }
        nomina_ensure_calendario_laboral_schema($conexion);
        $feriados = nomina_get_calendario_feriados($conexion, $periodo['fecha_desde'] ?? '', $periodo['fecha_hasta'] ?? '', true, true);
        foreach ($feriados as $fecha => $items) {
            foreach ($items as $item) {
                $nombre = mysqli_real_escape_string($conexion, (string)($item['nombre'] ?? 'Feriado'));
                $tipo = mysqli_real_escape_string($conexion, strtoupper((string)($item['tipo'] ?? 'NACIONAL')));
                $aplicaNomina = !empty($item['aplica_nomina']) ? 1 : 0;
                $aplicaBanco = !empty($item['aplica_banco']) ? 1 : 0;
                $fuente = mysqli_real_escape_string($conexion, (string)($item['fuente'] ?? ''));
                $baseLegal = mysqli_real_escape_string($conexion, (string)($item['base_legal'] ?? ''));
                $esDomingo = ((string)$tipo === 'DOMINGO_LOTTT' || !empty($item['es_domingo'])) ? 1 : 0;
                $res = mysqli_query($conexion, "SELECT id FROM nomina_corrida_calendario_snapshot WHERE corrida_id={$corridaId} AND fecha='{$fecha}' AND nombre='{$nombre}' LIMIT 1");
                if ($res && mysqli_num_rows($res) > 0) {
                    $row = mysqli_fetch_assoc($res);
                    $id = (int)$row['id'];
                    mysqli_query($conexion, "UPDATE nomina_corrida_calendario_snapshot SET tipo='{$tipo}', aplica_nomina={$aplicaNomina}, aplica_banco={$aplicaBanco}, fuente=" . ($fuente===''?'NULL':"'{$fuente}'") . ", base_legal=" . ($baseLegal===''?'NULL':"'{$baseLegal}'") . ", es_domingo={$esDomingo} WHERE id={$id}");
                } else {
                    mysqli_query($conexion, "INSERT INTO nomina_corrida_calendario_snapshot (corrida_id, fecha, nombre, tipo, aplica_nomina, aplica_banco, fuente, base_legal, es_domingo) VALUES ({$corridaId}, '{$fecha}', '{$nombre}', '{$tipo}', {$aplicaNomina}, {$aplicaBanco}, " . ($fuente===''?'NULL':"'{$fuente}'") . ", " . ($baseLegal===''?'NULL':"'{$baseLegal}'") . ", {$esDomingo})");
                }
            }
        }
        return true;
    }
}

if (!function_exists('nomina_schedule_by_calendar')) {
    function nomina_schedule_by_calendar(array $persona, array $periodo, $conexion = null) {
        $desde = nomina_date_to_iso($periodo['fecha_desde'] ?? '');
        $hasta = nomina_date_to_iso($periodo['fecha_hasta'] ?? '');
        $result = [
            'dias_laborados' => 0,
            'dias_descanso' => 0,
            'dias_feriado' => 0,
            'dias_descanso_trab' => 0,
            'he_diurna' => 0,
            'he_nocturna' => 0,
            'horas_nocturnas' => 0,
            'dias_periodo' => 0,
            'fuente' => 'CALENDARIO',
            'observacion' => '',
            'feriados_detectados' => [],
            'feriados_trabajados' => [],
            'desglose_auditor' => [
                'dias_periodo' => 0,
                'laborables_por_turno' => 0,
                'descansos_por_turno_total' => 0,
                'descansos_por_turno_puros' => 0,
                'domingos_lottt' => 0,
                'feriados_nomina_no_domingo' => 0,
                'feriados_en_dia_laborable' => 0,
                'feriados_en_descanso_turno' => 0,
                'no_laborables_total' => 0,
                'asistencia_en_feriado' => 0,
                'asistencia_en_descanso_turno' => 0,
                'base_calendario_pagada' => 0,
            ],
            'desglose_texto' => '',
        ];
        if ($desde === '' || $hasta === '') {
            return $result;
        }
        try {
            $d = new DateTime($desde);
            $h = new DateTime($hasta);
            $h->setTime(0, 0, 0);
            $end = clone $h;
            $end->modify('+1 day');
            $period = new DatePeriod($d, new DateInterval('P1D'), $end);
        } catch (Exception $e) {
            return $result;
        }

        $mapLibre = [
            1 => 'lunes_libre',
            2 => 'martes_libre',
            3 => 'miercoles_libre',
            4 => 'jueves_libre',
            5 => 'viernes_libre',
            6 => 'sabado_libre',
            7 => 'domingo_libre',
        ];

        $feriados = $conexion ? nomina_get_calendario_feriados($conexion, $desde, $hasta, true, true) : [];
        $attendanceDates = $conexion ? nomina_attendance_dates($conexion, (int)($persona['id'] ?? 0), $desde, $hasta) : [];

        foreach ($period as $day) {
            $result['dias_periodo']++;
            $fecha = $day->format('Y-m-d');
            $weekDay = (int)$day->format('N');
            $field = $mapLibre[$weekDay];
            if (!empty($persona['id_turno'])) {
                $isLibre = isset($persona[$field]) && (int)$persona[$field] === 1;
            } else {
                $isLibre = $weekDay >= 6;
            }

            $holidayItems = $feriados[$fecha] ?? [];
            $isHoliday = !empty($holidayItems);
            $isSunday = ($weekDay === 7);
            $hasSundayHoliday = false;
            $hasNonSundayNominaHoliday = false;
            foreach ($holidayItems as $hi) {
                $tipoHi = strtoupper((string)($hi['tipo'] ?? ''));
                if ($tipoHi === 'DOMINGO_LOTTT' || !empty($hi['es_domingo'])) {
                    $hasSundayHoliday = true;
                } else {
                    $hasNonSundayNominaHoliday = true;
                }
            }

            $result['desglose_auditor']['dias_periodo']++;
            if ($isLibre) {
                $result['desglose_auditor']['descansos_por_turno_total']++;
            } else {
                $result['desglose_auditor']['laborables_por_turno']++;
            }
            if ($isSunday || $hasSundayHoliday) {
                $result['desglose_auditor']['domingos_lottt']++;
            }
            if ($hasNonSundayNominaHoliday) {
                $result['desglose_auditor']['feriados_nomina_no_domingo']++;
                if ($isLibre) {
                    $result['desglose_auditor']['feriados_en_descanso_turno']++;
                } else {
                    $result['desglose_auditor']['feriados_en_dia_laborable']++;
                }
            }
            if ($isLibre && !$isHoliday) {
                $result['desglose_auditor']['descansos_por_turno_puros']++;
            }
            if (($isLibre || $isHoliday)) {
                $result['desglose_auditor']['no_laborables_total']++;
            }

            if ($isHoliday) {
                $names = [];
                foreach ($holidayItems as $hi) { $names[] = (string)($hi['nombre'] ?? 'Feriado'); }
                $result['feriados_detectados'][] = $fecha . ' ' . implode(' / ', array_unique($names));
            }

            if ($isLibre || $isHoliday) {
                $result['dias_descanso']++;
            } else {
                $result['dias_laborados']++;
            }

            if ($isHoliday && isset($attendanceDates[$fecha])) {
                $result['dias_feriado']++;
                $result['desglose_auditor']['asistencia_en_feriado']++;
                $names = [];
                foreach ($holidayItems as $hi) { $names[] = (string)($hi['nombre'] ?? 'Feriado'); }
                $result['feriados_trabajados'][] = $fecha . ' ' . implode(' / ', array_unique($names));
            } elseif ($isLibre && isset($attendanceDates[$fecha])) {
                $result['desglose_auditor']['asistencia_en_descanso_turno']++;
            }
        }

        $baseObs = !empty($persona['id_turno'])
            ? 'Precargado según turno asignado.'
            : 'Precargado con esquema general lunes a viernes.';
        if (!empty($result['feriados_detectados'])) {
            $baseObs .= ' Feriados detectados: ' . implode('; ', array_slice($result['feriados_detectados'], 0, 4));
            if (count($result['feriados_detectados']) > 4) { $baseObs .= '; ...'; }
        }
        if (!empty($result['feriados_trabajados'])) {
            $baseObs .= ' Atención: asistencia en feriado/domingo, se sugiere A-004: ' . implode('; ', array_slice($result['feriados_trabajados'], 0, 3));
        }
        if (!empty($result['desglose_auditor']['asistencia_en_descanso_turno'])) {
            $baseObs .= ' Atención: hay asistencia en día de descanso por turno; revisar si corresponde A-003 descanso trabajado.';
        }

        $result['desglose_auditor']['base_calendario_pagada'] = (float)$result['dias_laborados'] + (float)$result['dias_descanso'];
        $dg = $result['desglose_auditor'];
        $result['desglose_texto'] = 'Período: ' . $dg['dias_periodo'] . ' días · Laborables por turno: ' . $dg['laborables_por_turno'] .
            ' · Descanso turno puro: ' . $dg['descansos_por_turno_puros'] .
            ' · Domingos LOTTT: ' . $dg['domingos_lottt'] .
            ' · Feriados laborales no dominicales: ' . $dg['feriados_nomina_no_domingo'] .
            ' · Total no laborables calculados: ' . $dg['no_laborables_total'] .
            ' · Base calendario pagada: ' . $dg['base_calendario_pagada'];

        $result['observacion'] = $baseObs;
        return $result;
    }
}

if (!function_exists('nomina_attendance_days')) {
    function nomina_attendance_days($conexion, $personaId, $fechaDesde, $fechaHasta) {
        $personaId = (int)$personaId;
        $fechaDesde = mysqli_real_escape_string($conexion, nomina_date_to_iso($fechaDesde));
        $fechaHasta = mysqli_real_escape_string($conexion, nomina_date_to_iso($fechaHasta));
        if ($fechaDesde === '' || $fechaHasta === '') {
            return 0;
        }
        $sql = "SELECT COUNT(DISTINCT DATE(fecha_entrada)) AS dias
                FROM asistencia
                WHERE persona_id = {$personaId}
                  AND fecha_entrada IS NOT NULL
                  AND DATE(fecha_entrada) BETWEEN '{$fechaDesde}' AND '{$fechaHasta}'";
        $res = mysqli_query($conexion, $sql);
        $row = $res ? mysqli_fetch_assoc($res) : ['dias' => 0];
        return (float)($row['dias'] ?? 0);
    }
}

if (!function_exists('nomina_build_preloads')) {
    function nomina_build_preloads($conexion, array $personas, array $periodo, $source) {
        $source = strtoupper((string)$source);
        $data = [];
        foreach ($personas as $persona) {
            $base = nomina_schedule_by_calendar($persona, $periodo, $conexion);
            if ($source === 'MANUAL') {
                $base['dias_laborados'] = 0;
                $base['dias_descanso'] = 0;
                $base['observacion'] = 'Captura manual desde cero.';
            } elseif ($source === 'ASISTENCIA') {
                $base['dias_laborados'] = nomina_attendance_days($conexion, $persona['id'], $periodo['fecha_desde'], $periodo['fecha_hasta']);
                $base['observacion'] = 'Días laborados precargados por asistencia. Revisa ausencias para no duplicar descuentos.';
                $base['fuente'] = 'ASISTENCIA';
            }
            $data[(int)$persona['id']] = $base;
        }
        return $data;
    }
}



if (!function_exists('nomina_ensure_conceptos_fixed_schema')) {
    function nomina_ensure_conceptos_fixed_schema($conexion) {
        $changes = [];
        $errors = [];
        $columns = [
            'monto_fijo' => "ALTER TABLE conceptos_nomina ADD COLUMN monto_fijo DECIMAL(18,6) NULL DEFAULT NULL AFTER tasa",
            'moneda_monto_fijo' => "ALTER TABLE conceptos_nomina ADD COLUMN moneda_monto_fijo VARCHAR(3) NOT NULL DEFAULT 'VES' AFTER monto_fijo",
            'aplica_global' => "ALTER TABLE conceptos_nomina ADD COLUMN aplica_global TINYINT(1) NOT NULL DEFAULT 0 AFTER moneda_monto_fijo",
        ];
        foreach ($columns as $column => $sql) {
            if (!nomina_table_has_column($conexion, 'conceptos_nomina', $column)) {
                if (@mysqli_query($conexion, $sql)) {
                    $changes[] = $column;
                } else {
                    $errors[] = 'No se pudo crear la columna ' . $column . ' en conceptos_nomina: ' . mysqli_error($conexion);
                }
            }
        }
        return ['changes' => $changes, 'errors' => $errors];
    }
}

if (!function_exists('nomina_get_active_global_fixed_concepts')) {
    function nomina_get_active_global_fixed_concepts($conexion, $skipBase = true) {
        if (!nomina_table_has_column($conexion, 'conceptos_nomina', 'monto_fijo') || !nomina_table_has_column($conexion, 'conceptos_nomina', 'aplica_global')) {
            return [];
        }
        $rows = [];
        $sql = "SELECT id, codigo, nombre, tipo, modo_calculo, monto_fijo, moneda_monto_fijo, aplica_global
                FROM conceptos_nomina
                WHERE activo = 1
                  AND modo_calculo = 'FIJO'
                  AND aplica_global = 1
                  AND monto_fijo IS NOT NULL
                  AND monto_fijo <> 0
                ORDER BY codigo ASC";
        $res = mysqli_query($conexion, $sql);
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                if ($skipBase && nomina_es_concepto_base($row['codigo'])) {
                    continue;
                }
                $rows[] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('nomina_fixed_amount_to_bs')) {
    function nomina_fixed_amount_to_bs(array $concepto, $tasaBcv) {
        $monto = nomina_normalize_decimal($concepto['monto_fijo'] ?? 0);
        $moneda = strtoupper((string)($concepto['moneda_monto_fijo'] ?? 'VES'));
        if ($moneda === 'USD') {
            return $monto * (float)$tasaBcv;
        }
        return $monto;
    }
}

if (!function_exists('nomina_es_concepto_base')) {
    function nomina_es_concepto_base($codigo) {
        $codigo = strtoupper((string)$codigo);
        $base = ['A-001','A-002','A-003','A-004','A-005','A-006','A-007','A-008','A-009','A-010','A-011','B-001','B-002','B-003','B-004','B-005','B-006','B-007','B-008','P-001','P-002','P-003','P-004','P-005'];
        return in_array($codigo, $base, true);
    }
}
