¿Qué hace Pruebas de Regresión para IA?
Patrones de prueba diseñados específicamente para el desarrollo asistido por IA, donde el mismo modelo escribe código y lo revisa, creando puntos ciegos sistemáticos que solo las pruebas automatizadas pueden detectar.
Cuándo activarlo
- El agente de IA (Claude Code, Cursor, Codex) ha modificado rutas de API o lógica de backend
- Se encontró y corrigió un error, y se necesita evitar que vuelva a aparecer
- El proyecto tiene un modo sandbox/mock que se puede aprovechar para pruebas sin base de datos
- Se está ejecutando
/bug-checko comandos de revisión similares después de cambios en el código - Existen múltiples rutas de código (sandbox vs producción, feature flags, etc.)
El problema central
Cuando una IA escribe código y luego revisa su propio trabajo, lleva las mismas suposiciones a ambos pasos. Esto crea un patrón de fallo predecible:
La IA escribe una corrección → La IA revisa la corrección → La IA dice "parece correcto" → El error aún existe
Ejemplo real (observado en producción):
Corrección 1: Se añadió notification_settings a la respuesta de la API
→ Se olvidó añadirlo a la consulta SELECT
→ La IA lo revisó y no lo detectó (mismo punto ciego)
Corrección 2: Se añadió a la consulta SELECT
→ Error de compilación de TypeScript (columna no está en los tipos generados)
→ La IA revisó la Corrección 1 pero no detectó el problema del SELECT
Corrección 3: Se cambió a SELECT *
→ Se arregló la ruta de producción, se olvidó la ruta sandbox
→ La IA lo revisó y NO LO DETECTÓ OTRA VEZ (4ª ocurrencia)
Corrección 4: La prueba lo detectó al instante en la primera ejecución. PASÓ:
El patrón: la inconsistencia entre la ruta sandbox/producción es la regresión número 1 introducida por IA.
Pruebas de API en Modo Sandbox
La mayoría de los proyectos con arquitectura amigable para IA tienen un modo sandbox/mock. Esta es la clave para pruebas de API rápidas y sin base de datos.
Configuración (Vitest + Next.js App Router)
// vitest.config.ts
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["__tests__/**/*.test.ts"],
setupFiles: ["__tests__/setup.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "."),
},
},
});
// __tests__/setup.ts
// Forzar modo sandbox — no se necesita base de datos
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";
Helper de prueba para rutas de API de Next.js
// __tests__/helpers.ts
import { NextRequest } from "next/server";
export function createTestRequest(
url: string,
options?: {
method?: string;
body?: Record<string, unknown>;
headers?: Record<string, string>;
sandboxUserId?: string;
},
): NextRequest {
const { method = "GET", body, headers = {}, sandboxUserId } = options || {};
const fullUrl = url.startsWith("http") ? url : `http://localhost:3000${url}`;
const reqHeaders: Record<string, string> = { ...headers };
if (sandboxUserId) {
reqHeaders["x-sandbox-user-id"] = sandboxUserId;
}
const init: { method: string; headers: Record<string, string>; body?: string } = {
method,
headers: reqHeaders,
};
if (body) {
init.body = JSON.stringify(body);
reqHeaders["content-type"] = "application/json";
}
return new NextRequest(fullUrl, init);
}
export async function parseResponse(response: Response) {
const json = await response.json();
return { status: response.status, json };
}
Escribir pruebas de regresión
El principio clave: escribe pruebas para los errores que se encontraron, no para el código que funciona.
// __tests__/api/user/profile.test.ts
import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET, PATCH } from "@/app/api/user/profile/route";
// Define el contrato — qué campos DEBEN estar en la respuesta
const REQUIRED_FIELDS = [
"id",
"email",
"full_name",
"phone",
"role",
"created_at",
"avatar_url",
"notification_settings", // ← Añadido después de que se encontrara un error por estar ausente
];
describe("GET /api/user/profile", () => {
it("devuelve todos los campos requeridos", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { status, json } = await parseResponse(res);
expect(status).toBe(200);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
// Prueba de regresión — este error exacto fue introducido por IA 4 veces
it("notification_settings no es undefined (regresión BUG-R1)", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { json } = await parseResponse(res);
expect("notification_settings" in json.data).toBe(true);
const ns = json.data.notification_settings;
expect(ns === null || typeof ns === "object").toBe(true);
});
});
Probar paridad entre modo sandbox y producción
La regresión más común introducida por IA: arreglar la ruta de producción pero olvidar la ruta sandbox (o viceversa).
// Probar que las respuestas en modo sandbox coinciden con el contrato esperado
describe("GET /api/user/messages (lista de conversaciones)", () => {
it("incluye partner_name en modo sandbox", async () => {
const req = createTestRequest("/api/user/messages", {
sandboxUserId: "user-001",
});
const res = await GET(req);
const { json } = await parseResponse(res);
// Esto detectó un error donde partner_name se añadió
// a la ruta de producción pero no a la ruta sandbox
if (json.data.length > 0) {
for (const conv of json.data) {
expect("partner_name" in conv).toBe(true);
}
}
});
});
Integrar pruebas en el flujo de trabajo de verificación de errores
Definición de comando personalizado
<!-- .claude/commands/bug-check.md -->
# Verificación de Errores
## Paso 1: Pruebas automatizadas (obligatorio, no se puede omitir)
Ejecuta estos comandos PRIMERO antes de cualquier revisión de código:
npm run test # Suite de pruebas Vitest
npm run build # Verificación de tipos TypeScript + compilación
- Si las pruebas fallan → reporta como error de máxima prioridad
- Si la compilación falla → reporta errores de tipo como máxima prioridad
- Solo continúa al Paso 2 si ambos pasan
## Paso 2: Revisión de código (revisión por IA)
1. Consistencia entre ruta sandbox y producción
2. La forma de la respuesta de la API coincide con las expectativas del frontend
3. Integridad de la cláusula SELECT
4. Manejo de errores con rollback
5. Condiciones de carrera en actualizaciones optimistas
## Paso 3: Para cada error corregido, propón una prueba de regresión
El flujo de trabajo
Usuario: "バグチェックして" (o "/bug-check")
│
├─ Paso 1: npm run test
│ ├─ FALLA → Error detectado mecánicamente (no se necesita juicio de IA)
│ └─ PASA → Continuar
│
├─ Paso 2: npm run build
│ ├─ FALLA → Error de tipo detectado mecánicamente
│ └─ PASA → Continuar
│
├─ Paso 3: Revisión de código por IA (teniendo en cuenta los puntos ciegos conocidos)
│ └─ Hallazgos reportados
│
└─ Paso 4: Para cada corrección, escribe una prueba de regresión
└─ La próxima verificación detectará si la corrección se rompe
Patrones comunes de regresión de IA
Patrón 1: Discrepancia entre ruta sandbox y producción
Frecuencia: Más común (observado en 3 de cada 4 regresiones)
// FALLA: La IA añade el campo solo a la ruta de producción
if (isSandboxMode()) {
return { data: { id, email, name } }; // Falta el nuevo campo
}
// Ruta de producción
return { data: { id, email, name, notification_settings } };
// PASA: Ambas rutas deben devolver la misma forma
if (isSandboxMode()) {
return { data: { id, email, name, notification_settings: null } };
}
return { data: { id, email, name, notification_settings } };
Prueba para detectarlo:
it("sandbox y producción devuelven los mismos campos", async () => {
// En el entorno de prueba, el modo sandbox está forzado a ON
const res = await GET(createTestRequest("/api/user/profile"));
const { json } = await parseResponse(res);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
Patrón 2: Omisión en la cláusula SELECT
Frecuencia: Común con Supabase/Prisma al añadir nuevas columnas
// FALLA: Nueva columna añadida a la respuesta pero no al SELECT
const { data } = await supabase
.from("users")
.select("id, email, name") // notification_settings no está aquí
.single();
return { data: { ...data, notification_settings: data.notification_settings } };
// → notification_settings siempre es undefined
// PASA: Usa SELECT * o incluye explícitamente las nuevas columnas
const { data } = await supabase
.from("users")
.select("*")
.single();
Patrón 3: Fuga de estado de error
Frecuencia: Moderada — al añadir manejo de errores a componentes existentes
// FALLA: El estado de error se establece, pero los datos antiguos no se borran
catch (err) {
setError("Error al cargar");
// ¡las reservas aún muestran datos de la pestaña anterior!
}
// PASA: Limpiar el estado relacionado al ocurrir un error
catch (err) {
setReservations([]); // Limpiar datos obsoletos
setError("Error al cargar");
}
Patrón 4: Actualización optimista sin rollback adecuado
// FALLA: Sin rollback en caso de fallo
const handleRemove = async (id: string) => {
setItems(prev => prev.filter(i => i.id !== id));
await fetch(`/api/items/${id}`, { method: "DELETE" });
// Si la API falla, el elemento desaparece de la UI pero sigue en la DB
};
// PASA: Capturar el estado anterior y hacer rollback en caso de fallo
const handleRemove = async (id: string) => {
const prevItems = [...items];
setItems(prev => prev.filter(i => i.id !== id));
try {
const res = await fetch(`/api/items/${id}`, { method: "DELETE" });
if (!res.ok) throw new Error("Error de API");
} catch {
setItems(prevItems); // Rollback
alert("Error al eliminar");
}
};
Estrategia: Prueba donde se encontraron los errores
No apuntes a una cobertura del 100%. En su lugar:
Error encontrado en /api/user/profile → Escribe prueba para la API de perfil
Error encontrado en /api/user/messages → Escribe prueba para la API de mensajes
Error encontrado en /api/user/favorites → Escribe prueba para la API de favoritos
Sin error en /api/user/notifications → No escribas prueba (aún)
Por qué funciona con el desarrollo con IA:
- La IA tiende a cometer el mismo tipo de error repetidamente
- Los errores se agrupan en áreas complejas (auth, lógica de rutas múltiples, gestión de estado)
- Una vez probado, esa regresión exacta no puede volver a ocurrir
- El número de pruebas crece orgánicamente con las correcciones de errores — sin esfuerzo desperdiciado
Referencia rápida
| Patrón de Regresión de IA | Estrategia de Prueba | Prioridad |
|---|---|---|
| Discrepancia sandbox/producción | Afirmar la misma forma de respuesta en modo sandbox | Alta |
| Omisión en cláusula SELECT | Afirmar todos los campos requeridos en la respuesta | Alta |
| Fuga de estado de error | Afirmar limpieza de estado al ocurrir un error | Media |
| Falta de rollback | Afirmar que el estado se restaura al fallar la API | Media |
| Tipo de dato que enmascara null | Afirmar que el campo no es undefined | Media |
HACER / NO HACER
HACER:
- Escribir pruebas inmediatamente después de encontrar un error (antes de corregirlo si es posible)
- Probar la forma de la respuesta de la API, no la implementación
- Ejecutar las pruebas como primer paso de cada verificación de errores
- Mantener las pruebas rápidas (< 1 segundo en total con modo sandbox)
- Nombrar las pruebas según el error que previenen (ej., "regresión BUG-R1")
NO HACER:
- Escribir pruebas para código que nunca ha tenido un error
- Confiar en la autorevisión de la IA como sustituto de las pruebas automatizadas
- Omitir las pruebas de la ruta sandbox porque "son solo datos simulados"
- Escribir pruebas de integración cuando las pruebas unitarias son suficientes
- Apuntar a un porcentaje de cobertura — apunta a la prevención de regresiones