6.4 KiB
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:
LibraryPagemonta →useEffectllamaload()inmediatamente (sin guard de auth)load()llamasupabaseProcessRepo.getAll()— también sin guardgetAll()hacesupabase.from('processes').select('*')directo- En Supabase-js v2,
_initializeAuthFromStorage()es async — el cliente puede no tener la sesión lista cuando el request sale - El request sale sin JWT →
auth.uid()en PostgreSQL devuelve NULL → RLS bloquea todo → 0 filas - Milisegundos después,
onAuthStateChangedispara conINITIAL_SESSION→ sesión establecida - Pero
load()ya corrió y no vuelve a correr (sus deps no cambiaron)
En AuthContext.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:
// 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:
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():
// 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:
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
- Agregar logging en
process-repo.getAll()para confirmar diagnóstico - Aplicar Opción A: guard
if (!user) returnen LibraryPage useEffect - Aplicar Opción B:
await supabase.auth.getSession()enprocess-repo.getAll() - Verificar otros repos (paso 3 del alcance)
npm run build— debe pasar sin errores TypeScriptnpm run test— debe mantener 521+ tests verdes- Remover logging diagnóstico
- 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: falseen la primera llamada - Después de los fixes, el deploy en
inq-roi.inqualityhq.commuestra los procesos en la biblioteca npm run buildlimpio- 521+ tests verdes
- No hay
console.logde 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.