feat(admin): Registro de transacciones — log de auditoría de todo el sistema
NUEVA INFRAESTRUCTURA * SQL 010: tabla audit_log (id, fecha, user_email, user_name, modulo, accion, entity_type, entity_id, entity_label, detalles JSON, ip_addr) con índices por fecha/user/modulo/accion/entity. * src/AuditLog.php: helper estático con dos métodos: - log() lee actor de $_SESSION (uso normal post-auth) - logAs() recibe email/name explícito (para login/logout y CLI) Falla silenciosamente si la DB no responde — no rompe la operación principal por un fallo de logging. HOOKS EN MUTACIONES * Auth.php: registra 'login' al completar OAuth y 'logout' antes de destruir la sesión. * escrituras/api.php: registra create/update/delete con id+nombre. * tramites/api.php: registra 8 mutaciones — create/update/delete del trámite, set_status (con nuevo estatus en detalles), add_obs, add_hours/update_hours/delete_hours (con abogado y horas en detalles), todos referenciados por codigo (ADM-001, etc.). * admin/api.php: registra create_user/update_user/delete_user, con diff de cambios en update_user. * GETs (list_*) no se loguean — serían ruido. LECTOR * admin/api.php nuevo endpoint GET ?action=list_log que devuelve entradas con filtros from/to/user/modulo/accion/search (LIKE) + catálogos para dropdowns. Límite 500 por query. UI EN PANEL DE CONTROL * admin.html ahora tiene tabs: "Usuarios" (existente) y "Registro de transacciones" (nuevo). * Tab Registro: toolbar de filtros (rango fechas, usuario, módulo, acción, búsqueda libre, atajos Hoy/Semana/Mes/Todo) + tabla con Fecha·Hora · Usuario · Módulo (pill coloreado) · Acción · Entidad con label y tipo · Detalles JSON resumido.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/Db.php';
|
||||
|
||||
/**
|
||||
* AuditLog — helper estático para registrar transacciones en audit_log.
|
||||
* Diseñado para fallar silenciosamente: si por alguna razón no se puede
|
||||
* loguear, no debe bloquear la operación principal.
|
||||
*/
|
||||
final class AuditLog {
|
||||
public static function log(
|
||||
string $modulo,
|
||||
string $accion,
|
||||
?string $entityType = null,
|
||||
?string $entityId = null,
|
||||
?string $entityLabel = null,
|
||||
?array $detalles = null
|
||||
): void {
|
||||
$email = $_SESSION['user_email'] ?? '';
|
||||
$name = $_SESSION['user_name'] ?? '';
|
||||
self::logAs($email, $name, $modulo, $accion, $entityType, $entityId, $entityLabel, $detalles);
|
||||
}
|
||||
|
||||
/** Para casos donde el actor no está en $_SESSION (ej. login mismo). */
|
||||
public static function logAs(
|
||||
string $email,
|
||||
string $name,
|
||||
string $modulo,
|
||||
string $accion,
|
||||
?string $entityType = null,
|
||||
?string $entityId = null,
|
||||
?string $entityLabel = null,
|
||||
?array $detalles = null
|
||||
): void {
|
||||
try {
|
||||
$stmt = Db::pdo()->prepare(
|
||||
'INSERT INTO audit_log
|
||||
(user_email, user_name, modulo, accion, entity_type, entity_id, entity_label, detalles, ip_addr)
|
||||
VALUES
|
||||
(:email, :name, :modulo, :accion, :etype, :eid, :elabel, :det, :ip)'
|
||||
);
|
||||
$stmt->execute([
|
||||
':email' => $email,
|
||||
':name' => $name,
|
||||
':modulo' => $modulo,
|
||||
':accion' => $accion,
|
||||
':etype' => $entityType,
|
||||
':eid' => $entityId === null ? null : (string)$entityId,
|
||||
':elabel' => $entityLabel,
|
||||
':det' => $detalles !== null ? json_encode($detalles, JSON_UNESCAPED_UNICODE) : null,
|
||||
':ip' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('AuditLog failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/Config.php';
|
||||
require_once __DIR__ . '/Db.php';
|
||||
require_once __DIR__ . '/AuditLog.php';
|
||||
|
||||
final class Auth {
|
||||
/** Cache por request del row de users del usuario autenticado */
|
||||
@@ -182,11 +183,18 @@ final class Auth {
|
||||
$_SESSION['user_name'] = $name ?: $email;
|
||||
$_SESSION['auth_time'] = time();
|
||||
self::$userRowCache = null; // invalidate cache so next userRow() call re-fetches
|
||||
|
||||
AuditLog::logAs($email, $name ?: $email, 'auth', 'login', 'user', null, $email);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function logout(): void {
|
||||
self::startSession();
|
||||
$email = $_SESSION['user_email'] ?? '';
|
||||
$name = $_SESSION['user_name'] ?? '';
|
||||
if ($email !== '') {
|
||||
AuditLog::logAs($email, $name, 'auth', 'logout', 'user', null, $email);
|
||||
}
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$p = session_get_cookie_params();
|
||||
|
||||
Reference in New Issue
Block a user