# 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:

```ts
// 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:

```ts
// 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):

```ts
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:
```ts
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:
```ts
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:
```ts
const projectRef = new URL(supabaseUrl).hostname.split('.')[0]
const storageKey = `sb-${projectRef}-auth-token`
```
por:
```ts
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:

```tsx
<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:

```tsx
<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:

```ts
// 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:

```markdown
# 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.
