🧪

LeadFlow — AI Regression Testing Suite

Patrones de testing para desarrollo asistido por IA · Next.js 14 + Supabase + Vitest

✓ Sandbox Mode Vitest TypeScript 5 Patrones cubiertos
4
Regresiones en 2 semanas
8
Tests de regresión escritos
~0.4s
Duración total de suite
0
DB connections necesarias
100%
Bugs conocidos cubiertos
Historia de regresiones (BUG-R1 → BUG-R4)
1
BUG-R1 — crm_tags añadido a la respuesta pero no al SELECT
Claude Code añadió crm_tags al return de /api/leads/profile pero olvidó incluirlo en .select("id, email, name, score"). La IA revisó su propio código y lo encontró "correcto".
sandbox/prod mismatch
2
BUG-R2 — SELECT * añadido, TypeScript types no regenerados
Al cambiar a SELECT *, los tipos generados de Supabase quedaron desactualizados. Build roto en CI pero no localmente porque el modelo reutilizó tipos cacheados.
type mismatch
3
BUG-R3 — Ruta sandbox olvidada al añadir pipeline_stage
Se arregló la ruta de producción: return { ...data, pipeline_stage }. La ruta sandbox siguió devolviendo el objeto sin pipeline_stage. El mismo punto ciego, 3ª vez.
sandbox/prod mismatch
4
BUG-R4 — Optimistic update en PATCH sin rollback
El frontend actualizaba pipeline_stage localmente sin capturar el estado previo. Si la API fallaba, el UI mostraba el estado incorrecto permanentemente.
missing rollback
Suite de tests activa — todas las regresiones anteriores ahora cubiertas
Tras implementar los 8 tests, el mismo bug-check que tardaba 10 min de revisión manual ahora tarda 0.4s y detecta todas las categorías conocidas.
protegido
Workflow /bug-check
npm run test
Vitest sandbox
FAIL → bug mecánico
PASS → continuar
🏗️
npm run build
TS type check
FAIL → type error
PASS → continuar
🤖
AI code review
Con blind spots conocidos
Sandbox/prod parity
SELECT completeness
✍️
Regression test
Por cada bug fijado
→ Nunca más ese bug
Configuración del entorno de test
vitest.config.ts
typescript
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
typescript
// Fuerza sandbox mode — sin base de datos
// LeadFlow: todos los tests corren sin Supabase
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";

// LeadFlow-specific sandbox seeds
process.env.SANDBOX_LEADS_COUNT = "10";
process.env.SANDBOX_ORG_ID = "org-demo-leadflow";
Suite de tests de regresión
__tests__/api/leads/profile.test.ts — BUG-R1, BUG-R2, BUG-R3
typescript
import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET } from "@/app/api/leads/profile/route";

// Contrato del endpoint: todos estos campos DEBEN estar siempre
const REQUIRED_FIELDS = [
  "id", "email", "full_name", "company",
  "score",               // ← Añadido semana 1
  "pipeline_stage",     // ← BUG-R3: olvidado en sandbox path
  "crm_tags",           // ← BUG-R1: no estaba en SELECT
  "assigned_to",
  "last_contacted",
  "created_at",
];

describe("GET /api/leads/profile", () => {
  it("devuelve todos los campos requeridos del contrato", async () => {
    const req = createTestRequest("/api/leads/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, `Campo "${field}" no encontrado`)
        .toHaveProperty(field);
    }
  });

  // BUG-R1 regression — introducido por IA 4 veces
  it("crm_tags no es undefined en sandbox mode (BUG-R1)", async () => {
    const req = createTestRequest("/api/leads/profile");
    const res = await GET(req);
    const { json } = await parseResponse(res);

    expect("crm_tags" in json.data).toBe(true);
    expect(json.data.crm_tags === null || Array.isArray(json.data.crm_tags))
      .toBe(true);
  });

  // BUG-R3 regression — sandbox/production path mismatch
  it("pipeline_stage existe en sandbox (BUG-R3)", async () => {
    const req = createTestRequest("/api/leads/profile");
    const res = await GET(req);
    const { json } = await parseResponse(res);

    expect("pipeline_stage" in json.data).toBe(true);
    expect(json.data.pipeline_stage).not.toBeUndefined();
  });
});
__tests__/api/leads/list.test.ts — paridad sandbox/prod
typescript
describe("GET /api/leads/list", () => {
  it("incluye assigned_to en sandbox mode",
    async () => {
    const req = createTestRequest(
      "/api/leads/list",
      { sandboxUserId: "user-001" }
    );
    const res = await GET(req);
    const { json } = await parseResponse(res);

    if (json.data.length > 0) {
      for (const lead of json.data) {
        expect("assigned_to" in lead)
          .toBe(true);
        expect("last_contacted" in lead)
          .toBe(true);
      }
    }
  });
});
test BUG-R4 — optimistic rollback
typescript
// Patrón PASS: rollback correcto
const handleStageChange = async (
  id: string, newStage: string
) => {
  const prevLeads = [...leads]; // captura prev
  setLeads(prev => prev.map(l =>
    l.id === id
      ? { ...l, pipeline_stage: newStage }
      : l
  ));
  try {
    const res = await fetch(
      `/api/leads/${id}`,
      { method: "PATCH",
        body: JSON.stringify({newStage})
      });
    if (!res.ok) throw new Error();
  } catch {
    setLeads(prevLeads); // rollback
  }
};
Salida simulada de Vitest
RUN LeadFlow Regression Suite

PASS __tests__/api/leads/profile.test.ts
devuelve todos los campos requeridos del contrato (12ms)
crm_tags no es undefined en sandbox mode (BUG-R1) (8ms)
pipeline_stage existe en sandbox (BUG-R3) (7ms)

PASS __tests__/api/leads/list.test.ts
incluye assigned_to en sandbox mode (11ms)
last_contacted presente en todos los leads (9ms)

PASS __tests__/api/leads/patch.test.ts
devuelve 200 y lead actualizado (14ms)
devuelve 400 si pipeline_stage inválido (6ms)
error state limpia datos anteriores (BUG-R4) (8ms)
Test Files 3 passed (3)
Tests 8 passed (8)
Duration 384ms
Patrones de regresión IA detectados
Patrón IA Endpoint afectado Estrategia de test Prioridad Ocurrencias
Sandbox/prod path mismatch
Campo añadido en prod, olvidado en sandbox
/api/leads/profile
/api/leads/list
Assert mismo response shape en sandbox mode ▲ Alta
SELECT clause omission
Columna en return pero no en .select()
/api/leads/profile Assert todos los campos requeridos en respuesta ▲ Alta
Missing rollback
Optimistic update sin capturar estado previo
/api/leads/[id] PATCH Assert estado restaurado tras API failure ◆ Media
Type cast masking undefined
TS types desactualizados ocultan errores en runtime
/api/leads/* Assert campo !== undefined explícitamente ◆ Media
Error state leakage
Error set pero datos anteriores no limpiados
Frontend components Assert state cleanup en error handler ◆ Media 0× (preventivo)
.claude/commands/bug-check.md
.claude/commands/bug-check.md
markdown
# Bug Check — LeadFlow

## Step 1: Tests automáticos (OBLIGATORIO, no se puede saltear)

Ejecuta estos comandos ANTES de cualquier revisión de código:

    npm run test       # Suite Vitest en sandbox mode
    npm run build      # TypeScript type check + Next.js build

- Si tests fallan → reportar como bug de máxima prioridad
- Si build falla → reportar type errors como máxima prioridad
- Solo continuar al Step 2 si ambos pasan

## Step 2: Revisión de código (AI review con blind spots conocidos)

1. Consistencia sandbox/prod — ¿mismos campos en AMBAS rutas?
2. Shape de respuesta API — ¿coincide con lo que espera el frontend?
3. Completitud del SELECT — ¿todos los campos devueltos están en la query?
4. Error handling — ¿limpia estado previo antes de setError?
5. Optimistic updates — ¿captura prevState y hace rollback en catch?

## Step 3: Por cada bug encontrado, proponer regression test

Nombrar el test: "descripción del bug (BUG-Rn)"
Añadir al REQUIRED_FIELDS si es campo de contrato de API.
DO ✓
Escribir el test justo después de encontrar el bug (antes de fijar si es posible)
Testear el shape de la respuesta, no la implementación
Ejecutar tests como PRIMER paso del bug-check, siempre
Mantener tests rápidos (<1s total con sandbox mode)
Nombrar tests por el bug que previenen: "BUG-R1 regression"
Añadir campos al contrato REQUIRED_FIELDS cuando aparecen en bugs
DON'T ✗
Escribir tests para código que nunca ha tenido un bug
Confiar en la auto-revisión de la IA como sustituto de tests
Saltarse el test del path sandbox "porque son datos mock"
Escribir integration tests cuando basta un unit test
Apuntar a % de cobertura — apuntar a prevención de regresiones
Dejar que la IA revise el mismo código que escribió sin tests previos