<?php
function e($value) { return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); }
function redirect($path) { header('Location: ' . base_url($path)); exit; }
function base_url($path = '') { $base = defined('BASE_URL') ? rtrim(BASE_URL, '/') : ''; $path = ltrim($path, '/'); return $base . ($path ? '/' . $path : ''); }
function set_flash($type, $message) { $_SESSION['flash'] = ['type' => $type, 'message' => $message]; }
function get_flash() { if (!empty($_SESSION['flash'])) { $f = $_SESSION['flash']; unset($_SESSION['flash']); return $f; } return null; }
function is_post() { return ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST'; }
function now() { return date('Y-m-d H:i:s'); }
function audit_log($action, $table_name, $record_id = null, $details = null) { if (empty($_SESSION['user']['id'])) return; try { $stmt = db()->prepare("INSERT INTO audit_logs (user_id, action_type, table_name, record_id, details, created_at) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([$_SESSION['user']['id'], $action, $table_name, $record_id, $details, now()]); } catch (Throwable $e) {} }
function count_table($table) { $allowed = ['employees','departments','positions','attendance','leaves_requests','payroll_periods','users','payroll_items','recruitment_vacancies','recruitment_candidates','recruitment_applications']; if (!in_array($table, $allowed, true)) return 0; return (int) db()->query("SELECT COUNT(*) c FROM {$table}")->fetch()['c']; }
function role_is($role) { return isset($_SESSION['user']['role']) && $_SESSION['user']['role'] === $role; }
function role_in(array $roles) { return isset($_SESSION['user']['role']) && in_array($_SESSION['user']['role'], $roles, true); }
function ensure_roles(array $roles) { require_login(); if (!role_in($roles)) { set_flash('danger', 'No tenés permiso para acceder a esta sección.'); redirect('dashboard.php'); } }
function money_gs($amount) { return 'Gs. ' . number_format((float)$amount, 0, ',', '.'); }
function setting($key, $default = '') { static $settings = null; if ($settings === null) { $settings = []; try { foreach (db()->query("SELECT setting_key, setting_value FROM company_settings") as $row) { $settings[$row['setting_key']] = $row['setting_value']; } } catch (Throwable $e) { return $default; } } return $settings[$key] ?? $default; }
function vacation_days_for_years($years) { if ($years < 5) return 12; if ($years < 10) return 18; return 30; }
function vacation_balance(array $employee) { $hireDate = !empty($employee['hire_date']) ? new DateTime($employee['hire_date']) : new DateTime(); $today = new DateTime(); $years = max(0, (int)$hireDate->diff($today)->y); $earned = vacation_days_for_years($years); $stmt = db()->prepare("SELECT COALESCE(SUM(DATEDIFF(end_date, start_date) + 1),0) used_days FROM leaves_requests WHERE employee_id = ? AND leave_type = 'Vacaciones' AND status = 'Aprobado' AND YEAR(start_date) = YEAR(CURDATE())"); $stmt->execute([$employee['id']]); $used = (int)($stmt->fetch()['used_days'] ?? 0); $earnedTotal = 0; for ($i = 0; $i < $years; $i++) { $earnedTotal += vacation_days_for_years($i); } return ['years'=>$years,'earned'=>$earned,'earned_total'=>$earnedTotal,'used'=>$used,'pending'=>max(0,$earnedTotal-$used)]; }
function company_name() { return setting('company_name', APP_NAME); }
function public_logo_url() { $logo = setting('company_logo', 'assets/img/logo.png'); return base_url($logo); }
function can_manage_master_data() { return role_in(['admin','rrhh']); }
function can_manage_payroll() { return role_in(['admin','rrhh']); }
function can_approve_leaves() { return role_in(['admin','rrhh','supervisor']); }
function status_badge_class($status) { $s = strtolower(trim((string)$status)); return match ($s) { 'aprobado','activo','active','cerrado' => 'success', 'pendiente','borrador' => 'warning', 'rechazado','inactivo','inactive','anulado' => 'danger', default => 'secondary', }; }
function ensure_can_manage_master_data() { if (!can_manage_master_data()) { set_flash('danger', 'No tenés permiso para modificar esta sección.'); redirect('dashboard.php'); } }
function ensure_can_manage_payroll() { if (!can_manage_payroll()) { set_flash('danger', 'No tenés permiso para operar nómina.'); redirect('dashboard.php'); } }
function leave_days_between($startDate, $endDate) { $start = strtotime((string)$startDate); $end = strtotime((string)$endDate); if (!$start || !$end || $end < $start) return 0; return (int)(($end - $start) / 86400) + 1; }

function month_name_es($month) { $names = [1=>'Enero',2=>'Febrero',3=>'Marzo',4=>'Abril',5=>'Mayo',6=>'Junio',7=>'Julio',8=>'Agosto',9=>'Septiembre',10=>'Octubre',11=>'Noviembre',12=>'Diciembre']; return $names[(int)$month] ?? (string)$month; }

function payroll_advance_total_for_employee($employeeId, $year, $month) {
    try {
        $stmt = db()->prepare("SELECT COALESCE(SUM(amount),0) total FROM salary_advances WHERE employee_id = ? AND YEAR(advance_date) = ? AND MONTH(advance_date) = ? AND status = 'Activo'");
        $stmt->execute([$employeeId, $year, $month]);
        return (float)($stmt->fetch()['total'] ?? 0);
    } catch (Throwable $e) {
        return 0;
    }
}

function generate_or_refresh_payroll_period($periodId, $closePeriod = false) {
    $stmt = db()->prepare("SELECT * FROM payroll_periods WHERE id = ? LIMIT 1");
    $stmt->execute([$periodId]);
    $period = $stmt->fetch();
    if (!$period) {
        return false;
    }

    db()->prepare("DELETE FROM payroll_items WHERE period_id = ?")->execute([$periodId]);

    $employees = db()->query("SELECT * FROM employees WHERE status='active' ORDER BY full_name")->fetchAll();
    $insert = db()->prepare("INSERT INTO payroll_items (period_id, employee_id, base_salary, extras, discounts, net_pay, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)");

    foreach ($employees as $emp) {
        $base = (float)($emp['salary'] ?? 0);
        $extras = 0;
        $discounts = payroll_advance_total_for_employee((int)$emp['id'], (int)$period['year'], (int)$period['month']);
        $net = $base + $extras - $discounts;
        if ($net < 0) { $net = 0; }
        $insert->execute([$periodId, $emp['id'], $base, $extras, $discounts, $net, now()]);
    }

    $newStatus = $closePeriod ? 'Cerrado' : 'Generado';
    db()->prepare("UPDATE payroll_periods SET status = ? WHERE id = ?")->execute([$newStatus, $periodId]);
    return $period;
}

function xlsx_cell($v) {
    $v = (string)$v;
    $v = str_replace(['&','<','>'], ['&amp;','&lt;','&gt;'], $v);
    return '<c t="inlineStr"><is><t xml:space="preserve">' . $v . '</t></is></c>';
}
function download_xlsx($filename, array $headers, array $rows) {
    if (!class_exists('ZipArchive')) {
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . preg_replace('/\.xlsx$/', '.csv', $filename) . '"');
        $out = fopen('php://output', 'w');
        fputcsv($out, $headers, ';');
        foreach ($rows as $row) { fputcsv($out, $row, ';'); }
        fclose($out);
        exit;
    }
    $tmpBase = sys_get_temp_dir() . '/xlsx_' . uniqid();
    @mkdir($tmpBase, 0777, true);
    @mkdir($tmpBase . '/_rels', 0777, true);
    @mkdir($tmpBase . '/xl/_rels', 0777, true);
    @mkdir($tmpBase . '/xl/worksheets', 0777, true);
    @mkdir($tmpBase . '/docProps', 0777, true);

    $sheetRows = [];
    $allRows = array_merge([$headers], $rows);
    foreach ($allRows as $row) {
        $cells = '';
        foreach ($row as $value) { $cells .= xlsx_cell($value); }
        $sheetRows[] = '<row>' . $cells . '</row>';
    }
    $sheetXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      . '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
      . '<sheetData>' . implode('', $sheetRows) . '</sheetData></worksheet>';

    file_put_contents($tmpBase . '/[Content_Types].xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      . '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
      . '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
      . '<Default Extension="xml" ContentType="application/xml"/>'
      . '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
      . '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
      . '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
      . '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'
      . '</Types>');
    file_put_contents($tmpBase . '/docProps/app.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>HM ERP RRHH</Application></Properties>');
    file_put_contents($tmpBase . '/docProps/core.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:title>Reporte</dc:title></cp:coreProperties>');
    file_put_contents($tmpBase . '/_rels/.rels', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
      . '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
      . '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>'
      . '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>'
      . '</Relationships>');
    file_put_contents($tmpBase . '/xl/workbook.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      . '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
      . '<sheets><sheet name="Reporte" sheetId="1" r:id="rId1"/></sheets></workbook>');
    file_put_contents($tmpBase . '/xl/_rels/workbook.xml.rels', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
      . '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
      . '</Relationships>');
    file_put_contents($tmpBase . '/xl/worksheets/sheet1.xml', $sheetXml);

    $zipPath = sys_get_temp_dir() . '/' . uniqid('hmxlsx_') . '.xlsx';
    $zip = new ZipArchive();
    $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
    $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tmpBase, FilesystemIterator::SKIP_DOTS));
    foreach ($it as $file) {
        $filePath = $file->getPathname();
        $local = substr($filePath, strlen($tmpBase) + 1);
        $zip->addFile($filePath, $local);
    }
    $zip->close();

    header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Content-Length: ' . filesize($zipPath));
    readfile($zipPath);
    @unlink($zipPath);
    exit;
}
