Files
inq-roi-simulador-web/sprints/sprint-5/ETAPA_6_PROMPT.md
Marcos Benítez bf200b72f9
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Los cambios en reporte de eficiencia
2026-06-23 17:09:54 -03:00

11 KiB

Etapa 6 — Limpieza técnica: E2E Supabase + flakiness + documentación

Sprint: 5
Fecha: 2026-06-23
Alcance: tests E2E, botón ActivityPanel, documentación de cierre de sprint.


Contexto

Sprint 4 migró la persistencia de IndexedDB (Dexie) a Supabase. Dos specs E2E quedaron permanentemente rotos porque inyectan datos vía IndexedDB directamente:

  • tests/e2e/export-pdf.spec.ts
  • tests/e2e/validate-dod-etapa4.spec.ts

Adicionalmente, 3 specs fallan intermitentemente por timing al buscar el botón "Guardar cambios" antes de que esté habilitado.

Esta etapa no toca código de producto. Solo tests, un data-testid y documentación.


6a — Reescribir specs Dexie → Supabase

El problema

Ambos specs usan page.evaluate() para abrir IndexedDB directamente y escribir datos:

// patrón antiguo — NO funciona con Supabase
const db = await new Promise<IDBDatabase>((res, rej) => {
  const req = indexedDB.open('ProcessCostPlatform')
  req.onsuccess = () => res(req.result)
  // ...
})

Dexie/IndexedDB fue eliminado en Sprint 4. Las tablas ProcessCostPlatform no existen.

La solución

Reemplazar la inyección de datos por llamadas a la API REST de Supabase usando el service_role_key (bypasa RLS). El patrón es:

// en beforeAll / dentro del test
const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY)
const { data: activities } = await supabase
  .from('activities')
  .select('id, bpmn_element_id')
  .eq('process_id', processId)

await supabase.from('activities').upsert(
  activities.map(act => ({
    id: act.id,
    direct_cost_fixed: ACTUAL_COST[act.bpmn_element_id] ?? 200,
    execution_time_minutes: 60,
    automatable: AUTOMATABLE.has(act.bpmn_element_id),
    automated_cost_fixed: AUTOMATABLE.has(act.bpmn_element_id) ? AUTO_COST : 0,
    automated_time_minutes: AUTOMATABLE.has(act.bpmn_element_id) ? AUTO_TIME : 0,
  }))
)

Credenciales: leer de .env.test (ya existe, gitignored). Agregar si no existe:

SUPABASE_SERVICE_ROLE_KEY=<service_role_key>

Ver .env.example para el formato esperado.

Cleanup post-test: en afterAll, borrar el proceso creado por el test (elimina en cascada actividades y simulaciones):

afterAll(async () => {
  if (createdProcessId) {
    await supabase.from('processes').delete().eq('id', createdProcessId)
  }
})

Estrategia de implementación

  1. El flujo UI se mantiene igual: import BPMN → esperar workspace → capturar processId de la URL
  2. Reemplazar el bloque de page.evaluate(... indexedDB ...) por llamadas Supabase directas (no desde el browser, sino desde el proceso de Node.js del test)
  3. Después de inyectar los datos, hacer await page.reload() para que el store de Zustand recargue desde Supabase (mismo patrón que tenía antes)
  4. Continuar con el flujo de simulación y export igual que antes

Notas de esquema

Las columnas de Supabase usan snake_case. Los campos relevantes en activities:

  • direct_cost_fixed (número, costo fijo por ejecución)
  • execution_time_minutes
  • automatable (boolean)
  • automated_cost_fixed
  • automated_time_minutes
  • process_id
  • bpmn_element_id

Verificar el esquema real ejecutando:

const { data } = await supabase.from('activities').select('*').limit(1)
console.log(Object.keys(data[0]))

antes de escribir el upsert definitivo.

Para el proceso, las columnas relevantes:

  • annual_frequency
  • analysis_horizon_years
  • automation_investment

6a-extra — Diagnóstico y fix de auth-setup.ts (probable bug)

Diagnóstico a realizar antes de continuar con 6a:

tests/e2e/setup/auth-setup.ts guarda la sesión en localStorage bajo la clave:

const storageKey = `sb-${projectRef}-auth-token`

Sin embargo, @supabase/auth-js v2 usa la clave supabase.auth.token (constante STORAGE_KEY en node_modules/@supabase/auth-js/dist/main/lib/constants.js). Esta discrepancia existe desde Sprint 4 cuando se configuró el setup.

Verificar:

  1. Correr npx playwright test tests/e2e/sprint-4-smoke.spec.ts y observar si los tests "Con sesión" realmente obtienen datos de Supabase o si pasan vacíos (sin auth real)
  2. Ejecutar el test app header shows user avatar — si pasa sin auth real, hay algo raro
  3. Leer la constante en node_modules/@supabase/auth-js/dist/main/lib/constants.js para confirmar la clave correcta

Fix si se confirma el bug:

En auth-setup.ts, cambiar:

const projectRef = new URL(supabaseUrl).hostname.split('.')[0]
const storageKey = `sb-${projectRef}-auth-token`

por:

const storageKey = 'supabase.auth.token'

Importante: si auth-state.json ya existe con la clave incorrecta, regenerarlo corriendo el setup tras el fix. El archivo es gitignored — puede regenerarse sin afectar a otros.

Impacto: si el bug existe, todos los specs con auth que leen datos de Supabase son inválidos (pasan porque no hay assertions que fallen con datos vacíos, no porque el auth funcione). Esto sería deuda crítica que afecta la confiabilidad de toda la suite E2E.

Si el bug no se confirma (la clave ya es correcta por alguna razón), documentarlo en un comentario en auth-setup.ts explicando por qué difiere de la constante de la librería.


6b — Fix flakiness "Guardar cambios"

El problema

El botón en ActivityPanel.tsx (línea ~592) es:

<Button
  size="sm"
  onClick={handleSave}
  disabled={!isDirty}
  className="w-full"
>
  {isDirty ? 'Guardar cambios' : 'Sin cambios'}
</Button>

No tiene data-testid. Los specs lo buscan con page.getByRole('button', { name: /Guardar/i }) sin esperar a que esté habilitado. Cuando isDirty = false (estado inicial), el botón dice "Sin cambios" y está deshabilitado — Playwright lo encuentra pero falla al hacer click.

Fix en ActivityPanel.tsx

Agregar data-testid="save-activity-btn" al botón:

<Button
  data-testid="save-activity-btn"
  size="sm"
  onClick={handleSave}
  disabled={!isDirty}
  className="w-full"
>
  {isDirty ? 'Guardar cambios' : 'Sin cambios'}
</Button>

Fix en los specs afectados

Identificar los 3 specs que tienen el patrón flaky (buscar getByRole('button', { name: /Guardar/i })):

  • tests/e2e/payback-etapa-2.spec.ts — función configureActivity
  • tests/e2e/export-roi.spec.ts — función configureActivity
  • Cualquier otro spec que use el mismo patrón

Reemplazar el patrón de click sin wait:

// ANTES (flaky)
const guardar = page.getByRole('button', { name: /Guardar/i })
await guardar.click()
await page.waitForTimeout(500)

// DESPUÉS (determinístico)
await page.waitForSelector('[data-testid="save-activity-btn"]:not([disabled])', { timeout: 10_000 })
await page.click('[data-testid="save-activity-btn"]')
// Esperar confirmación de guardado (desaparece el isDirty):
await page.waitForSelector('[data-testid="save-activity-btn"][disabled]', { timeout: 10_000 })

6c — Documentación de cierre de sprint

BACKLOG.md — marcar como RESOLVED

Verificar si estos ítems siguen en estado pendiente y actualizarlos:

  1. [SPRINT-5] Indicador de simulación desactualizada — implementado en Sprint 3
  2. [SPRINT-5] Edición inline de proceso/cliente — implementado en Sprint 2
  3. [SPRINT-5] Controles de zoom BPMN — implementado en Sprint 5 Etapa 2
  4. [SPRINT-5] Columna "Eficiencia de automatización" — implementado en Sprint 5 Etapa 5

Para cada uno: cambiar el tag a [RESOLVED] y agregar la línea **Sprint cerrado en:** Sprint N Etapa X.

STRATEGIC_DIRECTION.md — agregar cierre Sprint 4 y Sprint 5

Si el archivo existe, agregar una sección de cierre para ambos sprints:

Sprint 4 (cerrado 2026-06-18):

  • Decisión estratégica: refactor arquitectural completo (Supabase + auth). Sin features de producto nuevas. Justificación: la deuda técnica de IndexedDB bloqueaba features colaborativas.
  • Resultado: auth Google OAuth + Supabase, migración IndexedDB→PostgreSQL, RLS, catálogo global de recursos. ~550+ tests.

Sprint 5 (cerrado 2026-06-23):

  • Decisión estratégica: retorno al rebalanceo normal (60-70% features web). UX polish + quick wins.
  • Resultado: fix workspace loading, zoom BPMN, heatmap gradiente 4 stops + toggle, recursos por proceso, campo empresa, columna eficiencia, reescritura E2E Supabase.

OBSERVATIONS.md — preparar para Sprint 6

Una vez validadas todas las etapas del Sprint 5:

  1. Mover hallazgos pendientes a TECH_DEBT.md o BACKLOG.md según corresponda
  2. Reemplazar el contenido con el encabezado limpio para Sprint 6:
# OBSERVATIONS — Sprint 6

> Hallazgos descubiertos durante el sprint actual.
> ...

## Hallazgos activos

*(vacío)*

TECH_DEBT.md — marcar RESOLVED los ítems resueltos en Etapa 6

  • [BACKLOG] E2E specs con Dexie directo[RESOLVED] con nota de la solución
  • [BACKLOG] E2E flakiness — "Guardar cambios" button timing[RESOLVED] con nota

Criterios de validación

Tests

  • npx playwright test tests/e2e/export-pdf.spec.ts — verde (o skip explícito si el test user no está disponible en el entorno local, con comentario en el spec)
  • npx playwright test tests/e2e/validate-dod-etapa4.spec.ts — verde
  • Los 3 specs flaky de "Guardar cambios" corren 3 veces seguidas sin fallar
  • npx playwright test tests/e2e/sprint-4-smoke.spec.ts — sin regresiones
  • npm run test — mantener o superar conteo anterior de Vitest

Código

  • data-testid="save-activity-btn" presente en ActivityPanel.tsx
  • npm run lint sin errores
  • npm run build limpio

Documentación

  • BACKLOG.md actualizado
  • STRATEGIC_DIRECTION.md con cierres Sprint 4 y 5
  • OBSERVATIONS.md preparado para Sprint 6

Validación del director

  • Revisar los 3 archivos de documentación antes del OK formal

Orden de ejecución recomendado

  1. Primero: diagnóstico de auth-setup.ts (6a-extra) — determina si la suite E2E con auth funciona realmente
  2. Segundo: fix de auth-setup.ts si aplica + regeneración de auth-state.json
  3. Tercero: data-testid en ActivityPanel (6b) — bajo esfuerzo, sin riesgo
  4. Cuarto: fix de timing en specs (6b) — depende del data-testid
  5. Quinto: reescritura E2E Dexie→Supabase (6a) — depende de que auth funcione
  6. Último: documentación (6c) — solo cuando todo lo técnico esté verde

Archivos a modificar

  1. tests/e2e/setup/auth-setup.ts — fix localStorage key (si se confirma el bug)
  2. tests/e2e/export-pdf.spec.ts — reescribir inyección de datos (Dexie → Supabase)
  3. tests/e2e/validate-dod-etapa4.spec.ts — reescribir inyección de datos (Dexie → Supabase)
  4. tests/e2e/payback-etapa-2.spec.ts — fix timing guardar
  5. tests/e2e/export-roi.spec.ts — fix timing guardar
  6. src/features/workspace/ActivityPanel.tsx — agregar data-testid
  7. BACKLOG.md — actualizar estados
  8. STRATEGIC_DIRECTION.md — agregar cierres Sprint 4 y 5
  9. OBSERVATIONS.md — preparar para Sprint 6
  10. TECH_DEBT.md — marcar resueltos

No modificar archivos de código de producto (src/) salvo el data-testid en ActivityPanel.


Nota operativa

Si los specs E2E de Supabase no corren en el entorno local (falta SUPABASE_SERVICE_ROLE_KEY en .env.test), documentar el procedimiento en .env.example y agregar un test.skip condicional con mensaje descriptivo en lugar de dejar los tests rojos. Un skip explícito es mejor que un test permanentemente rojo.