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.tstests/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
- El flujo UI se mantiene igual: import BPMN → esperar workspace → capturar
processIdde la URL - Reemplazar el bloque de
page.evaluate(... indexedDB ...)por llamadas Supabase directas (no desde el browser, sino desde el proceso de Node.js del test) - 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) - 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_minutesautomatable(boolean)automated_cost_fixedautomated_time_minutesprocess_idbpmn_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_frequencyanalysis_horizon_yearsautomation_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:
- Correr
npx playwright test tests/e2e/sprint-4-smoke.spec.tsy observar si los tests "Con sesión" realmente obtienen datos de Supabase o si pasan vacíos (sin auth real) - Ejecutar el test
app header shows user avatar— si pasa sin auth real, hay algo raro - Leer la constante en
node_modules/@supabase/auth-js/dist/main/lib/constants.jspara 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ónconfigureActivitytests/e2e/export-roi.spec.ts— funciónconfigureActivity- 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:
[SPRINT-5] Indicador de simulación desactualizada— implementado en Sprint 3[SPRINT-5] Edición inline de proceso/cliente— implementado en Sprint 2[SPRINT-5] Controles de zoom BPMN— implementado en Sprint 5 Etapa 2[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:
- Mover hallazgos pendientes a TECH_DEBT.md o BACKLOG.md según corresponda
- 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 regresionesnpm run test— mantener o superar conteo anterior de Vitest
Código
data-testid="save-activity-btn"presente enActivityPanel.tsxnpm run lintsin erroresnpm run buildlimpio
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
- Primero: diagnóstico de
auth-setup.ts(6a-extra) — determina si la suite E2E con auth funciona realmente - Segundo: fix de
auth-setup.tssi aplica + regeneración deauth-state.json - Tercero:
data-testiden ActivityPanel (6b) — bajo esfuerzo, sin riesgo - Cuarto: fix de timing en specs (6b) — depende del
data-testid - Quinto: reescritura E2E Dexie→Supabase (6a) — depende de que auth funcione
- Último: documentación (6c) — solo cuando todo lo técnico esté verde
Archivos a modificar
tests/e2e/setup/auth-setup.ts— fix localStorage key (si se confirma el bug)tests/e2e/export-pdf.spec.ts— reescribir inyección de datos (Dexie → Supabase)tests/e2e/validate-dod-etapa4.spec.ts— reescribir inyección de datos (Dexie → Supabase)tests/e2e/payback-etapa-2.spec.ts— fix timing guardartests/e2e/export-roi.spec.ts— fix timing guardarsrc/features/workspace/ActivityPanel.tsx— agregardata-testidBACKLOG.md— actualizar estadosSTRATEGIC_DIRECTION.md— agregar cierres Sprint 4 y 5OBSERVATIONS.md— preparar para Sprint 6TECH_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.