Auditoría Forense Asíncrona (MongoDB)
Para garantizar la inmutabilidad de los registros históricos y evitar la saturación de I/O en el motor relacional primario (MariaDB), Mañuti implementa un subsistema de auditoría no bloqueante sobre MongoDB 7.0, respaldado por colas en memoria con Redis y supervisado por Laravel Horizon.
1. Arquitectura de Desacoplamiento (Cero Bloqueo HTTP)
La persistencia de auditoría opera fuera del ciclo de vida síncrono de la petición del cliente. El servidor entrega la respuesta HTTP inmediatamente, delegando la serialización del payload a un worker en segundo plano:
flowchart TD
CLIENTE["[Cliente HTTP]"]
MIDDLEWARE["[AuditMiddleware]"]
CONTROLADOR["[Controlador / Lógica]"]
RESPUESTA["[HTTP 200/302 OK]<br/>(Respuesta Inmediata)"]
REDIS["[Redis Queue: ProcessAuditLog]"]
WORKER["[Worker Horizon Daemon]"]
MONGO["[MongoDB: Colección 'auditoria']"]
CLIENTE --> MIDDLEWARE
MIDDLEWARE --> CONTROLADOR
CONTROLADOR --> RESPUESTA
MIDDLEWARE -->|"Captura $userBefore / $userAfter<br/>Captura Response Status<br/>Sanitiza Payload"| REDIS
REDIS --> WORKER
WORKER --> MONGO
2. Diccionario del Documento BSON (auditoria)
La colección reside en la base de datos no relacional configurada (DB_MONGO_DATABASE=auditoria_db) y mapeada mediante la clase App\Models\AuditLog que extiende de MongoDB\Laravel\Eloquent\Model.
| Campo BSON | Tipo de Dato | Definición Técnica |
|---|---|---|
_id |
ObjectId |
Identificador único hexadecimal nativo de MongoDB. |
usuario_id |
Int64 / Null |
Puntero numérico hacia la tabla usuarios de MariaDB. Null para invitados. |
usuario_nombre |
String |
Snapshot estático del identificador de usuario (usuario) en el instante del evento. Preserva la autoría si el registro relacional es anonimizado o borrado. |
permiso |
String |
Identificador de la acción ejecutada (e.g., users.create, sistema_bloqueo_usuario, login_fallido). |
url |
String |
URI absoluta solicitada, incluyendo parámetros de consulta (Query Strings). |
metodo |
String |
Verbo de la petición HTTP (GET, POST, PUT, DELETE). |
ip |
String |
Dirección IP del cliente, resuelta a través de proxies de confianza. |
user_agent |
String |
Cadena completa del agente de navegación para análisis de contexto. |
status |
Int32 |
Código de estado HTTP retornado por la respuesta (e.g., 200, 302, 403, 500). |
payload |
Document (JSON) |
Conjunto de datos enviados en el request, sometido a sanitización recursiva. |
created_at |
ISODate |
Marca de tiempo UTC indexada de alta precisión. |
3. Sanitización de Datos Sensibles & Blindaje de Archivos
El middleware implementa la eliminación preventiva de secretos de autenticación e interrumpe la serialización accidental de binarios:
// app/Http/Middleware/AuditMiddleware.php
private function cleanPayload(array $data): array
{
foreach ($data as $key => $value) {
if ($value instanceof \Illuminate\Http\UploadedFile) {
$data[$key] = [
'filename' => $value->getClientOriginalName(),
'mime_type' => $value->getMimeType(),
'size_kb' => round($value->getSize() / 1024, 2),
'_type' => 'file_upload'
];
} elseif (is_array($value)) {
$data[$key] = $this->cleanPayload($value);
}
}
return $data;
}
- Campos depurados de raíz:
password,password_confirmation,current_password,_token,_method. - Esto erradica la excepción crítica
Serialization of 'Illuminate\Http\UploadedFile' is not allowedcuando los usuarios actualizan avatares o adjuntan documentos.
4. Estrategia de Invocación: Automática vs. Manual
Invocación Automática (Rutas Protegidas por RBAC)
El AuditMiddleware inspecciona las directivas asignadas a la ruta. Si detecta la presencia del middleware can:slug.permiso, consulta en memoria (o en caché de Redis) si dicho slug tiene activada la bandera auditable = 1 en MariaDB:
Route::middleware(['auth', 'no-cache', 'auditar'])->group(function () {
Route::post('/usuarios', [UserController::class, 'store'])
->middleware('can:users.create');
});
Filtro de Reducción de Ruido: El middleware omite automáticamente peticiones GET dirigidas exclusivamente a renderizar formularios vacíos (.create, .edit), registrando únicamente transacciones de escritura o accesos a grillas de datos (.index, .show).
Invocación Manual (Rutas Públicas y Críticas)
Para flujos sin control de capacidades (e.g., accesos públicos), la acción se inyecta por parámetro de middleware:
Route::post('/login', [AuthController::class, 'login'])
->middleware('auditar:intento_login');
5. El Job Asíncrono de Procesamiento (ProcessAuditLog)
// app/Jobs/ProcessAuditLog.php
namespace App\Jobs;
use App\Models\AuditLog;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class ProcessAuditLog implements ShouldQueue
{
use InteractsWithQueue, Queueable;
public function __construct(public array $data) {}
public function handle(): void
{
AuditLog::create($this->data);
}
public function tags(): array
{
return [
'auditoria',
'user:' . ($this->data['usuario_id'] ?? 'invitado'),
'action:' . ($this->data['permiso'] ?? 'general')
];
}
}
Los tags asignados permiten indexar y rastrear ejecuciones en vivo desde el panel de supervisión de Laravel Horizon.