Files
inq-roi-simulador-web/sprints/sprint-4/ETAPA_5B_PROMPT.md
Marcos Benítez 81a9424f7f
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Solución del login colgado
2026-06-18 21:33:48 -03:00

154 lines
6.4 KiB
Markdown

# Sprint 4 — Etapa 5B: Fix race condition auth/fetch en LibraryPage
## Contexto
InQ ROI — React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
Deploy activo en `https://inq-roi.inqualityhq.com` (Dokploy/VPS).
El deploy muestra la biblioteca con 0 procesos a pesar de que existen 6 en Supabase con `owner_id` correcto, RLS configurado y GRANT EXECUTE en `is_platform_admin()` verificado. Los procesos NO son un problema de datos ni de permisos PostgreSQL. El problema es de timing en el cliente.
---
## Diagnóstico
**Síntoma observado:** "la primera vez veo invocaciones a Supabase, después no".
**Causa raíz identificada:** Race condition entre la inicialización de la sesión y el fetch de procesos.
El flujo actual es:
1. `LibraryPage` monta → `useEffect` llama `load()` inmediatamente (sin guard de auth)
2. `load()` llama `supabaseProcessRepo.getAll()` — también sin guard
3. `getAll()` hace `supabase.from('processes').select('*')` directo
4. En Supabase-js v2, `_initializeAuthFromStorage()` es **async** — el cliente puede no tener la sesión lista cuando el request sale
5. El request sale sin JWT → `auth.uid()` en PostgreSQL devuelve NULL → RLS bloquea todo → 0 filas
6. Milisegundos después, `onAuthStateChange` dispara con `INITIAL_SESSION` → sesión establecida
7. Pero `load()` ya corrió y **no vuelve a correr** (sus deps no cambiaron)
En `AuthContext.tsx`:
```tsx
// user se pre-popula desde localStorage SÍNCRONAMENTE — esto está bien
const [user, setUser] = useState<AuthUser | null>(
isOAuthRedirect ? null : getUserFromStorage()
)
```
Pero el objeto `supabase` (el cliente de supabase-js) es una instancia separada que inicializa su sesión interna de forma async. El hecho de que el AuthContext tenga el user en estado React no garantiza que el cliente Supabase tenga el token listo para adjuntar al request.
En `LibraryPage.tsx`:
```tsx
// Se llama SIN verificar si el cliente Supabase tiene sesión activa
useEffect(() => {
load()
}, [load])
```
---
## Alcance estricto — qué entra en esta etapa
### 1. Agregar logging diagnóstico (temporal, para verificar)
En `src/persistence/supabase/process-repo.ts`, en el método `getAll()`, agregar antes del query:
```typescript
const { data: sessionData } = await supabase.auth.getSession()
console.log('[process-repo] getAll session:', {
hasSession: !!sessionData.session,
userId: sessionData.session?.user?.id ?? 'NULL',
tokenPrefix: sessionData.session?.access_token?.substring(0, 30) ?? 'NO TOKEN'
})
```
Este log confirma si el problema es efectivamente que el cliente no tiene sesión al momento del fetch.
### 2. Fix principal — esperar sesión antes de fetch
**Opción A (recomendada): Guard en LibraryPage**
En `src/features/library/LibraryPage.tsx`, cambiar el useEffect que llama `load()`:
```tsx
// ANTES:
useEffect(() => {
load()
}, [load])
// DESPUÉS:
useEffect(() => {
if (!user) return // no fetchear hasta tener usuario confirmado
load()
}, [load, user])
```
Esto garantiza que `load()` solo corre cuando `user` (que viene de `AuthContext`) está definido. Como `AuthContext` llama `onAuthStateChange` que espera la confirmación de Supabase, cuando `user` se setea el cliente ya tiene la sesión lista.
**Opción B (alternativa si A no alcanza): Await getSession en getAll**
Si la Opción A no es suficiente (el cliente aún no tiene el token aunque user exista en React), agregar al inicio de `getAll()` en `process-repo.ts`:
```typescript
async getAll(): Promise<Process[]> {
// Garantiza que el cliente Supabase tenga la sesión antes de la query
await supabase.auth.getSession()
const { data, error } = await supabase
.from('processes')
.select('*')
.order('updated_at', { ascending: false })
if (error) throw error
return (data ?? []).map(fromRow)
},
```
`getSession()` en Supabase-js v2 espera a que `_initializeAuthFromStorage()` complete antes de retornar. Es seguro llamarlo múltiples veces — es idempotente.
**Implementar ambas opciones** — son complementarias y defensivas.
### 3. Verificar si el mismo problema afecta otros repos
Si la Opción B se aplica en `process-repo.ts`, verificar si los demás repos (`activity-repo.ts`, `resource-repo.ts`, `simulation-repo.ts`, `gateway-repo.ts`) tienen el mismo patrón `supabase.from(...).select(...)` sin await previo de getSession. Si sí, aplicar el mismo fix en todos los métodos de lectura inicial (los que se llaman al montar componentes, no los que se llaman por acción del usuario).
### 4. Remover logging diagnóstico antes de commit
Una vez verificado que el fix funciona (los procesos aparecen en la biblioteca), remover los `console.log` agregados en el paso 1.
---
## Qué NO entra en esta etapa
- Cambios en las RLS policies (ya están correctas)
- Cambios en el AuthContext más allá del guard en LibraryPage
- Features de UI de Etapa 5 (ownerId, badges, toggle Todos/Míos) — esas van en Etapa 5 original
- Ningún cambio en PDF, simulación o workspace
---
## Orden de ejecución
1. Agregar logging en `process-repo.getAll()` para confirmar diagnóstico
2. Aplicar Opción A: guard `if (!user) return` en LibraryPage useEffect
3. Aplicar Opción B: `await supabase.auth.getSession()` en `process-repo.getAll()`
4. Verificar otros repos (paso 3 del alcance)
5. `npm run build` — debe pasar sin errores TypeScript
6. `npm run test` — debe mantener 521+ tests verdes
7. Remover logging diagnóstico
8. Commit: `fix(sprint-4/etapa-5b): race condition auth/fetch en LibraryPage`
---
## Criterios de validación
- [ ] Log diagnóstico confirma que antes del fix `hasSession: false` en la primera llamada
- [ ] Después de los fixes, el deploy en `inq-roi.inqualityhq.com` muestra los procesos en la biblioteca
- [ ] `npm run build` limpio
- [ ] 521+ tests verdes
- [ ] No hay `console.log` de diagnóstico en el código final
- [ ] **Validación visual del director** antes del OK formal: la biblioteca carga los procesos
---
## Nota de contexto para el fix
El objeto `supabase` (singleton creado en `src/persistence/supabase/client.ts` o similar) y el estado React `user` en `AuthContext` son dos sistemas paralelos. El `AuthContext` puede tener `user !== null` (leído de localStorage síncronamente) mientras que el cliente Supabase todavía está en proceso de restaurar la sesión internamente. El `await supabase.auth.getSession()` actúa como barrera de sincronización entre ambos sistemas.