This commit is contained in:
249
sprints/sprint-5/BRIEF.md
Normal file
249
sprints/sprint-5/BRIEF.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Sprint 5 — BRIEF
|
||||
|
||||
> InQ ROI — Sprint de UX polish + quick wins
|
||||
> Inicio: 2026-06-19
|
||||
> Foco: experiencia del usuario, mejoras visuales, features de alto impacto/bajo esfuerzo
|
||||
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
Sprint 4 fue un refactor arquitectural completo (IndexedDB → Supabase, auth, recursos globales). El producto está estable y en uso interno. Sprint 5 vuelve al rebalanceo normal: **60-70% features de producto, 10-15% infra/deuda técnica**.
|
||||
|
||||
Sin features de gran escala (historial de simulaciones, compartir con guests). Solo mejoras que suman experiencia real.
|
||||
|
||||
---
|
||||
|
||||
## Etapas
|
||||
|
||||
### Etapa 1 — Fix crítico: workspace loading
|
||||
|
||||
**Problema:** Al navegar desde la biblioteca a un proceso, el workspace se queda cargando indefinidamente. La pantalla solo carga correctamente después de refrescar la página manualmente.
|
||||
|
||||
**Diagnóstico a confirmar:** probable race condition entre `useEffect` de carga del proceso y disponibilidad del usuario autenticado. El `processId` llega por route params antes de que el store tenga el usuario listo, y el query a Supabase se dispara con un contexto incompleto o se omite silenciosamente.
|
||||
|
||||
**Alcance:**
|
||||
- Diagnosticar el flujo completo: `WorkspacePage` → `useProcess` hook → `process-repo.getById()` → render
|
||||
- Identificar dónde el guard de auth interactúa mal con la carga del proceso
|
||||
- Corregir sin introducir flickering ni doble render
|
||||
- El workspace debe cargar al primer intento, sin refresh
|
||||
|
||||
**Criterios:**
|
||||
- [ ] Navegar desde la biblioteca a cualquier proceso carga el workspace en el primer intento
|
||||
- [ ] Sin errores en consola durante la navegación
|
||||
- [ ] El estado de loading muestra un skeleton o spinner visible (no pantalla en blanco)
|
||||
- [ ] Tests Vitest verdes (sin regresiones)
|
||||
- [ ] Validación visual del director
|
||||
|
||||
---
|
||||
|
||||
### Etapa 2 — Fix + mejora: zoom BPMN
|
||||
|
||||
**Problema:** El botón de fit-viewport actual posiciona el diagrama pegado al borde superior, sin padding. Además no hay controles de zoom explícitos.
|
||||
|
||||
**Alcance:**
|
||||
|
||||
**Fix fit-viewport:**
|
||||
- Corregir `canvas.zoom('fit-viewport')` para que el diagrama quede centrado con padding uniforme (sugerido: 40px en todos los bordes)
|
||||
- bpmn-js expone `canvas.zoom('fit-viewport', true)` y opciones de padding — usar la API correcta
|
||||
|
||||
**Controles de zoom (versión wow):**
|
||||
- Botón "+" (zoom in) y "−" (zoom out) en el canvas
|
||||
- Botón de reset/fit (ícono de encuadre) — corrige el bug actual + sirve de reset
|
||||
- Posición: esquina inferior derecha del canvas, sin tapar el diagrama
|
||||
- Estilo: botones cuadrados de 32px, fondo blanco semitransparente, borde sutil, ícono Tabler
|
||||
- No interferir con el zoom por scroll/pinch que ya funciona
|
||||
|
||||
**Íconos sugeridos:** `ti-zoom-in`, `ti-zoom-out`, `ti-focus-2` (para fit)
|
||||
|
||||
**Criterios:**
|
||||
- [ ] Fit-viewport centra el diagrama con padding uniforme (no clip al borde)
|
||||
- [ ] Botones +/−/fit visibles en el canvas
|
||||
- [ ] +/− hacen zoom in/out incremental (factor 1.2x por click)
|
||||
- [ ] Fit resetea a la vista centrada
|
||||
- [ ] Los controles no aparecen encima de los paneles laterales
|
||||
- [ ] Tests Vitest verdes
|
||||
- [ ] Validación visual del director
|
||||
|
||||
---
|
||||
|
||||
### Etapa 3 — Heatmap con gradiente continuo + toggle de modo
|
||||
|
||||
**Problema actual:** el heatmap usa 3 buckets discretos (verde/amarillo/rojo) con colores fijos. Sin matices intermedios, sin gradiente.
|
||||
|
||||
**Alcance:**
|
||||
|
||||
**Escala de color continua:**
|
||||
Reemplazar la lógica de 3 buckets por una función de interpolación de 4 stops:
|
||||
```
|
||||
0.0 → #10b981 (verde)
|
||||
0.33 → #f59e0b (amarillo)
|
||||
0.67 → #f97316 (naranja)
|
||||
1.0 → #ef4444 (rojo vivo)
|
||||
```
|
||||
|
||||
Implementar en `src/lib/colors.ts` como `intensityToColor(normalized: number): string` usando interpolación lineal en RGB entre stops consecutivos.
|
||||
|
||||
**Dos modos de normalización:**
|
||||
|
||||
`percentile` (default): cada actividad se posiciona según su rango relativo entre todas las actividades del proceso. No sufre distorsión por outliers.
|
||||
|
||||
`minmax`: `(costo - mínimo) / (máximo - mínimo)`. Refleja valores absolutos pero se comprime con outliers.
|
||||
|
||||
**Persistencia del modo:** guardar la preferencia en `localStorage` (key: `heatmap-mode`). Default: `percentile`.
|
||||
|
||||
**Toggle en UI:**
|
||||
- Un toggle compacto en el panel del reporte o en la barra de herramientas del workspace
|
||||
- Label: "Escala de color: Relativa (percentil) / Absoluta (min-max)"
|
||||
- Tamaño pequeño, no protagonista — es una preferencia técnica, no el flujo principal
|
||||
|
||||
**Dónde aplica el heatmap:**
|
||||
- Nodos del canvas BPMN (overlay de color en actividades)
|
||||
- Tabla del reporte web (fondo de celdas de costo)
|
||||
- PDF: usa los mismos valores calculados pero sin el toggle (renderiza el modo activo al exportar)
|
||||
|
||||
**Recalibrar E2E de píxeles:** los tests que verifican colores del canvas BPMN necesitan actualizar sus umbrales/valores esperados para la nueva escala. Identificar los specs afectados y corregirlos.
|
||||
|
||||
**Criterios:**
|
||||
- [ ] El gradiente muestra transiciones suaves (no saltos abruptos de verde a rojo)
|
||||
- [ ] Modo percentil: actividades distribuidas a lo largo de la escala incluso con outliers
|
||||
- [ ] Modo min-max: outlier extremo domina la escala (comportamiento esperado)
|
||||
- [ ] Toggle funciona y persiste entre sesiones
|
||||
- [ ] PDF exporta con el modo activo al momento de la exportación
|
||||
- [ ] Tests E2E de heatmap recalibrados y verdes
|
||||
- [ ] Tests Vitest verdes (nueva función `intensityToColor` con tests unitarios)
|
||||
- [ ] Validación visual del director
|
||||
|
||||
---
|
||||
|
||||
### Etapa 4 — Ver procesos por recurso + campo empresa en perfil
|
||||
|
||||
Dos features pequeñas en una etapa.
|
||||
|
||||
**Feature A — Ver procesos asociados a un recurso:**
|
||||
|
||||
En `ResourceCatalogPage` (`/recursos`), cada fila de la tabla debe mostrar cuántos procesos usan ese recurso.
|
||||
|
||||
- Nueva columna "Procesos" en la tabla: muestra un badge con el número (ej: `3 procesos`)
|
||||
- Si es 0: muestra "—" con color muted
|
||||
- Si es > 0: el badge es clickeable y expande una lista de los procesos (nombre + link directo al workspace)
|
||||
- La expansión puede ser inline (fila expandible) o un popover/tooltip al hover
|
||||
- Dato: query a `activity_resource_assignments` agrupado por `resource_id`, con JOIN a `processes` para el nombre
|
||||
- Esto complementa la protección de borrado existente — ahora el usuario ve *qué* proceso usa el recurso antes de intentar eliminarlo
|
||||
|
||||
**Feature B — Campo empresa en perfil de usuario:**
|
||||
|
||||
- Nueva columna `company text` en tabla `users` de Supabase (nullable, sin default)
|
||||
- Migración: `ALTER TABLE users ADD COLUMN IF NOT EXISTS company text;`
|
||||
- UI: en el dropdown del AppHeader (donde aparece nombre y email), agregar una línea con la empresa si está definida
|
||||
- Edición: agregar un botón "Editar perfil" en el dropdown que abre un Sheet con campos editables: nombre y empresa (email es de solo lectura — viene de Google OAuth)
|
||||
- La empresa se muestra también en la tabla de la biblioteca si hay espacio (opcional, decidir con validación visual)
|
||||
|
||||
**Criterios:**
|
||||
- [ ] Columna "Procesos" visible en `/recursos` con conteo correcto
|
||||
- [ ] Click en el badge con conteo > 0 muestra la lista de procesos con links funcionales
|
||||
- [ ] Columna `company` creada en Supabase sin romper datos existentes
|
||||
- [ ] El dropdown del AppHeader muestra la empresa si está definida
|
||||
- [ ] Sheet de edición de perfil guarda nombre y empresa en Supabase
|
||||
- [ ] Tests Vitest verdes
|
||||
- [ ] Validación visual del director
|
||||
|
||||
---
|
||||
|
||||
### Etapa 5 — Columna "Eficiencia de automatización"
|
||||
|
||||
**Contexto:** la tabla de comparación (ComparisonTable) hoy muestra costo AS-IS, costo TO-BE, ahorro y probabilidad. Le falta una métrica que permita *rankear* actividades por valor de automatización — no solo quién ahorra más en valor absoluto, sino qué actividades ofrecen el mejor retorno por peso invertido.
|
||||
|
||||
**Métrica:**
|
||||
```
|
||||
eficiencia = (ahorro$ × probabilidad) / tiempo_ejecucion_minutos
|
||||
```
|
||||
|
||||
Alternativa si no hay inversión por actividad: usar `ahorro$ × probabilidad` como proxy simple (ahorro esperado ponderado). Decidir en implementación cuál es más interpretable para un consultor.
|
||||
|
||||
**Alcance:**
|
||||
- Nueva columna "Eficiencia" en `ComparisonTable` del reporte web
|
||||
- Valor = ahorro esperado ponderado por probabilidad de automatización
|
||||
- Ordenar la tabla por esta columna por defecto (mayor eficiencia primero) — con posibilidad de cambiar el orden haciendo click en el header
|
||||
- Versión wow: barra de progreso inline de ancho relativo (la actividad más eficiente = 100%, las demás proporcionales). Mismo estilo que las barras del heatmap
|
||||
- Tooltip en el header de la columna explicando la fórmula
|
||||
- Si `isAutomatable = false`: mostrar "—" (em dash), igual que columna Ahorro
|
||||
|
||||
**En PDF:**
|
||||
- Incluir la columna en la tabla de comparación del PDF si entra sin truncamiento
|
||||
- Si no entra, omitirla (el PDF ya es denso) — decidir en validación visual
|
||||
|
||||
**Criterios:**
|
||||
- [ ] Columna visible en el reporte web con valores correctos
|
||||
- [ ] Actividades no automatizables muestran "—"
|
||||
- [ ] Orden por defecto: mayor eficiencia primero
|
||||
- [ ] Click en header reordena la tabla
|
||||
- [ ] Barra de progreso proporcional visible
|
||||
- [ ] Tooltip en header con explicación de la fórmula
|
||||
- [ ] Cálculo tiene tests unitarios en Vitest
|
||||
- [ ] Validación visual del director
|
||||
|
||||
---
|
||||
|
||||
### Etapa 6 — Limpieza técnica
|
||||
|
||||
**6a — E2E: reescribir specs Dexie → Supabase**
|
||||
- `export-pdf.spec.ts` y `validate-dod-etapa4.spec.ts` inyectan datos vía Dexie/IndexedDB, que fue eliminado en Sprint 4
|
||||
- Reescribir fixtures para usar el test user `martin.bernal@inquality.com.py` via Supabase
|
||||
- Limpiar datos de test después de cada corrida (usar `afterAll` con `service_role_key`)
|
||||
|
||||
**6b — E2E: fix flakiness "Guardar cambios"**
|
||||
- 3 specs fallan intermitentemente por timing: Playwright no encuentra el botón en estado habilitado
|
||||
- Fix: `await page.waitForSelector('[data-testid="save-btn"]:not([disabled])')` o equivalente
|
||||
- Agregar `data-testid="save-btn"` al botón en `ActivityPanel.tsx` si no existe
|
||||
|
||||
**6c — Limpieza de BACKLOG y documentación**
|
||||
- Marcar como `[RESOLVED]` en `BACKLOG.md`: "Indicador de simulación desactualizada", "Edición inline de proceso/cliente", "Controles de zoom BPMN" (resueltos en Sprints 2/3 y Etapa 2 de este sprint)
|
||||
- Actualizar `STRATEGIC_DIRECTION.md` con cierre de Sprint 4 (pendiente desde sesión anterior)
|
||||
- Vaciar `OBSERVATIONS.md` y preparar encabezado para Sprint 5
|
||||
- Marcar como `[RESOLVED]` en `TECH_DEBT.md` los ítems resueltos en esta etapa
|
||||
|
||||
**Criterios:**
|
||||
- [ ] `export-pdf.spec.ts` y `validate-dod-etapa4.spec.ts` verdes en CI con Supabase real
|
||||
- [ ] 3 specs flaky del botón "Guardar" estables
|
||||
- [ ] `npm run test` — mantener o superar conteo anterior de Vitest
|
||||
- [ ] `npx playwright test` — 0 specs permanentemente rojos
|
||||
- [ ] BACKLOG, TECH_DEBT, STRATEGIC_DIRECTION y OBSERVATIONS actualizados
|
||||
- [ ] Validación del director sobre el estado de los archivos
|
||||
|
||||
---
|
||||
|
||||
## Distribución del sprint
|
||||
|
||||
| Área | Etapas | % |
|
||||
|---|---|---|
|
||||
| Features de producto / UX | 1, 2, 3, 4, 5 | ~83% |
|
||||
| Infraestructura / deuda técnica | 6 | ~17% |
|
||||
| PDF | (toca en Etapa 3 y 5, sin etapa dedicada) | — |
|
||||
|
||||
Dentro del rango válido del rebalanceo (60-70% web, 10-15% infra). PDF sin etapa dedicada — aceptable este sprint dado que Sprint 4 no tocó PDF.
|
||||
|
||||
---
|
||||
|
||||
## Qué NO entra en Sprint 5
|
||||
|
||||
- Historial de simulaciones / múltiples simulaciones por proceso
|
||||
- Implementación real de `setShared()` (diferida hasta primer guest)
|
||||
- Keycloak / migración de auth
|
||||
- Modo offline / PWA
|
||||
- i18n
|
||||
- Export Excel
|
||||
- Gateway OR inclusivo
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done del sprint
|
||||
|
||||
- [ ] 6 etapas con OK formal del director (validación visual de cada una)
|
||||
- [ ] `npm run build` limpio
|
||||
- [ ] `npm run lint` sin warnings
|
||||
- [ ] Tests Vitest: mantener o superar conteo de Sprint 4
|
||||
- [ ] Tests E2E: 0 specs permanentemente rojos
|
||||
- [ ] CHECKLIST_ENTREGA.md ejecutado antes de cada entrega de etapa
|
||||
- [ ] OBSERVATIONS.md con hallazgos del sprint al cierre
|
||||
- [ ] STRATEGIC_DIRECTION.md actualizado con Sprint 4 y 5
|
||||
192
sprints/sprint-5/ETAPA_1_PROMPT.md
Normal file
192
sprints/sprint-5/ETAPA_1_PROMPT.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# 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()`:
|
||||
|
||||
```typescript
|
||||
// Antes:
|
||||
await supabase.auth.getSession()
|
||||
|
||||
// Después:
|
||||
await getSessionSafe()
|
||||
```
|
||||
|
||||
Agregar el import si no está:
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```typescript
|
||||
// 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`:
|
||||
|
||||
```tsx
|
||||
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-pulse` en 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`:
|
||||
|
||||
```tsx
|
||||
<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:
|
||||
|
||||
```tsx
|
||||
// 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
|
||||
|
||||
1. Abrir `src/store/process-store.ts` — reemplazar `getSession()` por `getSessionSafe()`
|
||||
2. Grep en `src/store/` y `src/persistence/` para `getSession()` — auditar y corregir
|
||||
3. Agregar campo `error` al store y manejo en `WorkspacePage`
|
||||
4. Crear `WorkspaceSkeleton` y usarlo mientras `isLoading === true`
|
||||
5. Crear `ErrorBoundary` (si no existe) y envolver el workspace
|
||||
6. `npm run build` — 0 errores TypeScript
|
||||
7. `npm run lint` — 0 warnings en archivos tocados
|
||||
8. `npm run test` — mantener conteo anterior de tests Vitest (sin regresiones)
|
||||
9. 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 build` limpio
|
||||
- [ ] `npm run test` — sin regresiones
|
||||
- [ ] **Validación visual del director** antes del OK formal
|
||||
Reference in New Issue
Block a user