<?php
/**
 * GEOSTARK NOMINA - Vacaciones legales v8
 * Helper compatible MariaDB/MySQL. No usa ON DUPLICATE KEY UPDATE ni AS nuevo.
 */
if (!function_exists('nomina_vac_v8_has_column')) {
    function nomina_vac_v8_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; }
        $res = @mysqli_query($conexion, "SHOW COLUMNS FROM `{$table}` LIKE '" . mysqli_real_escape_string($conexion, $column) . "'");
        return $res && mysqli_num_rows($res) > 0;
    }
}

if (!function_exists('nomina_vac_v8_ensure_schema')) {
    function nomina_vac_v8_ensure_schema($conexion) {
        @mysqli_query($conexion, "CREATE TABLE IF NOT EXISTS nomina_vacaciones_eventos (
            id INT AUTO_INCREMENT PRIMARY KEY,
            persona_id INT NOT NULL,
            fecha_documento DATE NOT NULL,
            fecha_inicio DATE DEFAULT NULL,
            fecha_fin DATE DEFAULT NULL,
            tipo VARCHAR(30) NOT NULL DEFAULT 'DISFRUTE',
            ciclo_anual INT DEFAULT NULL,
            es_historico TINYINT(1) NOT NULL DEFAULT 0,
            dias_disfrute DECIMAL(10,2) NOT NULL DEFAULT 0,
            dias_bono DECIMAL(10,2) NOT NULL DEFAULT 0,
            monto_disfrute_bs DECIMAL(18,2) NOT NULL DEFAULT 0,
            monto_bono_bs DECIMAL(18,2) NOT NULL DEFAULT 0,
            periodo_id INT DEFAULT NULL,
            corrida_id INT DEFAULT NULL,
            observacion VARCHAR(255) DEFAULT NULL,
            estado VARCHAR(20) NOT NULL DEFAULT 'REGISTRADO',
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            KEY idx_persona_fecha (persona_id, fecha_documento),
            KEY idx_persona_ciclo (persona_id, ciclo_anual),
            KEY idx_periodo (periodo_id),
            KEY idx_estado (estado)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        $columns = [
            'fuente_salario' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN fuente_salario VARCHAR(40) NULL AFTER estado",
            'salario_mensual_bs' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN salario_mensual_bs DECIMAL(18,4) NOT NULL DEFAULT 0 AFTER fuente_salario",
            'salario_diario_bs' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN salario_diario_bs DECIMAL(18,4) NOT NULL DEFAULT 0 AFTER salario_mensual_bs",
            'tasa_bcv_usada' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN tasa_bcv_usada DECIMAL(18,6) NOT NULL DEFAULT 0 AFTER salario_diario_bs",
            'salario_moneda' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN salario_moneda VARCHAR(10) NULL AFTER tasa_bcv_usada",
            'salario_monto_original' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN salario_monto_original DECIMAL(18,4) NOT NULL DEFAULT 0 AFTER salario_moneda",
            'bono_extra_usd_mensual' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN bono_extra_usd_mensual DECIMAL(18,4) NOT NULL DEFAULT 0 AFTER salario_monto_original",
            'origen_registro' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN origen_registro VARCHAR(40) NOT NULL DEFAULT 'V8_VACACIONES' AFTER bono_extra_usd_mensual",
            'soporte_referencia' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN soporte_referencia VARCHAR(255) NULL AFTER origen_registro",
            'snapshot_json' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN snapshot_json JSON NULL AFTER soporte_referencia",
            'created_by' => "ALTER TABLE nomina_vacaciones_eventos ADD COLUMN created_by VARCHAR(120) NULL AFTER snapshot_json",
        ];
        foreach ($columns as $col => $ddl) {
            if (!nomina_vac_v8_has_column($conexion, 'nomina_vacaciones_eventos', $col)) {
                @mysqli_query($conexion, $ddl);
            }
        }
        @mysqli_query($conexion, "CREATE TABLE IF NOT EXISTS nomina_vacaciones_auditoria (
            id INT AUTO_INCREMENT PRIMARY KEY,
            fecha_evento DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            usuario VARCHAR(120) NULL,
            accion VARCHAR(80) NOT NULL,
            persona_id INT NULL,
            vacaciones_evento_id INT NULL,
            detalle TEXT NULL,
            ip VARCHAR(64) NULL,
            KEY idx_fecha (fecha_evento),
            KEY idx_persona (persona_id),
            KEY idx_accion (accion)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    }
}

if (!function_exists('nomina_vac_v8_audit')) {
    function nomina_vac_v8_audit($conexion, $accion, $personaId = null, $eventoId = null, $detalle = '') {
        @mysqli_query($conexion, "CREATE TABLE IF NOT EXISTS nomina_vacaciones_auditoria (
            id INT AUTO_INCREMENT PRIMARY KEY,
            fecha_evento DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            usuario VARCHAR(120) NULL,
            accion VARCHAR(80) NOT NULL,
            persona_id INT NULL,
            vacaciones_evento_id INT NULL,
            detalle TEXT NULL,
            ip VARCHAR(64) NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
        $usuario = isset($_SESSION['usuario']) ? (string)$_SESSION['usuario'] : 'sistema';
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';
        $stmt = mysqli_prepare($conexion, "INSERT INTO nomina_vacaciones_auditoria (usuario, accion, persona_id, vacaciones_evento_id, detalle, ip) VALUES (?, ?, ?, ?, ?, ?)");
        if ($stmt) {
            $pid = $personaId !== null ? (int)$personaId : null;
            $eid = $eventoId !== null ? (int)$eventoId : null;
            mysqli_stmt_bind_param($stmt, 'ssiiss', $usuario, $accion, $pid, $eid, $detalle, $ip);
            @mysqli_stmt_execute($stmt);
            mysqli_stmt_close($stmt);
        }
    }
}

if (!function_exists('nomina_vac_v8_iso')) {
    function nomina_vac_v8_iso($value) {
        if (function_exists('nomina_date_to_iso')) { return 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_vac_v8_decimal')) {
    function nomina_vac_v8_decimal($value, $default = 0.0) {
        if (function_exists('nomina_normalize_decimal')) { return nomina_normalize_decimal($value, $default); }
        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_vac_v8_fix_mojibake_text')) {
    function nomina_vac_v8_fix_mojibake_text($value) {
        if (!is_string($value) || $value === '') { return $value; }
        $map = [
            'Ã¡'=>'á','Ã©'=>'é','Ã­'=>'í','Ã³'=>'ó','Ãº'=>'ú','Ã±'=>'ñ','Ã‘'=>'Ñ',
            'Ã'=>'Á','Ã‰'=>'É','Ã'=>'Í','Ã“'=>'Ó','Ãš'=>'Ú','Ã¼'=>'ü','Ãœ'=>'Ü',
            'Â°'=>'°','Âº'=>'º','Âª'=>'ª','Â¿'=>'¿','Â¡'=>'¡','â€“'=>'–','â€”'=>'—',
            'â€œ'=>'“','â€'=>'”','â€˜'=>'‘','â€™'=>'’','Â '=>' '
        ];
        return strtr($value, $map);
    }
}

if (!function_exists('nomina_vac_v8_fix_mojibake_recursive')) {
    function nomina_vac_v8_fix_mojibake_recursive($value) {
        if (is_array($value)) {
            $out = [];
            foreach ($value as $k => $v) { $out[$k] = nomina_vac_v8_fix_mojibake_recursive($v); }
            return $out;
        }
        return nomina_vac_v8_fix_mojibake_text($value);
    }
}

if (!function_exists('nomina_vac_v8_valid_date')) {
    function nomina_vac_v8_valid_date($date) {
        $date = nomina_vac_v8_iso($date);
        if ($date === '' || $date === '0000-00-00') { return false; }
        try { new DateTime($date); return true; } catch (Exception $e) { return false; }
    }
}

if (!function_exists('nomina_vac_v8_fetch_periods')) {
    function nomina_vac_v8_fetch_periods($conexion) {
        $rows = [];
        $res = @mysqli_query($conexion, "SELECT id, codigo, fecha_desde, fecha_hasta, estado FROM periodos_nomina ORDER BY fecha_desde DESC, id DESC");
        if ($res) { while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } }
        return $rows;
    }
}

if (!function_exists('nomina_vac_v8_fetch_people')) {
    function nomina_vac_v8_fetch_people($conexion) {
        if (function_exists('nomina_ensure_cargos_schema')) { nomina_ensure_cargos_schema($conexion); }
        $sql = "SELECT p.id, p.nombre, p.apellido, p.cedula, p.fecha_ingreso, p.fecha_egreso, p.estatus_laboral, p.id_cargo, p.id_turno,
                       p.salario_base, p.moneda_salario, p.departamento, p.area,
                       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
                FROM personas p
                LEFT JOIN cargos c ON c.id_cargo = p.id_cargo
                LEFT JOIN turnos t ON t.id = p.id_turno
                WHERE IFNULL(p.estatus_laboral, 'ACTIVO') = 'ACTIVO'
                ORDER BY p.apellido, p.nombre";
        $rows = [];
        $res = mysqli_query($conexion, $sql);
        if ($res) { while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } }
        return $rows;
    }
}

if (!function_exists('nomina_vac_v8_invalid_people')) {
    function nomina_vac_v8_invalid_people($people, $fechaCorte) {
        $out = [];
        $corte = nomina_vac_v8_iso($fechaCorte);
        foreach ($people as $p) {
            $ing = nomina_vac_v8_iso($p['fecha_ingreso'] ?? '');
            if (!nomina_vac_v8_valid_date($ing) || ($corte !== '' && $ing > $corte)) {
                $out[] = $p;
            }
        }
        return $out;
    }
}

if (!function_exists('nomina_vac_v8_completed_years')) {
    function nomina_vac_v8_completed_years($fechaIngreso, $fechaCorte) {
        $ing = nomina_vac_v8_iso($fechaIngreso); $cut = nomina_vac_v8_iso($fechaCorte);
        if (!nomina_vac_v8_valid_date($ing) || !nomina_vac_v8_valid_date($cut)) { return 0; }
        $d1 = new DateTime($ing); $d2 = new DateTime($cut);
        if ($d2 < $d1) { return 0; }
        return (int)$d1->diff($d2)->y;
    }
}

if (!function_exists('nomina_vac_v8_cycle_days')) {
    function nomina_vac_v8_cycle_days($cycleNo) {
        $cycleNo = (int)$cycleNo;
        if ($cycleNo < 1) { return 0; }
        return 15 + min(max($cycleNo - 1, 0), 15);
    }
}

if (!function_exists('nomina_vac_v8_total_days')) {
    function nomina_vac_v8_total_days($completedYears) {
        $total = 0;
        for ($i = 1; $i <= (int)$completedYears; $i++) { $total += nomina_vac_v8_cycle_days($i); }
        return $total;
    }
}

if (!function_exists('nomina_vac_v8_latest_salary_history')) {
    function nomina_vac_v8_latest_salary_history($conexion, $personaId, $fechaCorte) {
        $personaId = (int)$personaId;
        $fecha = mysqli_real_escape_string($conexion, nomina_vac_v8_iso($fechaCorte));
        if ($personaId <= 0 || $fecha === '') { return null; }
        $sql = "SELECT * FROM nomina_salario_historial
                WHERE persona_id = {$personaId}
                  AND fecha_desde <= '{$fecha}'
                ORDER BY fecha_desde DESC, id DESC
                LIMIT 1";
        $res = @mysqli_query($conexion, $sql);
        return ($res && mysqli_num_rows($res)) ? mysqli_fetch_assoc($res) : null;
    }
}

if (!function_exists('nomina_vac_v8_salary_context')) {
    function nomina_vac_v8_salary_context($conexion, array $persona, $fechaCorte, $tasaBcv) {
        $hist = nomina_vac_v8_latest_salary_history($conexion, (int)$persona['id'], $fechaCorte);
        if ($hist) {
            $mensual = (float)($hist['salario_normal_mensual_bs'] ?: $hist['salario_mensual_bs']);
            if ($mensual <= 0) { $mensual = (float)$hist['salario_mensual_bs']; }
            return [
                'fuente' => 'HISTORIAL_CORRIDA',
                'salario_mensual_bs' => $mensual,
                'salario_diario_bs' => $mensual / 30,
                'tasa_bcv_usada' => (float)($hist['tasa_bcv_usada'] ?: $tasaBcv),
                'salario_moneda' => (string)($hist['salario_moneda'] ?: 'VES'),
                'salario_monto_original' => (float)$hist['salario_monto_original'],
                'bono_extra_usd_mensual' => (float)$hist['bono_extra_usd_mensual'],
                'referencia_corrida_id' => (int)($hist['referencia_corrida_id'] ?? 0),
                'snapshot' => $hist,
            ];
        }
        $salarioBase = $persona['salario_base'] ?? null;
        $usaPersona = ($salarioBase !== null && $salarioBase !== '');
        $monto = $usaPersona ? (float)$salarioBase : (float)($persona['salario_cargo'] ?? 0);
        $moneda = $usaPersona ? strtoupper((string)($persona['moneda_salario'] ?? 'VES')) : strtoupper((string)($persona['moneda_salario_cargo'] ?? 'VES'));
        $salarioBs = ($moneda === 'USD') ? ($monto * (float)$tasaBcv) : $monto;
        $bonoUsd = (float)($persona['bono_extra_usd_mensual'] ?? 0);
        $salarioNormalBs = $salarioBs + ($bonoUsd * (float)$tasaBcv);
        return [
            'fuente' => $usaPersona ? 'PERSONA_FALLBACK' : 'CARGO_FALLBACK',
            'salario_mensual_bs' => $salarioNormalBs,
            'salario_diario_bs' => $salarioNormalBs / 30,
            'tasa_bcv_usada' => (float)$tasaBcv,
            'salario_moneda' => $moneda,
            'salario_monto_original' => $monto,
            'bono_extra_usd_mensual' => $bonoUsd,
            'referencia_corrida_id' => 0,
            'snapshot' => ['persona' => $persona],
        ];
    }
}

if (!function_exists('nomina_vac_v8_usage_by_cycle')) {
    function nomina_vac_v8_usage_by_cycle($conexion, $personaId) {
        nomina_vac_v8_ensure_schema($conexion);
        $personaId = (int)$personaId;
        $rows = [];
        $sql = "SELECT ciclo_anual,
                       SUM(CASE WHEN estado='REGISTRADO' THEN dias_disfrute ELSE 0 END) AS dias_disfrute,
                       SUM(CASE WHEN estado='REGISTRADO' THEN dias_bono ELSE 0 END) AS dias_bono,
                       SUM(CASE WHEN estado='REGISTRADO' THEN monto_disfrute_bs ELSE 0 END) AS monto_disfrute_bs,
                       SUM(CASE WHEN estado='REGISTRADO' THEN monto_bono_bs ELSE 0 END) AS monto_bono_bs
                FROM nomina_vacaciones_eventos
                WHERE persona_id={$personaId} AND IFNULL(ciclo_anual,0)>0
                GROUP BY ciclo_anual";
        $res = mysqli_query($conexion, $sql);
        if ($res) { while ($r = mysqli_fetch_assoc($res)) { $rows[(int)$r['ciclo_anual']] = $r; } }
        return $rows;
    }
}

if (!function_exists('nomina_vac_v8_summary')) {
    function nomina_vac_v8_summary($conexion) {
        nomina_vac_v8_ensure_schema($conexion);
        $rows = [];
        $res = @mysqli_query($conexion, "SELECT persona_id,
                    SUM(CASE WHEN estado='REGISTRADO' THEN dias_disfrute ELSE 0 END) AS dias_disfrute,
                    SUM(CASE WHEN estado='REGISTRADO' THEN dias_bono ELSE 0 END) AS dias_bono,
                    SUM(CASE WHEN estado='REGISTRADO' THEN monto_disfrute_bs ELSE 0 END) AS monto_disfrute_bs,
                    SUM(CASE WHEN estado='REGISTRADO' THEN monto_bono_bs ELSE 0 END) AS monto_bono_bs
                 FROM nomina_vacaciones_eventos GROUP BY persona_id");
        if ($res) { while ($r = mysqli_fetch_assoc($res)) { $rows[(int)$r['persona_id']] = $r; } }
        return $rows;
    }
}

if (!function_exists('nomina_vac_v8_balance')) {
    function nomina_vac_v8_balance($conexion, array $persona, $fechaCorte, $tasaBcv, array $summary = null, array $usageByCycle = null) {
        $summary = $summary ?: [];
        $usageByCycle = $usageByCycle ?: nomina_vac_v8_usage_by_cycle($conexion, (int)$persona['id']);
        $years = nomina_vac_v8_completed_years($persona['fecha_ingreso'] ?? '', $fechaCorte);
        $total = nomina_vac_v8_total_days($years);
        $usedEnjoy = nomina_vac_v8_decimal($summary['dias_disfrute'] ?? 0);
        $usedBonus = nomina_vac_v8_decimal($summary['dias_bono'] ?? 0);
        $currentCycle = max(0, $years);
        $cycleDays = nomina_vac_v8_cycle_days($currentCycle);
        $cycleUsedEnjoy = nomina_vac_v8_decimal($usageByCycle[$currentCycle]['dias_disfrute'] ?? 0);
        $cycleUsedBonus = nomina_vac_v8_decimal($usageByCycle[$currentCycle]['dias_bono'] ?? 0);
        $pendingEnjoy = max(0, $total - $usedEnjoy);
        $pendingBonus = max(0, $total - $usedBonus);
        $pendingCycleEnjoy = max(0, $cycleDays - $cycleUsedEnjoy);
        $pendingCycleBonus = max(0, $cycleDays - $cycleUsedBonus);
        $salary = nomina_vac_v8_salary_context($conexion, $persona, $fechaCorte, $tasaBcv);
        return [
            'completed_years' => $years,
            'current_cycle' => $currentCycle,
            'cycle_days' => $cycleDays,
            'total_accrued_days' => $total,
            'total_bonus_days' => $total,
            'used_days' => $usedEnjoy,
            'used_bonus_days' => $usedBonus,
            'pending_days' => $pendingEnjoy,
            'pending_bonus_days' => $pendingBonus,
            'cycle_used_days' => $cycleUsedEnjoy,
            'cycle_used_bonus_days' => $cycleUsedBonus,
            'pending_cycle_days' => $pendingCycleEnjoy,
            'pending_cycle_bonus_days' => $pendingCycleBonus,
            'salary' => $salary,
            'daily_bs' => (float)$salary['salario_diario_bs'],
            'pending_enjoyment_bs' => $pendingEnjoy * (float)$salary['salario_diario_bs'],
            'pending_bonus_bs' => $pendingBonus * (float)$salary['salario_diario_bs'],
            'pending_total_bs' => ($pendingEnjoy + $pendingBonus) * (float)$salary['salario_diario_bs'],
            'cycle_enjoyment_bs' => $pendingCycleEnjoy * (float)$salary['salario_diario_bs'],
            'cycle_bonus_bs' => $pendingCycleBonus * (float)$salary['salario_diario_bs'],
        ];
    }
}


if (!function_exists('nomina_vac_v8_calendar_holidays')) {
    function nomina_vac_v8_calendar_holidays($conexion, $fechaDesde, $fechaHasta) {
        $fechaDesde = nomina_vac_v8_iso($fechaDesde);
        $fechaHasta = nomina_vac_v8_iso($fechaHasta);
        if (!nomina_vac_v8_valid_date($fechaDesde) || !nomina_vac_v8_valid_date($fechaHasta)) { return []; }
        if (!nomina_vac_v8_has_column($conexion, 'nomina_calendario_laboral', 'fecha')) { return []; }
        $desde = mysqli_real_escape_string($conexion, $fechaDesde);
        $hasta = mysqli_real_escape_string($conexion, $fechaHasta);
        $sql = "SELECT fecha, nombre, tipo FROM nomina_calendario_laboral
                WHERE activo=1 AND aplica_nomina=1 AND fecha BETWEEN '{$desde}' AND '{$hasta}'
                ORDER BY fecha";
        $out = [];
        $res = @mysqli_query($conexion, $sql);
        if ($res) {
            while ($r = mysqli_fetch_assoc($res)) {
                $f = (string)($r['fecha'] ?? '');
                if ($f !== '') {
                    $out[$f] = trim((string)($r['nombre'] ?? 'Feriado laboral'));
                }
            }
        }
        return $out;
    }
}

if (!function_exists('nomina_vac_v8_rest_weekdays')) {
    function nomina_vac_v8_rest_weekdays(array $persona) {
        // PHP/JS convention here: 0=domingo, 1=lunes, ... 6=sábado.
        $map = [
            1 => 'lunes_libre',
            2 => 'martes_libre',
            3 => 'miercoles_libre',
            4 => 'jueves_libre',
            5 => 'viernes_libre',
            6 => 'sabado_libre',
            0 => 'domingo_libre',
        ];
        $out = [];
        foreach ($map as $num => $field) {
            if (isset($persona[$field]) && (int)$persona[$field] === 1) { $out[] = (int)$num; }
        }
        // LOTTT art. 184: el domingo es feriado laboral. Para cálculo conservador de vacaciones,
        // se excluye siempre aunque el turno no lo marque como libre.
        if (!in_array(0, $out, true)) { $out[] = 0; }
        sort($out);
        return array_values(array_unique($out));
    }
}

if (!function_exists('nomina_vac_v8_suggest_end_date')) {
    function nomina_vac_v8_suggest_end_date($conexion, array $persona, $fechaInicio, $diasHabiles) {
        $fechaInicio = nomina_vac_v8_iso($fechaInicio);
        $diasHabiles = (float)$diasHabiles;
        if (!nomina_vac_v8_valid_date($fechaInicio) || $diasHabiles <= 0) {
            return ['fecha_fin'=>null,'dias_contados'=>0,'excluidos'=>0,'detalle'=>[]];
        }
        $diasObjetivo = (int)ceil($diasHabiles);
        $start = new DateTime($fechaInicio);
        $max = clone $start;
        $max->modify('+' . max(90, ($diasObjetivo * 4 + 60)) . ' days');
        $holidayMap = nomina_vac_v8_calendar_holidays($conexion, $start->format('Y-m-d'), $max->format('Y-m-d'));
        $restWeekdays = nomina_vac_v8_rest_weekdays($persona);

        $counted = 0;
        $excluded = 0;
        $detail = [];
        $cursor = clone $start;
        $limit = 0;
        $lastCounted = null;
        while ($limit < 800) {
            $date = $cursor->format('Y-m-d');
            $dow = (int)$cursor->format('w'); // 0 domingo
            $reason = '';
            if (in_array($dow, $restWeekdays, true)) {
                $reason = ($dow === 0) ? 'Domingo/feriado laboral LOTTT' : 'Descanso según turno';
            }
            if (isset($holidayMap[$date])) {
                $reason = $holidayMap[$date];
            }

            if ($reason === '') {
                $counted++;
                $lastCounted = $date;
                if (count($detail) < 40) { $detail[] = ['fecha'=>$date,'estado'=>'CUENTA','motivo'=>'Día hábil de disfrute']; }
                if ($counted >= $diasObjetivo) { break; }
            } else {
                $excluded++;
                if (count($detail) < 40) { $detail[] = ['fecha'=>$date,'estado'=>'NO_CUENTA','motivo'=>$reason]; }
            }
            $cursor->modify('+1 day');
            $limit++;
        }
        return [
            'fecha_fin' => $lastCounted,
            'dias_contados' => $counted,
            'excluidos' => $excluded,
            'dias_objetivo' => $diasObjetivo,
            'rest_weekdays' => $restWeekdays,
            'detalle' => $detail,
        ];
    }
}


if (!function_exists('nomina_vac_v8_person_by_id')) {
    function nomina_vac_v8_person_by_id(array $people, $id) {
        foreach ($people as $p) { if ((int)$p['id'] === (int)$id) { return $p; } }
        return null;
    }
}

if (!function_exists('nomina_vac_v8_period_info')) {
    function nomina_vac_v8_period_info($conexion, $periodoId) {
        $periodoId = (int)$periodoId;
        if ($periodoId <= 0) { return null; }
        $res = mysqli_query($conexion, "SELECT * FROM periodos_nomina WHERE id={$periodoId} LIMIT 1");
        return ($res && mysqli_num_rows($res)) ? mysqli_fetch_assoc($res) : null;
    }
}

if (!function_exists('nomina_vac_v8_sync_to_novedades')) {
    function nomina_vac_v8_sync_to_novedades($conexion, $periodoId, $personaId, $montoBs, $observacion) {
        $periodo = nomina_vac_v8_period_info($conexion, $periodoId);
        if (!$periodo) { return ['ok'=>false,'message'=>'Período no encontrado.']; }
        if (($periodo['estado'] ?? '') === 'CERRADO') { return ['ok'=>false,'message'=>'No se puede copiar a novedades de un período cerrado.']; }
        if (!function_exists('nomina_detect_novedades_schema')) { return ['ok'=>false,'message'=>'No está disponible el detector de estructura de novedades.']; }
        $schema = nomina_detect_novedades_schema($conexion);
        if (empty($schema['pago_vacaciones_bs'])) { return ['ok'=>false,'message'=>'La tabla novedades_periodo no tiene campo de pago manual de vacaciones.']; }
        $periodoId = (int)$periodoId; $personaId = (int)$personaId; $montoBs = round((float)$montoBs, 2);
        $res = mysqli_query($conexion, "SELECT id FROM novedades_periodo WHERE periodo_id={$periodoId} AND persona_id={$personaId} LIMIT 1");
        if (!$res || mysqli_num_rows($res) === 0) {
            $cols = ['periodo_id','persona_id']; $vals = [$periodoId,$personaId];
            foreach (['dias_ausencia_periodo','horas_retardo_periodo','he_diurna','he_nocturna','horas_nocturnas_ordinarias','dias_ausencia_mes','bono_manual_bs','otros_descuentos_bs','prestamo_bs','pago_vacaciones_bs'] as $k) {
                if (!empty($schema[$k])) { $cols[] = $schema[$k]; $vals[] = 0; }
            }
            if (!empty($schema['observaciones'])) { $cols[] = $schema['observaciones']; $vals[] = ''; }
            $colSql = '`' . implode('`,`', $cols) . '`';
            $valSql = implode(',', array_map(function($v) use ($conexion) { return is_numeric($v) ? (string)$v : "'" . mysqli_real_escape_string($conexion, (string)$v) . "'"; }, $vals));
            if (!mysqli_query($conexion, "INSERT INTO novedades_periodo ({$colSql}) VALUES ({$valSql})")) {
                return ['ok'=>false,'message'=>'No se pudo crear fila en novedades: '.mysqli_error($conexion)];
            }
        }
        $obsSql = '';
        if (!empty($schema['observaciones'])) {
            $obs = mysqli_real_escape_string($conexion, trim((string)$observacion));
            $obsSql = ", `{$schema['observaciones']}` = TRIM(CONCAT(IFNULL(`{$schema['observaciones']}`, ''), CASE WHEN IFNULL(`{$schema['observaciones']}`, '') = '' THEN '' ELSE ' | ' END, '{$obs}'))";
        }
        $sql = "UPDATE novedades_periodo SET `{$schema['pago_vacaciones_bs']}` = ROUND(IFNULL(`{$schema['pago_vacaciones_bs']}`,0) + {$montoBs}, 2){$obsSql} WHERE periodo_id={$periodoId} AND persona_id={$personaId}";
        $ok = mysqli_query($conexion, $sql);
        return ['ok'=>(bool)$ok,'message'=>$ok ? 'Pago de vacaciones sumado a novedades del período.' : mysqli_error($conexion)];
    }
}

if (!function_exists('nomina_vac_v8_insert_event')) {
    function nomina_vac_v8_insert_event($conexion, array $data) {
        nomina_vac_v8_ensure_schema($conexion);
        $sql = "INSERT INTO nomina_vacaciones_eventos
            (persona_id, fecha_documento, fecha_inicio, fecha_fin, tipo, ciclo_anual, es_historico, dias_disfrute, dias_bono,
             monto_disfrute_bs, monto_bono_bs, periodo_id, observacion, estado, fuente_salario, salario_mensual_bs,
             salario_diario_bs, tasa_bcv_usada, salario_moneda, salario_monto_original, bono_extra_usd_mensual,
             origen_registro, soporte_referencia, snapshot_json, created_by)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'REGISTRADO', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        $stmt = mysqli_prepare($conexion, $sql);
        if (!$stmt) { return ['ok'=>false,'message'=>mysqli_error($conexion),'id'=>0]; }
        $personaId = (int)$data['persona_id'];
        $fechaDoc = (string)$data['fecha_documento'];
        $fechaInicio = $data['fecha_inicio'] ?: null;
        $fechaFin = $data['fecha_fin'] ?: null;
        $tipo = (string)$data['tipo'];
        $ciclo = (int)($data['ciclo_anual'] ?? 0);
        $hist = (int)($data['es_historico'] ?? 0);
        $diasDisf = (float)$data['dias_disfrute'];
        $diasBono = (float)$data['dias_bono'];
        $montoDisf = (float)$data['monto_disfrute_bs'];
        $montoBono = (float)$data['monto_bono_bs'];
        $periodoId = (int)($data['periodo_id'] ?? 0);
        if ($periodoId <= 0) { $periodoId = null; }
        $obs = (string)($data['observacion'] ?? '');
        $fuente = (string)($data['fuente_salario'] ?? '');
        $salMens = (float)($data['salario_mensual_bs'] ?? 0);
        $salDia = (float)($data['salario_diario_bs'] ?? 0);
        $tasa = (float)($data['tasa_bcv_usada'] ?? 0);
        $moneda = strtoupper(trim((string)($data['salario_moneda'] ?? 'VES')));
        if ($moneda === '' || $moneda === '0') { $moneda = 'VES'; }
        $montoOrig = (float)($data['salario_monto_original'] ?? 0);
        $bonoUsd = (float)($data['bono_extra_usd_mensual'] ?? 0);
        $origen = trim((string)($data['origen_registro'] ?? 'V8_VACACIONES'));
        if ($origen === '' || $origen === '0') { $origen = ((int)($data['es_historico'] ?? 0) === 1) ? 'V8_AJUSTE_HISTORICO' : 'V8_VACACIONES_COMPLETAS'; }
        $soporte = nomina_vac_v8_fix_mojibake_text(trim((string)($data['soporte_referencia'] ?? '')));
        $snapshotData = nomina_vac_v8_fix_mojibake_recursive($data['snapshot_json'] ?? []);
        $snapshot = json_encode($snapshotData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $createdBy = isset($_SESSION['usuario']) ? (string)$_SESSION['usuario'] : 'sistema';
        mysqli_stmt_bind_param($stmt, 'issssiiddddissdddsddssss', $personaId, $fechaDoc, $fechaInicio, $fechaFin, $tipo, $ciclo, $hist, $diasDisf, $diasBono, $montoDisf, $montoBono, $periodoId, $obs, $fuente, $salMens, $salDia, $tasa, $moneda, $montoOrig, $bonoUsd, $origen, $soporte, $snapshot, $createdBy);
        $ok = mysqli_stmt_execute($stmt);
        $err = mysqli_stmt_error($stmt);
        $id = $ok ? (int)mysqli_insert_id($conexion) : 0;
        mysqli_stmt_close($stmt);
        if ($ok) { nomina_vac_v8_audit($conexion, 'REGISTRAR_VACACIONES', $personaId, $id, $obs); }
        return ['ok'=>$ok,'message'=>$ok ? 'Evento registrado.' : $err,'id'=>$id];
    }
}

if (!function_exists('nomina_vac_v8_recent_events')) {
    function nomina_vac_v8_recent_events($conexion, $limit = 25) {
        nomina_vac_v8_ensure_schema($conexion);
        $limit = max(1, (int)$limit);
        $sql = "SELECT e.*, p.nombre, p.apellido, p.cedula, per.codigo AS periodo_codigo, per.estado AS periodo_estado
                FROM nomina_vacaciones_eventos e
                LEFT JOIN personas p ON p.id=e.persona_id
                LEFT JOIN periodos_nomina per ON per.id=e.periodo_id
                ORDER BY e.fecha_documento DESC, e.id DESC LIMIT {$limit}";
        $rows = [];
        $res = mysqli_query($conexion, $sql);
        if ($res) { while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } }
        return $rows;
    }
}

if (!function_exists('nomina_vac_v8_event_by_id')) {
    function nomina_vac_v8_event_by_id($conexion, $eventId) {
        nomina_vac_v8_ensure_schema($conexion);
        $eventId = (int)$eventId;
        if ($eventId <= 0) { return null; }
        $res = mysqli_query($conexion, "SELECT * FROM nomina_vacaciones_eventos WHERE id={$eventId} LIMIT 1");
        return ($res && mysqli_num_rows($res)) ? mysqli_fetch_assoc($res) : null;
    }
}

if (!function_exists('nomina_vac_v8_copy_event_to_novedades')) {
    function nomina_vac_v8_copy_event_to_novedades($conexion, $eventId, $periodoId) {
        nomina_vac_v8_ensure_schema($conexion);
        $eventId = (int)$eventId;
        $periodoId = (int)$periodoId;
        if ($eventId <= 0) { return ['ok'=>false, 'message'=>'Evento de vacaciones inválido.']; }
        if ($periodoId <= 0) { return ['ok'=>false, 'message'=>'Selecciona un período abierto para copiar a novedades.']; }
        $event = nomina_vac_v8_event_by_id($conexion, $eventId);
        if (!$event) { return ['ok'=>false, 'message'=>'Evento de vacaciones no encontrado.']; }
        if (($event['estado'] ?? '') !== 'REGISTRADO') { return ['ok'=>false, 'message'=>'Solo se pueden copiar eventos registrados.']; }
        $already = (int)($event['periodo_id'] ?? 0);
        if ($already > 0) {
            return ['ok'=>false, 'message'=>'Este evento ya fue asociado/copiado a un período de nómina.'];
        }
        $periodo = nomina_vac_v8_period_info($conexion, $periodoId);
        if (!$periodo) { return ['ok'=>false, 'message'=>'Período de nómina no encontrado.']; }
        if (strtoupper((string)($periodo['estado'] ?? '')) === 'CERRADO') {
            return ['ok'=>false, 'message'=>'No se puede copiar a novedades de un período cerrado.'];
        }
        $monto = round((float)($event['monto_disfrute_bs'] ?? 0) + (float)($event['monto_bono_bs'] ?? 0), 2);
        if ($monto <= 0) { return ['ok'=>false, 'message'=>'El evento no tiene monto para copiar a novedades.']; }
        $personaId = (int)$event['persona_id'];
        $obsBase = trim((string)($event['observacion'] ?? ''));
        $obs = 'Vacaciones legales v8.3 evento #' . $eventId;
        if ($obsBase !== '') { $obs .= ': ' . $obsBase; }
        $sync = nomina_vac_v8_sync_to_novedades($conexion, $periodoId, $personaId, $monto, $obs);
        if (!$sync['ok']) { return $sync; }
        $ok = mysqli_query($conexion, "UPDATE nomina_vacaciones_eventos SET periodo_id={$periodoId}, updated_at=NOW() WHERE id={$eventId} LIMIT 1");
        if (!$ok) { return ['ok'=>false, 'message'=>'Se copió a novedades, pero no se pudo marcar el evento: '.mysqli_error($conexion)]; }
        nomina_vac_v8_audit($conexion, 'COPIAR_VACACIONES_NOVEDADES', $personaId, $eventId, 'Período '.$periodoId.' · monto Bs '.number_format($monto,2,'.',''));
        return ['ok'=>true, 'message'=>'Evento copiado a novedades del período '.$periodo['codigo'].' por Bs '.number_format($monto,2,',','.')];
    }
}


if (!function_exists('nomina_vac_v8_stats')) {
    function nomina_vac_v8_stats($conexion, array $people, $fechaCorte, $tasaBcv) {
        $summary = nomina_vac_v8_summary($conexion);
        $invalid = nomina_vac_v8_invalid_people($people, $fechaCorte);
        $pendingWorkers = 0; $pendingDays = 0; $pendingBonus = 0; $histSource = 0; $fallbackSource = 0;
        foreach ($people as $p) {
            if (!nomina_vac_v8_valid_date($p['fecha_ingreso'] ?? '')) { continue; }
            $bal = nomina_vac_v8_balance($conexion, $p, $fechaCorte, $tasaBcv, $summary[(int)$p['id']] ?? []);
            if ($bal['pending_days'] > 0 || $bal['pending_bonus_days'] > 0) { $pendingWorkers++; }
            $pendingDays += $bal['pending_days']; $pendingBonus += $bal['pending_bonus_days'];
            if ($bal['salary']['fuente'] === 'HISTORIAL_CORRIDA') { $histSource++; } else { $fallbackSource++; }
        }
        $res = mysqli_query($conexion, "SELECT COUNT(*) AS total FROM nomina_vacaciones_eventos WHERE estado='REGISTRADO'");
        $eventTotal = ($res && mysqli_num_rows($res)) ? (int)mysqli_fetch_assoc($res)['total'] : 0;
        return [
            'trabajadores' => count($people),
            'invalidos' => count($invalid),
            'pendientes_trabajadores' => $pendingWorkers,
            'pendientes_dias' => $pendingDays,
            'pendientes_bono' => $pendingBonus,
            'fuente_historial' => $histSource,
            'fuente_fallback' => $fallbackSource,
            'eventos' => $eventTotal,
        ];
    }
}
?>
