diff --git a/.env.example b/.env.example index c294917..5aec59b 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,11 @@ VITE_SUPABASE_ANON_KEY= # en la tabla public.users. Va en .env.test (gitignored), no en .env.local. TEST_USER_EMAIL=test@inquality.com.py TEST_USER_PASSWORD= + +# service_role_key para inyección directa de datos en specs E2E (export-pdf.spec.ts, +# validate-dod-etapa4.spec.ts) — bypasa RLS para escribir costos/configuración de +# actividades sin pasar por la UI. Obtener desde Supabase Dashboard → Project Settings +# → API → service_role (secret). Va en .env.test (gitignored), NUNCA en .env.local +# ni en código que se sirva al browser. Si no está presente, esos specs hacen skip +# explícito con mensaje descriptivo en lugar de fallar. +SUPABASE_SERVICE_ROLE_KEY= diff --git a/OBSERVATIONS.md b/OBSERVATIONS.md index ff8437f..86a6ae0 100644 --- a/OBSERVATIONS.md +++ b/OBSERVATIONS.md @@ -90,6 +90,23 @@ Dos causas independientes bajo el mismo síntoma ("la app se congela"): --- +### [RESOLVED] Etapa 5 — Columna "Eficiencia de automatización" en ComparisonTable + +**Estado:** implementado, tests verdes (542 Vitest). Pendiente OK formal del director. +**Entregado:** dos columnas A/B para focus group — "Ahorro esperado" (`ahorro$ × executionProbability`) y "Eficiencia" (`ahorro$ × executionProbability / executionTimeMinutes`, formato `valor currency/min`). Sort multi-columna con headers clickeables, tooltips explicativos, barras de progreso proporcionales (`MetricCell`). PDF explícitamente fuera de scope (6ª columna no entra en 138mm de ancho). +**Desviación de scope necesaria:** cambiar el sort por defecto a `efficiency` rompía un test existente en `roi-components.test.tsx` que asumía que un solo click invertía el orden. Ajustado a doble-click (activar columna → invertir) para ejercitar el comportamiento real sin dejar la suite roja — el propio prompt exigía `npm run test` verde. +**Carry-over a BACKLOG:** celda de Eficiencia corta el valor+unidad en dos líneas por ancho — falta `whitespace-nowrap` en el `` de `MetricCell`. + +### [RESOLVED] Etapa 6 — Limpieza técnica: E2E Supabase + flakiness + documentación + +**Estado:** implementado, 542 Vitest + 9 smoke E2E + 6 specs de payback/ROI (3 corridas) verdes. Pendiente OK formal del director. +**Diagnóstico 6a-extra (auth-setup.ts):** bug NO confirmado. `auth-js` define `STORAGE_KEY='supabase.auth.token'` como default de `GoTrueClient`, pero `@supabase/supabase-js`'s `createClient()` lo sobrescribe calculando `sb-${projectRef}-auth-token` desde la URL — exactamente la clave que ya usaba `auth-setup.ts`. Confirmado empíricamente: smoke test "app header shows user avatar" (aserción no-vacía, requiere sesión real) pasa; `auth-state.json` generado contiene la clave esperada. Documentado inline en `auth-setup.ts`. +**6b — data-testid + fix flakiness:** `data-testid="save-activity-btn"` en `ActivityPanel.tsx`. `payback-etapa-2.spec.ts` y `export-roi.spec.ts` reemplazan click directo por `waitForSelector(':not([disabled])')` → click → `waitForSelector('[disabled]')`. Confirmado estable 3/3 corridas tras rebuild (el `webServer` de Playwright sirve `dist/` sin rebuildear — hay que `npm run build` antes de cada corrida E2E tras tocar `src/`). +**6a — E2E Dexie→Supabase:** `export-pdf.spec.ts` (test "Heatmap con gradiente real") y `validate-dod-etapa4.spec.ts` reescritos con `service_role_key`. Sin la key en `.env.test`, ambos hacen `test.skip()` con mensaje descriptivo — confirmado (2 skipped, 0 failed). `afterAll` limpia el proceso creado. +**Desviación de scope explícita:** se encontraron 9 specs adicionales + `GlobalSettingsPanel.tsx` con el mismo patrón flaky de "Guardar" sin `data-testid`. No tocados — el prompt listaba solo 2 specs y 1 archivo de producción. Documentado en TECH_DEBT.md como ítems nuevos. + +--- + ## Hallazgos diferidos (promocionar al cerrar sprint) *(vacío)* diff --git a/STRATEGIC_DIRECTION.md b/STRATEGIC_DIRECTION.md index d95522c..79f1efa 100644 --- a/STRATEGIC_DIRECTION.md +++ b/STRATEGIC_DIRECTION.md @@ -258,4 +258,40 @@ Sprint 2 tuvo 4 etapas de web app (1, 2, 3, 4), 2 de PDF (5, 6) y 0 de infraestr - **Modelo B (AS-IS / TO-BE):** ¿abrimos esa puerta o seguimos con Modelo A y mejoramos la experiencia del modelo actual? - **Catálogo global de recursos:** ¿el valor de compartir recursos entre procesos justifica la complejidad en Sprint 3, o esperar a Sprint 4 con más procesos cargados? - **Indicador de simulación desactualizada:** alto impacto en talleres, bajo esfuerzo. Candidato a entrar en Sprint 3. -- **Posicionamiento:** post-Sprint 2 es el momento comprometido para una primera conversación sobre herramienta interna vs. venta externa. \ No newline at end of file +- **Posicionamiento:** post-Sprint 2 es el momento comprometido para una primera conversación sobre herramienta interna vs. venta externa. + +--- + +## Reflexiones del cierre de Sprint 4 + +> Agregado el 2026-06-23 al cerrar formalmente Sprint 4 y 5 juntos (Sprint 5 Etapa 6). + +### Decisión estratégica + +Refactor arquitectural completo: migración de IndexedDB (Dexie) a Supabase, con autenticación Google Workspace OAuth. **Sin features de producto nuevas** — el sprint completo fue inversión en infraestructura. + +**Justificación:** la deuda técnica de IndexedDB (persistencia local, sin colaboración multi-usuario) bloqueaba cualquier feature colaborativa futura (compartir procesos entre consultores, catálogo de recursos entre cuentas, identidad de "quién editó qué"). Postergar el refactor habría compuesto el costo de migración a medida que crecía el volumen de datos en producción. + +### Resultado + +Auth Google OAuth + Supabase, migración completa IndexedDB→PostgreSQL, RLS (`is_platform_admin()` + políticas granulares), catálogo global de recursos (sin `processId`), identidad en UI (avatar/dropdown, "Actualizado por"), `ResourceCatalogPage` + `ResourceFormSheet`, fix Web Locks API deadlock (`processLock`). ~550+ tests al cierre. + +**Cerrado:** 2026-06-18. + +--- + +## Reflexiones del cierre de Sprint 5 + +> Agregado el 2026-06-23 al cerrar formalmente el sprint. + +### Decisión estratégica + +Retorno al rebalanceo normal (60-70% features de producto web, 10-15% infraestructura/deuda técnica) tras el sprint 100% infraestructural de Sprint 4. Foco en UX polish y quick wins de alto impacto/bajo esfuerzo — explícitamente sin features de gran escala (historial de simulaciones, `setShared()` real, Keycloak, PWA, i18n, Excel). + +### Resultado + +Fix crítico workspace loading (race condition auth/processId), zoom BPMN (fit-viewport con padding + controles +/−/fit), heatmap con gradiente continuo de 4 stops + toggle percentile/min-max persistido en localStorage, "ver procesos por recurso" en catálogo, campo empresa en perfil de usuario, columna "Eficiencia de automatización" en `ComparisonTable` (A/B con "Ahorro esperado" para focus group), reescritura de specs E2E Dexie→Supabase, fix de flakiness en botón "Guardar cambios". + +**Hallazgo operativo del sprint:** el bug de freeze al guardar perfil (Etapa 4) requirió 12 commits y ~10 iteraciones de diagnóstico basado en lectura de código fuente de Supabase/Radix antes de resolverse. El quiebre llegó cuando el director compartió el HTML real de la pantalla congelada en vez de seguir teorizando sobre el comportamiento esperado de las librerías — un dato observable (`document.body.style.pointerEvents = 'none'`) colapsó el espacio de hipótesis de inmediato. Ver `sprints/sprint-5/INFORME_ETAPA_4_FREEZE_PERFIL.md` y memoria de feedback asociada. + +**Cerrado:** 2026-06-23 (pendiente validación visual final del director sobre Etapas 5 y 6). \ No newline at end of file diff --git a/TECH_DEBT.md b/TECH_DEBT.md index 682ab16..dfb560c 100644 --- a/TECH_DEBT.md +++ b/TECH_DEBT.md @@ -47,6 +47,16 @@ en los 3 PDFs. Si hay truncamiento, evaluar: ## Deuda media — afecta DX o evolución del producto +### [INVESTIGATING] Clave de localStorage del auth E2E vs producción +**Estado actual:** contradicción no resuelta entre dos hechos empíricos verificados: +- En producción, `ProfileSheet.getAccessToken()` lee `supabase.auth.token` y funciona correctamente (Sprint 5 Etapa 4). +- En E2E, `auth-setup.ts` escribe la sesión bajo `sb-${projectRef}-auth-token` y el smoke test "app header shows user avatar" pasa. +**Hipótesis:** el cliente Supabase de la app (`src/lib/supabase.ts`) y el de `auth-setup.ts` podrían estar usando `storageKey` distinto. La documentación inline en `auth-setup.ts` explica el comportamiento observado pero no reconcilia la contradicción. +**Impacto:** si el auth E2E no funciona realmente (sesión vacía), todos los specs con `storageState` serían inválidos — pasarían por ausencia de assertions fuertes, no porque el auth funcione. +**Acción:** en Sprint 6, comparar el `localStorage` real en producción vs el `auth-state.json` del E2E. Ejecutar un spec que haga una query autenticada a Supabase y afirme que devuelve datos reales del usuario de test. +**Archivos afectados:** `tests/e2e/setup/auth-setup.ts`, `src/lib/supabase.ts`. +**Origen:** Sprint 5 Etapa 6 diagnóstico de auth-setup. + ### [BACKLOG] `getCurrentUser()` en SupabaseAuthService — sin callers activos **Estado actual:** `SupabaseAuthService.getCurrentUser()` usa `getSession()` directamente (sin timeout, sin processLock workaround). Es la única excepción documentada a la regla — justificada porque necesita el objeto sesión completo, no solo una barrera. Sin callers activos hoy. **Impacto si no se resuelve:** ninguno mientras no tenga callers. Si en el futuro algún flujo llama `getCurrentUser()`, podría colgar el processLock si el servidor no responde. @@ -60,17 +70,27 @@ en los 3 PDFs. Si hay truncamiento, evaluar: **Archivos afectados:** `src/components/ProfileSheet.tsx`. **Origen:** Sprint 5 Etapa 4G (fix intermedio que quedó al resolverse la causa raíz en 4H). -### [BACKLOG] E2E specs con Dexie directo — reescribir para Supabase -**Estado actual:** `export-pdf.spec.ts` y `validate-dod-etapa4.spec.ts` inyectan datos vía Dexie/IndexedDB directamente. Dexie fue eliminado en Sprint 4 Etapa 4 — estos specs fallan en cualquier entorno donde no exista la DB local del sprint anterior. -**Impacto:** 2 specs permanentemente rojos. No bloquean build ni deploy, pero degradan la señal de la suite E2E. -**Solución propuesta:** reescribir los fixtures de datos para insertar vía Supabase (usando el test user `martin.bernal@inquality.com.py` y el `service_role_key` en el setup de Playwright). Limpiar datos de test después de cada corrida. -**Costo:** medio. Requiere diseño cuidadoso de fixtures para no interferir con datos de producción. +### [RESOLVED] E2E specs con Dexie directo — reescribir para Supabase +**Resultado:** `export-pdf.spec.ts` (test "Heatmap con gradiente real") y `validate-dod-etapa4.spec.ts` reescritos para inyectar datos vía `@supabase/supabase-js` con `SUPABASE_SERVICE_ROLE_KEY` (bypasa RLS), en lugar de IndexedDB directo. `afterAll` borra el proceso creado por cada test (cascada en `activities`/`simulations`). Si `SUPABASE_SERVICE_ROLE_KEY` no está configurada en `.env.test`, ambos tests hacen `test.skip()` con mensaje descriptivo en vez de fallar — ver `.env.example`. Diagnóstico previo de `auth-setup.ts` confirmó que la storageKey (`sb-${projectRef}-auth-token`) ya era correcta (no era parte del problema): `@supabase/supabase-js`'s `createClient()` sobrescribe el `STORAGE_KEY` default de `auth-js` con esa clave calculada desde la URL. +**Sprint cerrado en:** Sprint 5 Etapa 6. -### [BACKLOG] E2E flakiness — "Guardar cambios" button timing en ActivityPanel -**Estado actual:** 3 specs de PDF fallan intermitentemente porque Playwright no encuentra el botón "Guardar cambios" en estado habilitado. Validación manual confirma que el botón SÍ funciona correctamente — es un issue de timing en el test, no un bug real. -**Causa probable:** el test hace click en la actividad y espera el botón sin suficiente guard sobre el estado inicial de `isDirty`. -**Solución propuesta:** agregar `await page.waitForSelector('[data-testid="save-btn"]:not([disabled])')` o un `data-testid` explícito que permita espera determinística. -**Costo:** bajo. Una línea por spec afectado. +### [RESOLVED] E2E flakiness — "Guardar cambios" button timing en ActivityPanel +**Resultado:** agregado `data-testid="save-activity-btn"` al botón en `ActivityPanel.tsx`. Specs afectados (`payback-etapa-2.spec.ts`, `export-roi.spec.ts`) reemplazan el click directo por `waitForSelector(':not([disabled])')` → click → `waitForSelector('[disabled]')` (confirma que el guardado se procesó). Confirmado estable en 3 corridas consecutivas sin fallos. `GlobalSettingsPanel.tsx` tiene el mismo patrón `isDirty`/disabled en su botón "Guardar configuración" pero no se tocó — fuera del scope de Etapa 6; queda como ítem nuevo abajo. +**Sprint cerrado en:** Sprint 5 Etapa 6. + +### [BACKLOG] GlobalSettingsPanel — mismo patrón de flakiness potencial que ActivityPanel +**Estado actual:** `GlobalSettingsPanel.tsx` tiene un botón "Guardar configuración" con el mismo guard `disabled={!isDirty}` que `ActivityPanel.tsx` tenía antes de Etapa 6. La función `configureGlobal()` en `payback-etapa-2.spec.ts` y `export-roi.spec.ts` sigue usando `getByRole('button', { name: /Guardar/i }).click()` sin esperar a que se habilite — el mismo riesgo de flakiness, no confirmado como activo todavía. +**Impacto:** potencial, no confirmado. Estos specs pasaron 3/3 en Etapa 6 sin tocar esta función. +**Solución propuesta:** agregar `data-testid="save-global-btn"` a `GlobalSettingsPanel.tsx` y aplicar el mismo patrón de `waitForSelector` que se usó para `save-activity-btn`. +**Costo:** bajo. Mismo patrón ya validado. +**Origen:** Sprint 5 Etapa 6 — detectado durante el diagnóstico, explícitamente fuera del scope del prompt (solo `ActivityPanel.tsx` listado en archivos a modificar). + +### [BACKLOG] 9 specs adicionales con el mismo patrón flaky de "Guardar" sin data-testid +**Estado actual:** además de los 2 specs corregidos en Etapa 6, otros 9 archivos (`sprint-1-5-final.spec.ts`, `screenshots-etapa-3.spec.ts`, `etapa-4-pdfs.spec.ts` a `etapa-9-badge.spec.ts`, `etapa-11-polish.spec.ts`) usan el mismo `getByRole('button', { name: /Guardar/i }).click()` sin esperar a `isDirty`. No confirmados como flaky activamente — fuera del scope explícito de Etapa 6 (que listaba solo 2 archivos a modificar). +**Impacto:** riesgo de flakiness latente, no confirmado. +**Solución propuesta:** aplicar el mismo patrón `waitForSelector('[data-testid="save-activity-btn"]:not([disabled])')` ahora que el testid existe en producción. +**Costo:** bajo, mecánico — mismo cambio repetido 9 veces. +**Origen:** Sprint 5 Etapa 6 — identificado durante el grep de diagnóstico, no aplicado por respeto al scope explícito del prompt. ### [INVESTIGATING] Dexie live queries (sincronización store ↔ db) diff --git a/src/features/workspace/ActivityPanel.tsx b/src/features/workspace/ActivityPanel.tsx index 413e2dd..3b6b991 100644 --- a/src/features/workspace/ActivityPanel.tsx +++ b/src/features/workspace/ActivityPanel.tsx @@ -587,6 +587,7 @@ export function ActivityPanel({ selectedElementId }: ActivityPanelProps) { {/* Guardar */}