CodeGym /Cursos /ChatGPT Apps /Auditoría y ciclo de vida: registros de auditoría, retenc...

Auditoría y ciclo de vida: registros de auditoría, retención de datos, eliminación a petición, continuidad/copias de seguridad

ChatGPT Apps
Nivel 15 , Lección 4
Disponible

1. Por qué necesitas auditoría y ciclo de vida en ChatGPT App

Mientras escribes un prototipo, el usuario eres tú mismo, la base de datos es un SQLite local, y los «incidentes» se curan con git reset --hard; todo parece sencillo y casero.

Pero en cuanto tu GiftGenius (u otra aplicación de ChatGPT (App)) consigue usuarios reales, especialmente con pagos y PII, de repente aparecen:

  • los responsables de seguridad del cliente con la pregunta «¿quién puede ver nuestros pedidos y quién los ha cambiado?»;
  • los abogados con la pregunta «¿cuánto tiempo conserváis los datos y cómo cumplís una solicitud de “eliminadme”?»;
  • la realidad de producción con la pregunta «¿qué pasa si un desarrollador borra una tabla en prod?».

En esta lección veremos cuatro bloques fundamentales:

  1. Registros de auditoría — una capa de logging separada para seguridad y auditoría.
  2. Retención de datos — los plazos de vida de distintos tipos de datos y cómo implementarlos.
  3. Eliminación a petición del usuario — el «derecho al olvido» en la práctica técnica.
  4. Continuidad de negocio y copias de seguridad — cómo sobrellevar fallos sin perder la cara ni los datos.

Siempre que sea posible, vincularemos los ejemplos a nuestra App de aprendizaje (el hipotético GiftGenius en Next.js + Apps SDK + MCP).

2. Registros de auditoría: quién, qué, cuándo y cómo terminó

En qué se diferencian los registros de auditoría de los logs normales

Los logs de aplicación normales son mensajes amistosos para el desarrollador. En ellos viven stack traces, información de depuración, valores extraños de variables y console.log("aquí seguro que no debería haber null"). Suelen vivir poco y los leen ingenieros.

Los registros de auditoría son otro universo. Su público principal son los responsables de seguridad, auditores y, a veces, abogados. No necesitan una línea «NullPointer en la línea 55», necesitan un registro del tipo «el usuario X cambió la configuración de pago de la organización Y en tal momento; resultado — éxito». Las entradas de auditoría suelen vivir mucho más (años) y se consideran una prueba en caso de investigaciones.

Diferencias clave:

Característica Logs de aplicación Registros de auditoría
Objetivo Depuración, diagnóstico Seguridad, cumplimiento, investigaciones
Audiencia Desarrolladores, SRE Seguridad, abogados, a veces reguladores
Contenido de datos Detalles técnicos, stack trace Quién/qué/cuándo/sobre qué recurso/con qué resultado
Plazo de conservación Semanas–un mes Años (a menudo ≥ 1 año)
Operaciones sobre los logs Se pueden borrar/sobrescribir Preferible append‑only, sin UPDATE/DELETE

OWASP y guías similares subrayan por separado: es mejor mantener los registros de auditoría en un almacenamiento o tabla aparte, en lugar de mezclarlos con los logs normales de la aplicación.

Qué registrar en el contexto de una aplicación de ChatGPT

Para una aplicación de ChatGPT, especialmente con comercio, el mínimo razonable de auditoría es:

  • eventos de autenticación: login, logout, intentos de acceso;
  • operaciones con datos críticos: creación/actualización/eliminación de perfiles, pedidos, configuraciones de pago;
  • acciones administrativas: cambio de roles, modificación de la configuración del inquilino (tenant);
  • invocaciones de herramientas sensibles de MCP/Agents: create_order, charge_customer, cancel_subscription, etc.

Una buena intuición: todo aquello que, en un incidente, quieras preguntar en plan «¿quién hizo esto y a través de qué?», debe ir a la auditoría.

Estructura de un evento de auditoría

Un modelo mental útil: cada registro es «quién / qué acción / sobre qué / en qué contexto / con qué resultado». A menudo se formula como la estructura who, action, resource, context, outcome.

Para nuestro GiftGenius, definamos la interfaz en TypeScript:

// lib/audit.ts
export type AuditAction =
  | "auth.login"
  | "auth.logout"
  | "order.create"
  | "order.cancel"
  | "account.delete"
  | "giftidea.generate";

export interface AuditEvent {
  eventId: string;          // uuid
  timestamp: string;        // ISO
  actor: {
    userId: string | null;  // puede ser null antes del inicio de sesión
    tenantId?: string | null;
    ip?: string | null;
    client: "chatgpt-app" | "admin-panel" | string;
  };
  action: AuditAction;
  resource?: {
    type: string;           // "order", "user", ...
    id?: string;
  };
  context?: {
    mcpTool?: string;
    requestId?: string;
  };
  outcome: {
    status: "success" | "failure";
    reason?: string | null;
  };
}

Observa que en el evento no entran e‑mails completos, números de tarjeta y otra PII que, según lecciones anteriores, aprendimos a enmascarar y a no registrar sin necesidad.

Dónde y cómo almacenar los registros de auditoría

Requisitos mínimos del almacenamiento:

  • una tabla aparte o incluso una base de datos separada de los logs normales, para que sea más difícil borrar algo por accidente;
  • si es posible, modo append‑only: técnicamente puede ser simplemente la política «nunca hacemos UPDATE/DELETE en esta tabla», más un rol de BD que solo tenga permisos de INSERT y SELECT;
  • acceso limitado: no todo el equipo técnico debe poder leer la auditoría completa.

Si utilizas PostgreSQL a través de Prisma/Drizzle, el modelo puede verse así (ejemplo simplificado):

CREATE TABLE audit_events (
  event_id   uuid PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT now(),
  actor_user_id text,
  actor_tenant_id text,
  actor_ip      inet,
  action        text NOT NULL,
  resource_type text,
  resource_id   text,
  context_mcp_tool text,
  context_request_id text,
  outcome_status text NOT NULL,
  outcome_reason text
);

El esquema se adapta a tus necesidades, pero lo principal es la estructuración. Un JSON‑basura en una sola línea lo acabarás maldiciendo.

Implementación de la auditoría en nuestra App

Hagamos un pequeño helper en la aplicación Next.js (entorno Node, por ejemplo en el servidor MCP o en un endpoint de la API):

// lib/audit.ts
import { randomUUID } from "crypto";
import { db } from "./db"; // tu cliente de BD

export async function logAudit(event: Omit<AuditEvent, "eventId" | "timestamp">) {
  const full: AuditEvent = {
    ...event,
    eventId: randomUUID(),
    timestamp: new Date().toISOString(),
  };

  // En la vida real — vía cola/worker; aquí, solo un insert
  await db.insertInto("audit_events").values({
    event_id: full.eventId,
    created_at: full.timestamp,
    actor_user_id: full.actor.userId,
    action: full.action,
    outcome_status: full.outcome.status,
    outcome_reason: full.outcome.reason ?? null,
    // ...otros campos
  });
}

Ahora añadimos la llamada en el manejador que crea un pedido (imaginemos que es una herramienta MCP o un endpoint de servidor):

// app/api/orders/route.ts
export async function POST(req: Request) {
  const user = await requireUser(req); // del módulo de autenticación
  const body = await req.json();
  const order = await createOrderInDb(user, body);

  await logAudit({
    actor: { userId: user.id, client: "chatgpt-app" },
    action: "order.create",
    resource: { type: "order", id: order.id },
    context: { mcpTool: "create_order_tool" },
    outcome: { status: "success" },
  });

  return Response.json(order);
}

Lo mismo puede hacerse alrededor de operaciones peligrosas: cancelar un pedido, cambiar datos de pago, eliminar una cuenta.

Ya tenemos una capa de auditoría separada y estructurada — perfecto. La siguiente pregunta natural es: ¿cuánto tiempo deben vivir todos esos eventos (y el resto de datos de usuario) y qué hacer con ellos cuando expira el plazo?

3. Retención de datos: cuánto viven tus datos

Por qué no se puede guardar todo para siempre

El instinto ingenieril de «por si acaso» en el contexto de datos de usuario — es muy peligroso.

En primer lugar, cuanto más tiempo y más datos acumulas, más graves son las consecuencias en caso de fuga: cuanto más grande el barril de gasolina, peor el incendio. Muchas guías de protección de datos llaman a los datos un «activo tóxico»: hay que guardarlos con propósito, pero minimizando volumen y plazo.

En segundo lugar, la legislación tipo GDPR/CCPA introduce el principio de «no más tiempo del necesario para la finalidad del tratamiento». Es decir, los datos personales no pueden conservarse indefinidamente «por si acaso». Para cada tipo de dato deben existir plazos claros de conservación y procedimientos de eliminación o anonimización.

En tercer lugar, el almacenamiento en la nube cuesta dinero. Las tablas grandes de logs e historiales de chat crecen rápido, y al cabo de un año resulta que la mitad de la factura del proveedor es «basura de ayer».

Datos distintos — plazos distintos

La experiencia de empresas y las guías públicas dan aproximadamente este panorama:

Tipo de datos Plazos típicos de conservación
Logs de depuración, métricas técnicas de 1 a 12 meses
Registros de auditoría ≥ 12 meses, a veces 2–5 años
Pedidos, pagos, facturas 3–7 años (por requisitos contables/fiscales)
Sesiones, tokens temporales horas–días
Chats o peticiones en bruto desde varias semanas hasta varios meses, o directamente no almacenarlos
Agregados anónimos (analítica) más tiempo, ya que ya no hay PII

Importante: esto no es asesoramiento jurídico, sino referencias de ingeniería. Para un producto real, acordarás los plazos con el equipo legal, pero técnicamente ya debes estar preparado para implementar distintos TTL.

Cómo implementar la retención en código

El patrón más común: la tabla tiene created_at o expires_at, y tienes un proceso periódico que elimina o anonimiza los registros antiguos.

Ejemplo: limpieza de logs normales con más de 90 días.

// scripts/cleanup-logs.ts
import { db } from "../lib/db";

async function cleanup() {
  await db
    .deleteFrom("app_logs")
    .where("created_at", "<", new Date(Date.now() - 90 * 24 * 60 * 60 * 1000));
  console.log("Old logs removed");
}

cleanup().catch(console.error);

Este script puede lanzarse con cron, con GitHub Actions programado o con el planificador de tu nube.

Para PII, en lugar de borrar, a menudo se anonimiza. Por ejemplo, los pedidos de más de N años pierden el vínculo con el usuario concreto:

UPDATE orders
SET user_id = NULL
WHERE created_at < now() - interval '3 years';

Se conservan los importes, artículos y demás «contabilidad», pero desaparece el vínculo con la persona concreta.

No olvides que las copias de seguridad también deben tener su propio plazo de vida. La periodicidad y la duración de retención de los backups la comentaremos aparte en el bloque de copias de seguridad, pero la idea es la misma: ni siquiera los archivos de archivo deben mantenerse para siempre, o el «derecho al olvido» se convierte en una ficción.

4. Eliminación a petición del usuario: el «derecho al olvido» en código

De dónde viene el requisito

El GDPR europeo (y leyes similares) introduce el llamado «derecho al olvido»: el usuario puede exigir la eliminación de sus datos personales, y la empresa debe hacerlo sin dilación injustificada.

Desde el punto de vista del desarrollador de una aplicación así, esto significa: tarde o temprano te llegará una solicitud de «eliminad todos mis datos» (o pondrás tú mismo un botón «Delete my data»), y habrá que no solo borrar el registro en la tabla users, sino recorrer todo el rastro: pedidos, sesiones, tokens, registros de actividad, CRM, pagos, etc.

Pero también hay leyes que te obligan a conservar ciertos datos: transacciones financieras, por ejemplo. Así que hay más complejidad jurídica que técnica.

Qué exactamente hay que limpiar

Mínimo para nuestro GiftGenius:

  • perfil de usuario (nombre, e‑mail, configuración);
  • sesiones, refresh tokens, vínculos con proveedores OAuth;
  • pedidos, si no son necesarios «personalizados» (o si se pueden anonimizar);
  • logs y registros de auditoría donde haya PII dentro (por ejemplo, e‑mail en bruto).

Permanecen los datos importantes para informes, pero ya despersonalizados: importes de pedidos, número de transacciones, agregados por país, etc.

Ejemplo de algoritmo de eliminación

Esquema del escenario:

  1. El usuario (autenticado) pulsa «Eliminar mi cuenta».
  2. Al servidor le llega una solicitud con su userId.
  3. El servidor:
    • borra/anonimiza los registros dependientes (pedidos, sesiones, integraciones);
    • limpia la PII del perfil;
    • escribe un registro en la auditoría «se ha procesado la solicitud de eliminación de datos».

Para simplificar, mostramos una variante mínima sobre un par de tablas. En un producto real, alrededor de este núcleo añadirás entidades adicionales (integraciones, servicios de terceros, etc.).

Código de servicio en Next.js (ejemplo simplificado):

// app/api/delete-me/route.ts
import { db } from "@/lib/db";
import { logAudit } from "@/lib/audit";

export async function POST(req: Request) {
  const user = await requireUser(req);

  await db.transaction(async (tx) => {
    await tx.deleteFrom("sessions").where("user_id", "=", user.id);
    await tx.deleteFrom("orders").where("user_id", "=", user.id);

    await tx.updateTable("users")
      .set({
        is_deleted: true,
        name: null,
        email: null,
      })
      .where("id", "=", user.id);

    await logAudit({
      actor: { userId: user.id, client: "chatgpt-app" },
      action: "account.delete",
      outcome: { status: "success" },
    });
  });

  return new Response(null, { status: 204 });
}

En el mundo real, aquí añadirás llamadas a APIs externas (por ejemplo, Stripe — para desvincular al customer), y harás la transacción más seria. Pero el principio ya está: todo en un mismo lugar, con registro de auditoría.

Relación con las copias de seguridad (backups)

La parte difícil de «¿y qué pasa con los backups?» trae muchas preguntas interesantes. Incluso si has eliminado al usuario de la BD de producción, sus datos pueden quedar en snapshots nocturnos. Para que esto no se convierta en «en la práctica nunca eliminamos a nadie», hay dos enfoques:

  1. Los backups viven un tiempo limitado (por ejemplo, 30–90 días) y, pasado ese tiempo, desaparecen junto con los datos. Tras expirar la retención, ni la BD principal ni los archivos contienen al usuario.
  2. Si aun así levantas el sistema desde un backup, tienes un registro de IDs «eliminados» y, después de restaurar, vuelves a ejecutar los scripts de eliminación/anonimización.

En empresas grandes a veces se practica el crypto‑shredding: la PII del usuario se cifra con una clave separada y, ante una solicitud de eliminación, se destruye la clave. Incluso si quedan copias de los datos cifrados (en logs, backups), sin la clave son basura inútil. Es genial, pero para una startup es algo «ciencia de cohetes».

Un punto de UX importante

Recuerda que la eliminación no es solo SQL. El usuario espera:

  • una forma clara de enviar la solicitud (botón, formulario, e‑mail);
  • plazos razonables de ejecución (en la práctica, hasta 30 días);
  • una notificación de éxito o una negativa motivada (por ejemplo, cuando parte de los datos debe conservarse por ley).

Desde el punto de vista técnico, ya lo tienes todo listo: sabes limpiar, registrar la acción y no conservar de más en los backups.

5. Continuidad de negocio y copias de seguridad

Ahora imagina que todo lo anterior funciona perfecto… hasta que sucede un fatal DROP TABLE orders, un fallo en la nube o la caída de una región. Necesitamos mecanismos para devolver el servicio a la vida en un plazo razonable y no perder datos críticos.

RTO y RPO — dos siglas que definen tu dolor

Dos parámetros básicos de Disaster Recovery:

  • RTO (Recovery Time Objective) — cuánto tiempo te puedes permitir estar indisponible. Por ejemplo, si RTO = 1 hora, significa que tras un fallo serio estás obligado a levantar el sistema como máximo en una hora.
  • RPO (Recovery Point Objective) — cuántos datos, en tiempo, estás dispuesto a perder. Si RPO = 10 minutos, significa que al restaurar puedes perder los últimos 10 minutos de historia, pero no más.

Cuanto más crítico es el producto (banca, sistemas de trading), más cerca de cero están ambos parámetros. Para el GiftGenius didáctico, se puede vivir con RTO ~ unas horas y RPO ~ 15–60 minutos, pero incluso eso hay que implementarlo de alguna manera.

Qué puede salir mal en tu stack

En el contexto de una App de ChatGPT en Vercel + BD en la nube + APIs externas, la lista típica de males es:

  • OpenAI API indisponible: tu App responde con errores a las llamadas de herramientas.
  • Fallo en Vercel (o quien sea): el widget no puede alcanzar tu backend.
  • Base de datos dañada o algo borrado accidentalmente (por ejemplo, DROP TABLE).
  • Cuenta rota o comprometida que gestiona la infraestructura.

A todo esto respondes con una combinación de backups, replicaciones y un comportamiento razonable de la App ante fallos.

Estrategias de copia de seguridad

Los Postgres/otras BDs en la nube modernas suelen dar al menos tres opciones:

  1. Backups completos + incrementales.
    Hacer un snapshot completo de la BD una vez al día y entre medias guardar cambios incrementales. La restauración — volver a un snapshot concreto y aplicar el registro de cambios.
  2. Point‑in‑Time Recovery (PITR).
    La base escribe el journal de transacciones (WAL) y permite restaurar a un instante arbitrario (por ejemplo, «estado a las 14:03:00, antes de que borráramos la tabla»).
  3. Replicación a otra región.
    Mantener una réplica pasiva o activa de la BD en otra región/nube. Si se pierde la región principal, puedes conmutar la App a la réplica, perdiendo solo los datos aún no replicados.

Para nuestra escala, suele bastar con activar PITR en el proveedor de BD y backups periódicos off‑site.

Ejemplo sencillo: volcado diario para la BD local/dev

Aunque en producción te apoyes en una BD gestionada, para staging/dev a veces conviene tener un script simple:

# scripts/backup.sh
#!/usr/bin/env bash
set -e
DATE=$(date +%F)
pg_dump "$DATABASE_URL" > "backups/backup-$DATE.sql"
echo "Backup created: backups/backup-$DATE.sql"

Puedes ejecutarlo con cron o GitHub Actions. Lo principal — no olvides que los backups también hay que borrarlos según su plazo de vida.

Comportamiento de la App cuando caen servicios externos

Los backups y el PITR resuelven el problema de «qué hacer si todo se rompe o los datos se corrompen». Pero en la realidad de negocio suelen ocurrir fallos parciales — se cae una API externa, la red se corta, el procesador de pagos se queda colgado.

Cuando la OpenAI API o el procesador de pagos están caídos, la peor estrategia es devolver un 500 pelado y un stack trace sin sentido. Lo ideal:

  • el backend devuelve un error estructurado como { error: "upstream_unavailable" };
  • el widget muestra un mensaje comprensible para la persona: «El servicio no está disponible temporalmente; inténtalo más tarde»;
  • el sistema no sigue bombardeando la API caída con reintentos infinitos (los patrones Circuit Breaker, etc., los veremos en detalle en el módulo de resiliencia).

Ejemplo de manejador de herramienta MCP que tiene en cuenta un error externo:

// mcp/tools/createGiftIdea.ts
export async function createGiftIdea(args: Input): Promise<Output> {
  try {
    return await callOpenAiModel(args);
  } catch (err) {
    await logAudit({
      actor: { userId: args.userId ?? null, client: "chatgpt-app" },
      action: "giftidea.generate",
      outcome: { status: "failure", reason: "openai_unavailable" },
    });
    throw new Error("UPSTREAM_UNAVAILABLE");
  }
}

Después, tu capa entre MCP y el widget ya sabe cómo mostrar este error en el UI de forma adecuada.

Verificación de restauración: un backup sin restore es solo un archivo

El anti‑patrón clásico: se hace el backup a diario, todos contentos… hasta que resulta que no se pueden restaurar (cambió el formato, se perdió la clave, faltó espacio).

Plan mínimo:

  • levantar periódicamente (por ejemplo, una vez al mes) un entorno de staging desde un backup;
  • recorrer los escenarios básicos: login, creación de pedido, funcionamiento de la App;
  • asegurarse de que el tiempo de restauración y la pérdida de datos encajan en tus RTO/RPO.

En lugar de un curso sobre la «religión DevOps», en el marco de esta lección basta con entender: los procesos de copia de seguridad forman parte de la arquitectura de la App, no «algo que hará alguien en la nube».

6. Visualización: ciclo de vida de datos y eventos

Para que no quede solo en palabras, dibujemos dos esquemas sencillos.

Ciclo de vida de los datos del usuario

flowchart TD
  A["Creación de datos<br/>(registro, pedido)"] --> B["Almacenamiento y uso<br/>(BD de prod)"]
  B --> C["Archivo/agregación<br/>(métricas anónimas)"]
  B --> D[Solicitud de eliminación]
  D --> E[Eliminación/anonimización<br/>en BD de prod]
  E --> F["Caducidad de las copias de seguridad<br/>(retención)"]
    

La idea principal: el ciclo de vida de los datos no termina en la BD de producción — continúa también en los backups.

Flujo de auditoría para una acción peligrosa

sequenceDiagram
  participant User as Usuario
  participant ChatGPT as ChatGPT
  participant App as Tu backend/MCP
  participant DB as Base de datos
  participant Audit as Almacén de auditoría

  User->>ChatGPT: "Cancela el pedido #123"
  ChatGPT->>App: callTool cancel_order
  App->>DB: UPDATE orders SET status='canceled'
  App->>Audit: INSERT audit_event {actor, action, resource, outcome}
  App-->>ChatGPT: Resultado de la operación
  ChatGPT-->>User: Mensaje con el resultado
    

7. Errores típicos en auditoría y ciclo de vida

Error n.º 1: Mezclar registros de auditoría y logs normales.
Cuando todos los mensajes van a un único índice logs, a los seis meses nadie distinguirá «el usuario cambió el rol de administrador» de «tenemos otra vez un null reference». En auditoría deben existir eventos estructurados de nivel negocio (ver la sección sobre la estructura de un evento de auditoría) y un almacenamiento separado con acceso limitado.

Error n.º 2: Registrar PII en la auditoría y en los logs de depuración.
E‑mail completo, teléfono, dirección de envío, últimos cuatro dígitos de la tarjeta — todo esto a menudo acaba en los logs por accidente. Esto eleva el riesgo de fuga y contradice las recomendaciones de privacidad. En su lugar, registra identificadores y valores enmascarados.

Error n.º 3: Ausencia de política de retención — «guardamos todo siempre».
En la fase MVP parece «no pasa nada», y al cabo de un año tus tablas crecen a tamaños monstruosos y cualquier consulta de analítica se convierte en un DDoS a tu BD. Además, violas el principio de minimización recogido en las leyes modernas de datos. Deben pensarse TTL mínimos por tipo de dato y automatizar la limpieza.

Error n.º 4: «Eliminación a petición» == DELETE FROM users.
Si solo borras la fila del usuario pero dejas su PII en pedidos, sesiones y logs, en esencia no has eliminado a nadie. El enfoque correcto — recorrer transaccionalmente todas las entidades relacionadas y, donde no se pueda borrar, anonimizar. Y no olvides registrar el propio hecho de la eliminación como evento de auditoría.

Error n.º 5: Ignorar los backups al eliminar datos.
Eliminaste al usuario en prod — bien, pero sus datos viven un año en snapshots antiguos. Al restaurar desde ellos, todo «resucita», y vuelves a violar tus propias promesas al usuario y en la Privacy Policy. Hay que limitar el plazo de vida de los backups o tener un procedimiento de reaplicación de eliminaciones tras la restauración.

Error n.º 6: «Tenemos backups activados, así que todo bien», pero nadie ha intentado restaurar.
Un backup que nunca se ha probado en restore — es solo un archivo caro. Sin comprobaciones periódicas de restauración, no conoces ni tu RTO/RPO reales ni si tu plan de DR funciona. Mínimo — levantar regularmente un staging de prueba desde un backup con un checklist.

Error n.º 7: Desajuste entre la documentación y la realidad.
En la Privacy Policy escribes que conservas los logs 30 días y borras los datos a petición, pero en el código todo queda para siempre. La tienda de ChatGPT, clientes enterprise y auditores lo detectarán fácilmente con preguntas como «muestra la tabla de retención» y «demuestra la eliminación de un usuario concreto». Mejor hazlo primero y después escríbelo.

1
Cuestionario/control
Seguridad, nivel 15, lección 4
No disponible
Seguridad
Seguridad y cumplimiento
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION