<?php
declare(strict_types=1);
$config = require __DIR__ . '/config.php';
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https';
session_name($config['session_name'] ?? 'SIAPA_SYNC_SESSION');
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'secure' => $secure,
    'httponly' => true,
    'samesite' => 'Strict',
]);
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: same-origin');
function respond(array $data, int $status=200): never { http_response_code($status); echo json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit; }
function method(string $expected): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== $expected) respond(['error'=>'Método no permitido.'],405); }
function input_json(int $max): array { $len=(int)($_SERVER['CONTENT_LENGTH']??0); if($len>$max) respond(['error'=>'Carga demasiado grande.'],413); $raw=file_get_contents('php://input'); $data=json_decode($raw?:'{}',true); if(!is_array($data)) respond(['error'=>'JSON inválido.'],400); return $data; }
function csrf_token(): string { if(empty($_SESSION['csrf'])) $_SESSION['csrf']=bin2hex(random_bytes(24)); return $_SESSION['csrf']; }
function require_auth(bool $checkCsrf=false): void { if(empty($_SESSION['authenticated'])) respond(['error'=>'Sesión de sincronización no autenticada.'],401); if($checkCsrf){$sent=$_SERVER['HTTP_X_CSRF_TOKEN']??''; if(!$sent||!hash_equals((string)($_SESSION['csrf']??''),$sent)) respond(['error'=>'Token CSRF inválido.'],403);} }
function storage_paths(array $config): array { $dir=$config['storage_dir']; if(!is_dir($dir)&&!mkdir($dir,0770,true)&&!is_dir($dir)) respond(['error'=>'No se pudo crear almacenamiento.'],500); $hist=$dir.'/history'; if(!is_dir($hist)) @mkdir($hist,0770,true); return [$dir,$dir.'/siapa-state.json',$dir.'/siapa.lock',$hist]; }
function blank_state(): array { return ['revision'=>0,'checksum'=>null,'updated_at'=>null,'device_id'=>null,'payload'=>null]; }
function read_state(string $file): array { if(!is_file($file)) return blank_state(); $raw=file_get_contents($file); $d=json_decode($raw?:'',true); return is_array($d)?array_merge(blank_state(),$d):blank_state(); }
function validate_payload(mixed $p): array { if(!is_array($p)||!isset($p['db'])||!is_array($p['db'])||!isset($p['db']['estudiantes'],$p['db']['docentes'])||!is_array($p['db']['estudiantes'])||!is_array($p['db']['docentes'])) respond(['error'=>'La carga no contiene una estructura SIAPA válida.'],422); return $p; }
function checksum_payload(array $payload): string { return hash('sha256',json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)); }
function prune_history(string $dir,int $limit): void { $files=glob($dir.'/revision-*.json')?:[]; usort($files,fn($a,$b)=>filemtime($b)<=>filemtime($a)); foreach(array_slice($files,$limit) as $f) @unlink($f); }
$script = basename((string)($_SERVER['SCRIPT_NAME'] ?? ''));
if (!is_file(__DIR__ . '/config.override.php') && $script !== 'health.php') {
    respond(['error' => 'SIAPA no está instalado en este servidor. Abra instalar.php para crear la configuración institucional.'], 503);
}
