Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 076 — Auditoría#

⬅️ 075 · 📚 Parte 5 · 🎓 Clases · 077 ➡️ Parte 5 — Identidad y seguridad · Nivel 🔴 avanzado · Pista backendClase construida — 4 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Dejar rastro de quién hizo qué, y que ese rastro sirva después. Las clases 070 y 071 pusieron controles de acceso; esta clase asume que algún control fallará —o que alguien con permiso legítimo hará algo indebido— y prepara la respuesta a la única pregunta que importa entonces: ¿quién tocó esto, y cuándo? [adkins-building-secure-reliable].

🧩 La situación#

Un recurso se crea, se lee y se borra. Al final, el registro de auditoría tiene exactamente dos entradas —la creación y el borrado, no la lectura—, y cada una nombra actor, acción, recurso e instante.

🧮 El contrato#

Petición Respuesta Qué mide
GET /auditoria total: 0 el punto de partida
POST /tareas como ana 201 un cambio ocurre
GET /tareas/{id} 200 una lectura ocurre
DELETE /tareas/{id} como luis 204 otro cambio
GET /auditoria total: 2 la lectura no dejó rastro
GET /auditoria actor, accion, recurso exactos + instante presente el registro es utilizable

El quinto caso es el que mide de verdad: total: 2 con tres peticiones de por medio. Un registro que apunta todo —lecturas incluidas— no es más seguro, es inservible: el volumen entierra los dos eventos que importaban, y el coste de almacenarlo empuja a recortar la retención justo cuando hace falta mirar atrás.

El instante se comprueba por presencia, no por valor (json_campos_- presentes, nuevo en el verificador): predecir una marca de tiempo sería predecir lo impredecible, pero un registro sin instante no responde «cuándo», y entonces no responde nada.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Auditoría El rastro de quién cambió qué y cuándo. Tres reglas: un solo lugar por donde pasa cada cambio, el instante lo pone el servidor, y el almacén es de solo apéndice y está aparte — si quien borró el dato puede borrar su rastro, el rastro no protege.

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Express framework web de Node.js (JavaScript) 2010 MIT OpenJS Foundation
FastAPI framework web de Python (Python) 2018 MIT proyecto independiente
Spring Boot framework de aplicación de JVM (Java) 2014 Apache-2.0 Broadcom/VMware y colaboradores
ASP.NET Core framework web de .NET (C#) 2016 MIT Microsoft y .NET Foundation

🔧 Express#

Definió el modelo de middleware encadenado que copiaron casi todos los frameworks de Node.js. Minimalista no significa biblioteca: posee el bucle de peticiones.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 FastAPI#

Deriva validación, serialización y documentación OpenAPI de las anotaciones de tipo. Demostró que el tipado opcional de Python podía ser infraestructura, no adorno.

Arrancarla suelta, sin el verificador:

PORT=3000 python -m uvicorn main:app --host 127.0.0.1 --port 3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
main.py código Python
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Spring Boot#

Autoconfiguración y servidor incrustado sobre Spring. Convirtió un framework famoso por su configuración XML en uno de arranque inmediato.

Preparar sus dependencias, dentro de su directorio:

mvn -q -B package -DskipTests

Arrancarla suelta, sin el verificador:

PORT=3000 java -jar target/clase-076-1.0.0.jar --server.port=3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
pom.xml manifiesto de Maven: el proyecto, su Java, sus dependencias y cómo se empaqueta
src/main/java/labs/Aplicacion.java código Java

🔧 ASP.NET Core#

Reescritura multiplataforma y de código abierto de la pila web de Microsoft. Sus API mínimas trajeron el estilo de los microframeworks al ecosistema .NET.

Preparar sus dependencias, dentro de su directorio:

dotnet build -c Release --nologo -v quiet

Arrancarla suelta, sin el verificador:

PORT=3000 dotnet run -c Release --no-build --urls http://127.0.0.1:3000

Qué hay dentro de su directorio:

Archivo Qué es
Clase076.csproj proyecto de .NET: el marco de destino y las dependencias
Program.cs código C#
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

Las cuatro comparten la decisión que define la clase: un solo lugar por donde pasa cada cambio. Escribir el rastro dentro de cada manejador funciona hasta el manejador número siete, que lo olvida — y ese olvido no rompe ninguna prueba: solo deja un hueco silencioso en el registro.

Lo que cambia entre los cuatro es qué mecanismo del framework sostiene ese único lugar.

Express · express/server.mjs — una función#

function registrar(peticion, accion, recurso, id) {
  auditoria.push({
    actor: peticion.get("x-actor") ?? "anonimo",
    accion,
    recurso,
    recurso_id: String(id),
    instante: new Date().toISOString(),
  });
}
  tareas.set(id, tarea);
  registrar(peticion, "crear", "tarea", id);
  tareas.delete(peticion.params.id);
  registrar(peticion, "borrar", "tarea", peticion.params.id);

Explícito y visible — y con la disciplina como única garantía. Nada impide añadir mañana un PUT /tareas/:id que no llame a registrar.

Fíjate también en la lectura:

  if (!tarea) return respuesta.status(404).json({ error: "no-encontrada" });

Leer no se audita. La auditoría registra cambios. Los accesos a datos sensibles a veces sí se registran, pero en un canal aparte y con otro propósito; esta clase mide la auditoría de cambios, que es la universal.

FastAPI · fastapi/main.py — la misma función, y el actor como parámetro#

def registrar(actor: str, accion: str, recurso: str, recurso_id: str) -> None:
    auditoria.append({
        "actor": actor or "anonimo",
        "accion": accion,
        "recurso": recurso,
        "recurso_id": recurso_id,
        # El instante lo pone el SERVIDOR: un actor no fecha sus propios actos.
        "instante": datetime.now(timezone.utc).isoformat(),
    })

Idéntica a Express salvo en un detalle que sí importa: el actor llega como argumento, no se saca de la petición dentro de la función. Eso deja registrar sin dependencia del framework — se puede probar sin montar un servidor, que es la idea de la clase 065.

Spring Boot · spring-boot/…/Aplicacion.java — un componente inyectado#

    @Component
    public static class Auditoria {
        private final List<Map<String, String>> registros = new CopyOnWriteArrayList<>();

        public void registrar(String actor, String accion, String recurso, String id) {

La auditoría deja de ser una función suelta y pasa a ser una pieza del contenedor (clase 036): quien la necesite la pide y el framework la entrega. CopyOnWriteArrayList resuelve de paso lo que Express y FastAPI no tienen que resolver — varios hilos escribiendo a la vez.

En producción el paso siguiente es @EntityListeners o Spring Data Envers, que auditan en la capa de persistencia: el rastro deja de depender de que el manejador se acuerde, porque lo dispara el propio guardado.

ASP.NET Core · aspnet-core/Program.cs — servicio singleton#

constructor.Services.AddSingleton<Auditoria>();
app.MapPost("/tareas", (Cuerpo? cuerpo, HttpRequest peticion, Auditoria auditoria) =>
        lock (_candado) { _registros.Add(registro); }

Mismo patrón que Spring, con el ciclo de vida declarado en la línea de registro en lugar de deducido de una anotación: AddSingleton dice literalmente que hay una sola instancia para todo el proceso — la distinción de la clase 037. Y la inyección ocurre en la firma del manejador, no en el constructor de una clase.

El siguiente paso idiomático aquí es un interceptor de SaveChanges en Entity Framework Core: el equivalente exacto de los entity listeners de Spring.

Las dos reglas que no cambian en ninguna#

    instante: new Date().toISOString(),

El instante lo pone el servidor. Si lo pusiera el cliente, el actor podría mentir sobre cuándo hizo lo que hizo — y un registro que el auditado puede fechar no es una auditoría.

Y la segunda, que en el laboratorio es una lista en memoria pero en producción decide si el registro sirve de algo: almacén de solo apéndice y aparte de la base de negocio. Si quien borró la tarea puede borrar también su rastro, el rastro no protege de nada.

El actor llega por la cabecera X-Actor para que el contrato pueda fijarlo sin montar el inicio de sesión entero. En una aplicación real sale de la sesión (clase 066) o del token (clase 067) — nunca de algo que el cliente escriba libremente.

📊 Comparación#

Framework Dónde vive el registro El paso siguiente del ecosistema
Spring Boot @Component inyectado @EntityListeners, Spring Data Envers
ASP.NET Core servicio singleton interceptor de SaveChanges en EF Core
Express módulo con una función escribirlo tú, o un middleware propio
FastAPI función compartida dependencia inyectada, o hooks del ORM

La diferencia real no es de calidad de código sino de dónde puede vivir el punto único. Spring y .NET pueden bajarlo hasta el ORM, donde ningún camino de escritura lo esquiva; en Express y FastAPI el punto único vive en la capa de aplicación, y una escritura que baje directamente a la base pasa por debajo.

🧭 Qué hace útil un registro de auditoría#

Cuatro campos y tres propiedades:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 076

Los casos están en contrato.json. El verificador ejecuta las implementaciones que encuentre y declara las que omitió.

🧪 Reto de transferencia#

Añade PATCH /tareas/{id} que registre el cambio, no el estado: {"campo": "titulo", "antes": "pagar", "despues": "pagar la luz"}. Después añade el caso que mide lo que esto habilita: reconstruir el valor de un recurso en un instante dado a partir del registro. Es el salto de «hay rastro» a «el rastro explica».

🔗 Enlaces#

Fuentes#