<?php
/* AGESCONT - API con MySQL + sesión server-side.
   Reemplaza el store JSON. Mantiene el contrato del front (getAll/get/save/sync/login/...).
   Seguridad: TODOS los endpoints de datos exigen sesión iniciada. */
session_start();
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit(); }

$cfg = require __DIR__ . '/db_config.php';
try {
    $pdo = new PDO("mysql:host={$cfg['host']};dbname={$cfg['dbname']};charset=utf8mb4", $cfg['user'], $cfg['pass'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
} catch (Exception $e) { http_response_code(500); echo json_encode(['success'=>false,'message'=>'DB error']); exit(); }

function jresp($ok, $data = null, $msg = '', $code = 200) {
    http_response_code($code);
    echo json_encode(['success'=>$ok,'data'=>$data,'message'=>$msg,'timestamp'=>date('Y-m-d H:i:s')], JSON_UNESCAPED_UNICODE);
    exit();
}
function input() { $d = json_decode(file_get_contents('php://input'), true); return $d === null ? null : $d; }
function requireAuth() { if (empty($_SESSION['uid'])) jresp(false, null, 'No autenticado', 401); }
function d($v) { $v = trim((string)($v ?? '')); return $v === '' ? null : substr($v, 0, 10); }
function num($v) { return is_numeric($v) ? (float)$v : null; }
$J = fn($x) => json_encode($x, JSON_UNESCAPED_UNICODE);

// Función para registrar en audit_log
function audit($pdo, $accion, $entidad, $entidadId = null, $descripcion = null, $datosAnteriores = null, $datosNuevos = null) {
    $uid = $_SESSION["uid"] ?? null;
    $nombre = $_SESSION["nombre"] ?? $_SESSION["usuario"] ?? "Sistema";
    $ip = $_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"] ?? null;
    $st = $pdo->prepare("INSERT INTO audit_log (usuario_id, usuario_nombre, accion, entidad, entidad_id, descripcion, datos_anteriores, datos_nuevos, ip) VALUES (?,?,?,?,?,?,?,?,?)");
    $st->execute([
        $uid,
        $nombre,
        $accion,
        $entidad,
        $entidadId,
        $descripcion,
        $datosAnteriores ? json_encode($datosAnteriores, JSON_UNESCAPED_UNICODE) : null,
        $datosNuevos ? json_encode($datosNuevos, JSON_UNESCAPED_UNICODE) : null,
        $ip
    ]);
}

// --- extractores de columnas indexadas por tipo ---
function cols($type, $r) {
    switch ($type) {
        case 'clientes': return [$r['id'] ?? null, $r['nit'] ?? null, $r['razonSocial'] ?? null, $r['estado'] ?? null];
        case 'pagos':    return [(string)($r['id'] ?? ''), $r['clienteId'] ?? null, $r['concepto'] ?? null, num($r['monto'] ?? null), $r['estado'] ?? null, d($r['fecha'] ?? ''), d($r['fechaVencimiento'] ?? ''), $r['metodoPago'] ?? null, $r['refYape'] ?? null];
        case 'gastos':   return [$r['id'] ?? null, d($r['fecha'] ?? ''), num($r['importe'] ?? null), $r['metodo'] ?? null];
        case 'trabajos': return [$r['id'] ?? null, $r['clienteId'] ?? null, $r['estado'] ?? null];
    }
    return [];
}
$INSERT = [
    'clientes' => "INSERT INTO clientes (id,nit,razon_social,estado,data) VALUES (?,?,?,?,?)",
    'pagos'    => "INSERT INTO pagos (id,cliente_id,concepto,monto,estado,fecha,fecha_vencimiento,metodo_pago,ref_yape,data) VALUES (?,?,?,?,?,?,?,?,?,?)",
    'gastos'   => "INSERT INTO gastos (id,fecha,importe,metodo,data) VALUES (?,?,?,?,?)",
    'trabajos' => "INSERT INTO trabajos (id,cliente_id,estado,data) VALUES (?,?,?,?)",
];

function replaceType($pdo, $J, $INSERT, $type, $rows) {
    if (!isset($INSERT[$type]) || !is_array($rows)) return;
    $pdo->exec("DELETE FROM $type");
    $st = $pdo->prepare($INSERT[$type]);
    foreach ($rows as $r) { $c = $r; unset($c['_pk']); $st->execute(array_merge(cols($type, $r), [$J($c)])); }
}
function replaceControl($pdo, $J, $obj) {
    $pdo->exec("DELETE FROM control_mensual");
    if (!is_array($obj)) return;
    $st = $pdo->prepare("INSERT INTO control_mensual (clave,data) VALUES (?,?)");
    foreach ($obj as $k => $v) { $st->execute([$k, $J($v)]); }
}
function readType($pdo, $type) {
    $out = [];
    if ($type === 'pagos') {
        foreach ($pdo->query("SELECT pk, data FROM pagos ORDER BY pk") as $row) {
            $o = json_decode($row['data'], true); if (!is_array($o)) $o = [];
            $o['_pk'] = (int)$row['pk']; $out[] = $o;
        }
        return $out;
    }
    foreach ($pdo->query("SELECT data FROM $type ORDER BY id") as $row) $out[] = json_decode($row['data'], true);
    return $out;
}
function readControl($pdo) {
    $out = [];
    foreach ($pdo->query("SELECT clave,data FROM control_mensual") as $row) $out[$row['clave']] = json_decode($row['data'], true);
    return $out ?: new stdClass();
}

$action = $_GET['action'] ?? '';
$type = $_GET['type'] ?? '';
$valid = ['clientes','pagos','trabajos','gastos'];

try {
    switch ($action) {

        case 'login':
            $c = input();
            if (!$c || empty($c['usuario']) || !isset($c['password'])) jresp(false, null, 'Usuario y contraseña requeridos', 400);
            $st = $pdo->prepare("SELECT * FROM usuarios WHERE usuario = ?");
            $st->execute([$c['usuario']]);
            $u = $st->fetch();
            if ($u && password_verify($c['password'], $u['password'])) {
                $_SESSION['uid'] = $u['id']; $_SESSION['usuario'] = $u['usuario']; $_SESSION['nombre'] = $u['nombre'];
                audit($pdo, 'LOGIN', 'sesion', $u['id'], "Inicio de sesión: {$u['nombre']} ({$u['usuario']})");
                jresp(true, ['usuario'=>$u['usuario'],'nombre'=>$u['nombre']], 'Login exitoso');
            }
            audit($pdo, 'LOGIN_FALLIDO', 'sesion', null, "Intento fallido para usuario: {$c['usuario']}");
            jresp(false, null, 'Usuario o contraseña incorrectos', 401);
            break;

        case 'logout':
            $uid = $_SESSION['uid'] ?? null;
            $nombre = $_SESSION['nombre'] ?? 'Desconocido';
            audit($pdo, 'LOGOUT', 'sesion', $uid, "Cierre de sesión: $nombre");
            $_SESSION = []; session_destroy();
            jresp(true, null, 'Sesión cerrada');
            break;

        case 'me':
            if (empty($_SESSION['uid'])) jresp(false, null, 'No autenticado', 401);
            jresp(true, ['usuario'=>$_SESSION['usuario'],'nombre'=>$_SESSION['nombre']]);
            break;

        case 'getAll':
            requireAuth();
            jresp(true, [
                'clientes' => readType($pdo, 'clientes'),
                'pagos' => readType($pdo, 'pagos'),
                'trabajos' => readType($pdo, 'trabajos'),
                'gastos' => readType($pdo, 'gastos'),
                'controlMensual' => readControl($pdo),
            ], 'Datos cargados');
            break;

        case 'get':
            requireAuth();
            if ($type === 'controlMensual') jresp(true, readControl($pdo));
            if (!in_array($type, $valid)) jresp(false, null, 'Tipo no válido', 400);
            jresp(true, readType($pdo, $type));
            break;

        case 'save':
            requireAuth();
            $data = input();
            if ($data === null) jresp(false, null, 'No se recibieron datos', 400);
            $pdo->beginTransaction();
            if ($type === 'controlMensual') { replaceControl($pdo, $J, $data); audit($pdo, 'SYNC', 'controlMensual', null, 'Sincronización de control mensual'); }
            elseif (in_array($type, $valid)) { replaceType($pdo, $J, $INSERT, $type, $data); audit($pdo, 'SYNC', $type, null, "Sincronización completa de $type (" . count($data) . " registros)"); }
            else { $pdo->rollBack(); jresp(false, null, 'Tipo no válido', 400); }
            $pdo->commit();
            jresp(true, null, "Guardado $type");
            break;

        case 'sync':
            requireAuth();
            $all = input();
            if (!$all) jresp(false, null, 'No se recibieron datos', 400);
            $pdo->beginTransaction();
            foreach ($valid as $t) if (isset($all[$t])) replaceType($pdo, $J, $INSERT, $t, $all[$t]);
            if (isset($all['controlMensual'])) replaceControl($pdo, $J, $all['controlMensual']);
            audit($pdo, 'SYNC', 'todos', null, 'Sincronización completa de datos: ' . implode(', ', array_keys($all)));
            $pdo->commit();
            jresp(true, array_keys($all), 'Sincronizado');
            break;

        // --- FASE 2: operaciones por registro (para conciliación Yape y ediciones puntuales) ---
        case 'savePago': // crear (sin _pk) o editar (con _pk) UN pago
            requireAuth();
            $p = input(); if (!$p) jresp(false, null, 'Falta pago', 400);
            $c = $p; unset($c['_pk']);
            if (!empty($p['_pk'])) {
                // Obtener datos anteriores para auditoría
                $stOld = $pdo->prepare("SELECT data FROM pagos WHERE pk = ?");
                $stOld->execute([(int)$p['_pk']]);
                $oldData = $stOld->fetch();
                $oldPago = $oldData ? json_decode($oldData['data'], true) : null;

                $st = $pdo->prepare("UPDATE pagos SET id=?,cliente_id=?,concepto=?,monto=?,estado=?,fecha=?,fecha_vencimiento=?,metodo_pago=?,ref_yape=?,data=? WHERE pk=?");
                $st->execute(array_merge(cols('pagos', $p), [$J($c), (int)$p['_pk']]));
                $c['_pk'] = (int)$p['_pk'];
                audit($pdo, 'EDITAR', 'pagos', $c['_pk'], "Pago editado: {$p['concepto']} - {$p['monto']}", $oldPago, $c);
            } else {
                $pdo->prepare($INSERT['pagos'])->execute(array_merge(cols('pagos', $p), [$J($c)]));
                $c['_pk'] = (int)$pdo->lastInsertId();
                audit($pdo, 'CREAR', 'pagos', $c['_pk'], "Pago creado: {$p['concepto']} - {$p['monto']}", null, $c);
            }
            jresp(true, $c, 'Pago guardado');
            break;
        case 'insertPagos': // inserta un lote de pagos NUEVOS (para "Generar pagos del mes")
            requireAuth();
            $arr = input(); if (!is_array($arr)) jresp(false, null, 'Falta lista', 400);
            $pdo->beginTransaction();
            $st = $pdo->prepare($INSERT['pagos']); $res = [];
            foreach ($arr as $p) { $c = $p; unset($c['_pk']); $st->execute(array_merge(cols('pagos', $p), [$J($c)])); $c['_pk'] = (int)$pdo->lastInsertId(); $res[] = $c; }
            audit($pdo, 'CREAR_LOTE', 'pagos', null, 'Generación de pagos del mes: ' . count($res) . ' registros');
            $pdo->commit();
            jresp(true, $res, 'Pagos insertados: ' . count($res));
            break;
        case 'deletePagos': // elimina SOLO los pk indicados
            requireAuth();
            $pks = array_values(array_filter(array_map('intval', (input()['pks'] ?? []))));
            if (!$pks) jresp(false, null, 'Sin selección', 400);
            $ph = implode(',', array_fill(0, count($pks), '?'));
            audit($pdo, 'ELIMINAR', 'pagos', null, 'Pagos eliminados: ' . count($pks) . ' registros');
            $pdo->prepare("DELETE FROM pagos WHERE pk IN ($ph)")->execute($pks);
            jresp(true, ['eliminados'=>count($pks)], 'Eliminados: ' . count($pks));
            break;

        // --- Salud de datos: auditoría + reparación ---
        case 'dataIssues':
            requireAuth();
            $orphans = $pdo->query("SELECT p.pk,p.id,p.cliente_id,p.concepto,p.monto,p.estado,p.fecha FROM pagos p LEFT JOIN clientes c ON p.cliente_id=c.id WHERE p.cliente_id IS NOT NULL AND c.id IS NULL ORDER BY p.cliente_id,p.fecha")->fetchAll();
            $externos = $pdo->query("SELECT pk,id,concepto,monto,estado,fecha, JSON_UNQUOTE(JSON_EXTRACT(data,'$.clienteExterno')) ext FROM pagos WHERE cliente_id IS NULL ORDER BY fecha DESC")->fetchAll();
            $sinMetodo = $pdo->query("SELECT pk,id,cliente_id,concepto,monto,estado,fecha FROM pagos WHERE estado='Pagado' AND (metodo_pago IS NULL OR metodo_pago='') ORDER BY fecha DESC")->fetchAll();
            $clientes = $pdo->query("SELECT id,razon_social FROM clientes ORDER BY razon_social")->fetchAll();
            jresp(true, ['orphans'=>$orphans,'externos'=>$externos,'sinMetodo'=>$sinMetodo,'clientes'=>$clientes], 'ok');
            break;
        case 'resolvePagos':
            requireAuth();
            $in = input(); $pks = array_values(array_filter(array_map('intval', $in['pks'] ?? []))); $act = $in['action'] ?? '';
            if (!$pks) jresp(false, null, 'Sin selección', 400);
            $ph = implode(',', array_fill(0, count($pks), '?'));
            $pdo->beginTransaction();
            if ($act === 'reassign') {
                $cid = (int)($in['clienteId'] ?? 0); if (!$cid) { $pdo->rollBack(); jresp(false, null, 'Falta cliente', 400); }
                $row = $pdo->query("SELECT razon_social FROM clientes WHERE id=$cid")->fetch(); $nombre = $row ? $row['razon_social'] : '';
                $pdo->prepare("UPDATE pagos SET cliente_id=?, data=JSON_SET(data,'$.clienteId',?,'$.clienteNombre',?,'$.clienteExterno',NULL) WHERE pk IN ($ph)")->execute(array_merge([$cid,$cid,$nombre],$pks));
                audit($pdo, 'REASIGNAR', 'pagos', null, "Pagos reasignados a cliente $nombre: " . count($pks) . " registros");
            } elseif ($act === 'setMetodo') {
                $m = $in['metodo'] ?? ''; $pdo->prepare("UPDATE pagos SET metodo_pago=?, data=JSON_SET(data,'$.metodoPago',?) WHERE pk IN ($ph)")->execute(array_merge([$m,$m],$pks));
                audit($pdo, 'EDITAR', 'pagos', null, "Método de pago actualizado a '$m': " . count($pks) . " registros");
            } elseif ($act === 'markExterno') {
                $nombre = $in['nombre'] ?? 'Externo'; $pdo->prepare("UPDATE pagos SET cliente_id=NULL, data=JSON_SET(data,'$.clienteId',NULL,'$.clienteExterno',?) WHERE pk IN ($ph)")->execute(array_merge([$nombre],$pks));
                audit($pdo, 'EDITAR', 'pagos', null, "Pagos marcados como externos '$nombre': " . count($pks) . " registros");
            } elseif ($act === 'delete') {
                $pdo->prepare("DELETE FROM pagos WHERE pk IN ($ph)")->execute($pks);
                audit($pdo, 'ELIMINAR', 'pagos', null, "Pagos eliminados desde resolución: " . count($pks) . " registros");
            } else { $pdo->rollBack(); jresp(false, null, 'Acción inválida', 400); }
            $pdo->commit();
            jresp(true, ['afectados'=>count($pks)], 'Aplicado a ' . count($pks));
            break;
        case 'reportResumen':
            requireAuth();
            $deuda = $pdo->query("SELECT c.id,c.razon_social, COALESCE(SUM(CASE WHEN p.estado<>'Pagado' THEN p.monto ELSE 0 END),0) deuda, COALESCE(SUM(CASE WHEN p.estado='Pagado' THEN p.monto ELSE 0 END),0) pagado FROM clientes c LEFT JOIN pagos p ON p.cliente_id=c.id GROUP BY c.id,c.razon_social ORDER BY deuda DESC")->fetchAll();
            $porMes = $pdo->query("SELECT DATE_FORMAT(fecha,'%Y-%m') mes, COALESCE(SUM(CASE WHEN estado='Pagado' THEN monto ELSE 0 END),0) ingresos, COUNT(*) n FROM pagos WHERE fecha IS NOT NULL GROUP BY mes ORDER BY mes DESC LIMIT 24")->fetchAll();
            $porMetodo = $pdo->query("SELECT COALESCE(NULLIF(metodo_pago,''),'(sin método)') metodo, COUNT(*) n, COALESCE(SUM(monto),0) total FROM pagos WHERE estado='Pagado' GROUP BY metodo ORDER BY total DESC")->fetchAll();
            jresp(true, ['deuda'=>$deuda,'porMes'=>$porMes,'porMetodo'=>$porMetodo], 'ok');
            break;
        case 'saveCliente':
            requireAuth();
            $c = input(); if (!$c || empty($c['id'])) jresp(false, null, 'Falta cliente', 400);
            // Verificar si existe para saber si es crear o editar
            $stCheck = $pdo->prepare("SELECT data FROM clientes WHERE id = ?");
            $stCheck->execute([$c['id']]);
            $existing = $stCheck->fetch();
            $oldData = $existing ? json_decode($existing['data'], true) : null;

            $st = $pdo->prepare("INSERT INTO clientes (id,nit,razon_social,estado,data) VALUES (?,?,?,?,?)
                ON DUPLICATE KEY UPDATE nit=VALUES(nit),razon_social=VALUES(razon_social),estado=VALUES(estado),data=VALUES(data)");
            $st->execute(array_merge(cols('clientes', $c), [$J($c)]));

            if ($existing) {
                audit($pdo, 'EDITAR', 'clientes', $c['id'], "Cliente editado: {$c['razonSocial']}", $oldData, $c);
            } else {
                audit($pdo, 'CREAR', 'clientes', $c['id'], "Cliente creado: {$c['razonSocial']}", null, $c);
            }
            jresp(true, $c, 'Cliente guardado');
            break;
        case 'deleteCliente':
            requireAuth();
            $id = $_GET['id'] ?? (input()['id'] ?? null); if (!$id) jresp(false, null, 'Falta id', 400);
            // Obtener nombre antes de eliminar
            $stName = $pdo->prepare("SELECT razon_social FROM clientes WHERE id = ?");
            $stName->execute([$id]);
            $row = $stName->fetch();
            $nombre = $row ? $row['razon_social'] : 'Desconocido';

            $pdo->prepare("DELETE FROM clientes WHERE id = ?")->execute([$id]);
            audit($pdo, 'ELIMINAR', 'clientes', $id, "Cliente eliminado: $nombre");
            jresp(true, null, 'Cliente eliminado');
            break;

        case 'backup':
            requireAuth();
            audit($pdo, 'BACKUP', 'sistema', null, 'Backup de datos generado');
            jresp(true, ['version'=>'3.0','fecha'=>date('Y-m-d H:i:s'),'datos'=>[
                'clientes'=>readType($pdo,'clientes'),'pagos'=>readType($pdo,'pagos'),
                'trabajos'=>readType($pdo,'trabajos'),'gastos'=>readType($pdo,'gastos'),
                'controlMensual'=>readControl($pdo)]], 'Backup generado');
            break;

        case 'restore':
            requireAuth();
            $b = input(); if (!$b || !isset($b['datos'])) jresp(false, null, 'Backup inválido', 400);
            $pdo->beginTransaction();
            foreach ($valid as $t) if (isset($b['datos'][$t])) replaceType($pdo, $J, $INSERT, $t, $b['datos'][$t]);
            if (isset($b['datos']['controlMensual'])) replaceControl($pdo, $J, $b['datos']['controlMensual']);
            audit($pdo, 'RESTORE', 'sistema', null, 'Backup restaurado');
            $pdo->commit();
            jresp(true, null, 'Backup restaurado');
            break;

        case 'changePassword':
            requireAuth();
            $data = input();
            if (!$data || empty($data['currentPassword']) || empty($data['newPassword'])) jresp(false, null, 'Datos incompletos', 400);
            $st = $pdo->prepare("SELECT * FROM usuarios WHERE id = ?"); $st->execute([$_SESSION['uid']]); $u = $st->fetch();
            if (!$u || !password_verify($data['currentPassword'], $u['password'])) jresp(false, null, 'Contraseña actual incorrecta', 401);
            $pdo->prepare("UPDATE usuarios SET password = ? WHERE id = ?")->execute([password_hash($data['newPassword'], PASSWORD_DEFAULT), $_SESSION['uid']]);
            audit($pdo, 'CAMBIO_PASSWORD', 'usuarios', $_SESSION['uid'], "Cambio de contraseña");
            jresp(true, null, 'Contraseña actualizada');
            break;

        case 'createUser':
            requireAuth();
            $data = input();
            if (!$data || empty($data['usuario']) || empty($data['password'])) jresp(false, null, 'Datos requeridos', 400);
            try {
                $st = $pdo->prepare("INSERT INTO usuarios (usuario,password,nombre,fecha_creacion) VALUES (?,?,?,NOW())");
                $st->execute([$data['usuario'], password_hash($data['password'], PASSWORD_DEFAULT), $data['nombre'] ?? $data['usuario']]);
                audit($pdo, 'CREAR', 'usuarios', $pdo->lastInsertId(), "Usuario creado: {$data['usuario']}");
                jresp(true, ['usuario'=>$data['usuario']], 'Usuario creado', 201);
            } catch (Exception $e) { jresp(false, null, 'El usuario ya existe', 409); }
            break;

        case 'dashboard':
            requireAuth();
            $mes = date('Y-m');
            $tot = $pdo->query("SELECT COUNT(*) c FROM clientes")->fetch()['c'];
            $ing = $pdo->query("SELECT COALESCE(SUM(monto),0) s FROM pagos WHERE estado='Pagado' AND DATE_FORMAT(fecha,'%Y-%m')='$mes'")->fetch()['s'];
            $ade = $pdo->query("SELECT COALESCE(SUM(monto),0) s FROM pagos WHERE estado<>'Pagado'")->fetch()['s'];
            $trp = $pdo->query("SELECT COUNT(*) c FROM trabajos WHERE estado<>'Completado'")->fetch()['c'];
            jresp(true, ['totalClientes'=>(int)$tot,'ingresosMes'=>(float)$ing,'totalAdeudos'=>(float)$ade,'trabajosPendientes'=>(int)$trp]);
            break;

        // --- HISTORIAL DE AUDITORÍA ---
        case 'getAuditLog':
            requireAuth();
            $limit = (int)($_GET['limit'] ?? 100);
            $offset = (int)($_GET['offset'] ?? 0);
            $usuario = $_GET['usuario'] ?? null;
            $entidad = $_GET['entidad'] ?? null;
            $desde = $_GET['desde'] ?? null;
            $hasta = $_GET['hasta'] ?? null;

            $where = [];
            $params = [];
            if ($usuario) { $where[] = "usuario_nombre LIKE ?"; $params[] = "%$usuario%"; }
            if ($entidad) { $where[] = "entidad = ?"; $params[] = $entidad; }
            if ($desde) { $where[] = "fecha >= ?"; $params[] = $desde; }
            if ($hasta) { $where[] = "fecha <= ?"; $params[] = "$hasta 23:59:59"; }

            $whereSQL = count($where) ? "WHERE " . implode(" AND ", $where) : "";
            $sql = "SELECT * FROM audit_log $whereSQL ORDER BY fecha DESC LIMIT $limit OFFSET $offset";
            $st = $pdo->prepare($sql);
            $st->execute($params);
            $logs = $st->fetchAll();

            // Decodificar JSON de datos
            foreach ($logs as &$log) {
                $log['datos_anteriores'] = $log['datos_anteriores'] ? json_decode($log['datos_anteriores'], true) : null;
                $log['datos_nuevos'] = $log['datos_nuevos'] ? json_decode($log['datos_nuevos'], true) : null;
            }

            // Total para paginación
            $countSQL = "SELECT COUNT(*) c FROM audit_log $whereSQL";
            $stCount = $pdo->prepare($countSQL);
            $stCount->execute($params);
            $total = $stCount->fetch()['c'];

            jresp(true, ['logs'=>$logs, 'total'=>(int)$total, 'limit'=>$limit, 'offset'=>$offset], 'ok');
            break;

        default:
            jresp(false, null, 'Acción no válida', 400);
    }
} catch (Exception $e) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    jresp(false, null, 'Error del servidor: ' . $e->getMessage(), 500);
}
