7.8 KiB
Sprint 5 — Etapa 1: Fix crítico workspace loading
Contexto
InQ ROI — React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind + Supabase. Sprint 4 completado. La app tiene auth (Google OAuth + Supabase), PostgreSQL, RLS, catálogo de recursos global.
Bug en producción: al navegar desde la biblioteca (/) a un proceso (/workspace/:processId), la pantalla del workspace queda cargando indefinidamente. Solo carga correctamente después de un refresh manual de la página.
Causa raíz — ya diagnosticada, no requiere investigación adicional
El diagnóstico es preciso. No es necesario "investigar el flujo" — el problema está localizado.
src/store/process-store.ts llama a supabase.auth.getSession() directamente como barrera de sesión antes del fetch. Esta llamada no tiene timeout: si Supabase tarda en responder (token refresh en curso, red lenta, lock interno), getSession() se bloquea indefinidamente → loadProcess nunca resuelve → isLoading queda true para siempre.
src/store/library-store.ts hace lo mismo pero usa getSessionSafe() — una función en src/lib/supabase.ts que hace race entre getSession() y un timeout de 4 segundos, garantizando que siempre resuelve. Por eso la biblioteca carga y el workspace no.
La inconsistencia es sistémica: el mismo patrón puede existir en otros stores. Esta etapa lo audita y corrige en todos.
Parte 1 — Fix en process-store.ts
En src/store/process-store.ts, localizar la llamada a supabase.auth.getSession() (usada como barrera antes del query principal) y reemplazarla por getSessionSafe():
// Antes:
await supabase.auth.getSession()
// Después:
await getSessionSafe()
Agregar el import si no está:
import { supabase, getSessionSafe } from '@/lib/supabase'
Verificar que getSessionSafe ya existe en src/lib/supabase.ts. Si por alguna razón no existe, crearla siguiendo el mismo patrón que se usa en library-store.ts.
Parte 2 — Auditar todos los stores
Buscar en src/store/ y src/persistence/ cualquier archivo que llame a supabase.auth.getSession() directamente (sin pasar por getSessionSafe). Reportar qué archivos tienen este patrón.
Para cada archivo que lo use:
- Si es una barrera de sesión antes de un query → reemplazar por
getSessionSafe() - Si es otro uso (ej: verificar si hay sesión activa para tomar una decisión, no como barrera) → evaluar caso por caso y documentar la decisión
Parte 3 — Estado de error en el workspace
Problema adicional: actualmente, si getSessionSafe() agota su timeout, loadProcess lanza un error, pero el workspace no tiene un estado error propio. El resultado es que isLoading puede quedar true o la pantalla queda vacía sin mensaje al usuario.
Fix requerido: en process-store.ts (y el hook/componente que lo consume), agregar manejo de estado de error:
// En el store, el estado debe incluir:
{
isLoading: boolean
error: string | null // 🆕
process: Process | null
}
// En loadProcess, al catch:
set({ isLoading: false, error: 'No se pudo cargar el proceso. Revisá tu conexión.' })
En WorkspacePage.tsx (o el componente raíz del workspace), manejar el estado error:
if (error) {
return (
<div className="flex flex-col items-center justify-center h-full gap-4">
<p className="text-sm text-muted-foreground">{error}</p>
<Button variant="outline" onClick={() => loadProcess(processId)}>
Reintentar
</Button>
</div>
)
}
No usar alert() ni logs crudos. El error debe ser visible, accionable, y en español.
Parte 4 — Skeleton screen en el workspace
Estado actual: el workspace muestra un spinner genérico mientras carga. En una app moderna esto es insuficiente — el usuario no sabe qué está cargando ni cuánto ocupa la pantalla.
Requerimiento: reemplazar el spinner de carga del workspace por un skeleton screen que preserve el layout de la página.
El workspace tiene una estructura aproximada de: barra superior + panel BPMN (zona grande central/izquierda) + panel lateral (ActivityPanel/ResourcesPanel).
Crear un componente WorkspaceSkeleton que muestre:
- Header con altura igual al real (sin contenido)
- Zona del canvas BPMN: rectángulo con
animate-pulseen gris claro, misma altura aproximada - Panel lateral: 3-4 filas de skeleton (títulos y texto en gris)
Usar shadcn/ui Skeleton si ya está disponible en el proyecto, o implementar con Tailwind animate-pulse:
<div className="animate-pulse bg-muted rounded-md h-full w-full" />
El skeleton se muestra cuando isLoading === true y no hay process todavía. Una vez que process está disponible, el skeleton desaparece y el workspace real se monta.
Parte 5 — React Error Boundary en el workspace
Una app de nueva generación no deja que un error de renderizado en el workspace blankee la pantalla completa. Agregar un Error Boundary al nivel del workspace.
Si ya existe un ErrorBoundary en el proyecto (src/components/ErrorBoundary.tsx o similar), usarlo. Si no existe, crear uno mínimo:
// src/components/ErrorBoundary.tsx
import { Component, ReactNode } from 'react'
interface Props { children: ReactNode; fallback?: ReactNode }
interface State { hasError: boolean; error: Error | null }
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null }
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="flex flex-col items-center justify-center h-full gap-2 p-8">
<p className="text-sm font-medium text-destructive">Algo salió mal al cargar el workspace.</p>
<p className="text-xs text-muted-foreground">{this.state.error?.message}</p>
<button
className="text-xs underline text-muted-foreground"
onClick={() => window.location.reload()}
>
Recargar la página
</button>
</div>
)
}
return this.props.children
}
}
Envolver WorkspacePage (o el contenido del workspace) con <ErrorBoundary> en el router o en App.tsx.
Qué NO entra en esta etapa
- Cambios en el diseño visual del workspace (paneles, BPMN canvas, ActivityPanel)
- Cambios en la lógica de simulación
- Cambios en recursos, reporte, o PDF
- Nuevas features de producto
- Cambios en auth (login, logout, OAuth)
Orden de ejecución
- Abrir
src/store/process-store.ts— reemplazargetSession()porgetSessionSafe() - Grep en
src/store/ysrc/persistence/paragetSession()— auditar y corregir - Agregar campo
erroral store y manejo enWorkspacePage - Crear
WorkspaceSkeletony usarlo mientrasisLoading === true - Crear
ErrorBoundary(si no existe) y envolver el workspace npm run build— 0 errores TypeScriptnpm run lint— 0 warnings en archivos tocadosnpm run test— mantener conteo anterior de tests Vitest (sin regresiones)- Commit:
fix(sprint-5/etapa-1): workspace loading — getSessionSafe + error state + skeleton
Criterios de validación
- Navegar desde la biblioteca a cualquier proceso carga el workspace en el primer intento (sin refresh)
- Si el proceso no carga en ~5s, se muestra mensaje de error con botón "Reintentar"
- El estado de carga muestra un skeleton screen que preserva el layout (no spinner en blanco)
- Si hay un error de renderizado en el workspace, la app muestra el Error Boundary (no pantalla en blanco)
- Todos los stores auditados — ninguno usa
getSession()como barrera de sesión sin timeout npm run buildlimpionpm run test— sin regresiones- Validación visual del director antes del OK formal