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.
193 lines
7.2 KiB
PHP
193 lines
7.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../src/Auth.php';
|
|
require_once __DIR__ . '/../../src/Db.php';
|
|
require_once __DIR__ . '/../../src/AuditLog.php';
|
|
|
|
if (($_GET['action'] ?? '') === 'logout') {
|
|
header('Location: /auth/logout.php');
|
|
exit;
|
|
}
|
|
|
|
Auth::requireAuth();
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
|
|
|
function ok($data = null): void {
|
|
echo json_encode(['ok' => true, 'data' => $data, 'user' => Auth::user()], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
function err(string $msg, int $code = 400): void {
|
|
http_response_code($code);
|
|
echo json_encode(['ok' => false, 'error' => $msg], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
function body(): array {
|
|
return json_decode((string) file_get_contents('php://input'), true) ?? [];
|
|
}
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
$action = $_GET['action'] ?? '';
|
|
$user = Auth::user();
|
|
$pdo = Db::pdo();
|
|
|
|
try {
|
|
if ($method === 'GET' && $action === 'list') {
|
|
$rows = $pdo->query('SELECT * FROM escrituras_tramites ORDER BY id DESC')->fetchAll();
|
|
ok($rows);
|
|
}
|
|
|
|
if ($method === 'POST' && $action === 'create') {
|
|
$b = body();
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO escrituras_tramites
|
|
(tramite, cliente, abogado, notaria, inscribe, altera_turno, instrucciones,
|
|
fecha_solicitud, estatus, entrada, observaciones, creado_por)
|
|
VALUES
|
|
(:tramite, :cliente, :abogado, :notaria, :inscribe, :altera, :instrucciones,
|
|
NOW(), :estatus, :entrada, :obs, :user)'
|
|
);
|
|
$stmt->execute([
|
|
':tramite' => $b['tramite'] ?? '',
|
|
':cliente' => $b['cliente'] ?? '',
|
|
':abogado' => $b['abogado'] ?? '',
|
|
':notaria' => $b['notaria'] ?? '',
|
|
':inscribe' => $b['inscribe'] ?? 'Sí',
|
|
':altera' => $b['altera_turno'] ?? 'No',
|
|
':instrucciones' => $b['instrucciones'] ?? '',
|
|
':estatus' => $b['estatus'] ?? 'Solicitado',
|
|
':entrada' => $b['entrada'] ?? '',
|
|
':obs' => $b['observaciones'] ?? '',
|
|
':user' => $user['email'],
|
|
]);
|
|
$id = (int) $pdo->lastInsertId();
|
|
|
|
$pdo->prepare(
|
|
"INSERT INTO escrituras_historial
|
|
(tramite_id, modificado_por, campo, valor_anterior, valor_nuevo)
|
|
VALUES (?, ?, 'Trámite', '', 'Creado')"
|
|
)->execute([$id, $user['email']]);
|
|
|
|
$row = $pdo->prepare('SELECT * FROM escrituras_tramites WHERE id = :id');
|
|
$row->execute([':id' => $id]);
|
|
$r = $row->fetch();
|
|
AuditLog::log('escrituras', 'create', 'escritura', (string)$id, $r['tramite'] ?? '');
|
|
ok($r);
|
|
}
|
|
|
|
if ($method === 'PUT' && $action === 'update') {
|
|
$b = body();
|
|
$id = (int) ($b['id'] ?? 0);
|
|
if (!$id) err('ID requerido');
|
|
|
|
$beforeStmt = $pdo->prepare('SELECT * FROM escrituras_tramites WHERE id = :id');
|
|
$beforeStmt->execute([':id' => $id]);
|
|
$before = $beforeStmt->fetch();
|
|
if (!$before) err('Trámite no encontrado', 404);
|
|
|
|
$fields = [
|
|
'tramite' => $b['tramite'] ?? '',
|
|
'cliente' => $b['cliente'] ?? '',
|
|
'abogado' => $b['abogado'] ?? '',
|
|
'notaria' => $b['notaria'] ?? '',
|
|
'inscribe' => $b['inscribe'] ?? 'Sí',
|
|
'altera_turno' => $b['altera_turno'] ?? 'No',
|
|
'instrucciones' => $b['instrucciones'] ?? '',
|
|
'estatus' => $b['estatus'] ?? 'Solicitado',
|
|
'entrada' => $b['entrada'] ?? '',
|
|
'observaciones' => $b['observaciones'] ?? '',
|
|
];
|
|
|
|
$stmt = $pdo->prepare(
|
|
'UPDATE escrituras_tramites SET
|
|
tramite=:tramite, cliente=:cliente, abogado=:abogado, notaria=:notaria,
|
|
inscribe=:inscribe, altera_turno=:altera_turno, instrucciones=:instrucciones,
|
|
estatus=:estatus, entrada=:entrada, observaciones=:observaciones,
|
|
actualizado_por=:actualizado_por
|
|
WHERE id=:id'
|
|
);
|
|
$stmt->execute(array_merge(
|
|
array_combine(
|
|
array_map(fn($k) => ':' . $k, array_keys($fields)),
|
|
array_values($fields)
|
|
),
|
|
[':actualizado_por' => $user['email'], ':id' => $id]
|
|
));
|
|
|
|
$labels = [
|
|
'tramite' => 'Nombre del Trámite',
|
|
'cliente' => 'Cliente',
|
|
'abogado' => 'Abogado Solicitante',
|
|
'notaria' => 'Notaría',
|
|
'inscribe' => 'Se Inscribe',
|
|
'altera_turno' => 'Alt. de Turno',
|
|
'instrucciones' => 'Instrucciones Especiales',
|
|
'estatus' => 'Estatus',
|
|
'entrada' => 'Entrada',
|
|
'observaciones' => 'Observaciones',
|
|
];
|
|
|
|
$hist = $pdo->prepare(
|
|
'INSERT INTO escrituras_historial
|
|
(tramite_id, modificado_por, campo, valor_anterior, valor_nuevo)
|
|
VALUES (:tid, :por, :campo, :ant, :nvo)'
|
|
);
|
|
foreach ($fields as $campo => $nuevo) {
|
|
$anterior = $before[$campo] ?? '';
|
|
if ((string) $anterior !== (string) $nuevo) {
|
|
$hist->execute([
|
|
':tid' => $id,
|
|
':por' => $user['email'],
|
|
':campo' => $labels[$campo] ?? $campo,
|
|
':ant' => $anterior,
|
|
':nvo' => $nuevo,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$row = $pdo->prepare('SELECT * FROM escrituras_tramites WHERE id = :id');
|
|
$row->execute([':id' => $id]);
|
|
$r = $row->fetch();
|
|
AuditLog::log('escrituras', 'update', 'escritura', (string)$id, $r['tramite'] ?? '');
|
|
ok($r);
|
|
}
|
|
|
|
if ($method === 'DELETE' && $action === 'delete') {
|
|
$id = (int) ($_GET['id'] ?? 0);
|
|
if (!$id) err('ID requerido');
|
|
// Fetch name before delete for the log
|
|
$nameStmt = $pdo->prepare('SELECT tramite FROM escrituras_tramites WHERE id = :id');
|
|
$nameStmt->execute([':id' => $id]);
|
|
$tramiteName = (string)($nameStmt->fetchColumn() ?: '');
|
|
$pdo->prepare('DELETE FROM escrituras_tramites WHERE id = :id')->execute([':id' => $id]);
|
|
AuditLog::log('escrituras', 'delete', 'escritura', (string)$id, $tramiteName);
|
|
ok(['deleted' => $id]);
|
|
}
|
|
|
|
if ($method === 'GET' && $action === 'historial') {
|
|
$tid = (int) ($_GET['id'] ?? 0);
|
|
if (!$tid) err('ID requerido');
|
|
$stmt = $pdo->prepare(
|
|
'SELECT * FROM escrituras_historial
|
|
WHERE tramite_id = :tid
|
|
ORDER BY modificado_en DESC
|
|
LIMIT 200'
|
|
);
|
|
$stmt->execute([':tid' => $tid]);
|
|
ok($stmt->fetchAll());
|
|
}
|
|
|
|
err('Acción no reconocida', 404);
|
|
|
|
} catch (PDOException $e) {
|
|
error_log('escrituras db error: ' . $e->getMessage());
|
|
err('Error de base de datos', 500);
|
|
} catch (Throwable $e) {
|
|
error_log('escrituras error: ' . $e->getMessage());
|
|
err('Error interno', 500);
|
|
}
|