🧪

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 3×
SELECT clause omission
Columna en return pero no en .select()
/api/leads/profile Assert todos los campos requeridos en respuesta ▲ Alta 2×
Missing rollback
Optimistic update sin capturar estado previo
/api/leads/[id] PATCH Assert estado restaurado tras API failure ◆ Media 1×
Type cast masking undefined
TS types desactualizados ocultan errores en runtime
/api/leads/* Assert campo !== undefined explícitamente ◆ Media 1×
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