Files
donna/public/escrituras/api.php
T
Soporte ZH Consultores cd7595832f 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.
2026-06-04 20:30:46 -05:00

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);
}