diff --git a/sprints/sprint-7/ETAPA_5_PROMPT.md b/sprints/sprint-7/ETAPA_5_PROMPT.md
new file mode 100644
index 0000000..ad0bc78
--- /dev/null
+++ b/sprints/sprint-7/ETAPA_5_PROMPT.md
@@ -0,0 +1,197 @@
+# Etapa 5 — UX de roles de cliente y routing
+
+**Sprint:** 7
+**Fecha:** 2026-07-06
+**Alcance:** guards de rol en router + ajustes de UI en LibraryPage, WorkspacePage y ReportPage. Sin cambios en migraciones, Edge Functions, o panel admin.
+
+---
+
+## Contexto
+
+Las Etapas 1-4 construyeron la infraestructura. Esta etapa cierra la experiencia de usuario para `client_editor` y `client_viewer`:
+- El router impide que `client_viewer` entre al workspace
+- La Library oculta elementos internos de InQuality (crear proceso, grupos)
+- El workspace oculta "Importar BPMN" para `client_editor`
+- El reporte arranca en la tab ROI para `client_viewer`
+
+La RLS ya filtra los datos (un `client_editor` de Banco Solar solo ve procesos de Banco Solar). Esta etapa solo ajusta la UI para que el comportamiento sea coherente con esa realidad de datos.
+
+**ANTES de empezar:** leer el código actual de `src/router.tsx`, `LibraryPage.tsx`, `WorkspacePage.tsx` y `ReportPage.tsx`. Los nombres exactos de componentes y props pueden diferir del BRIEF.
+
+---
+
+## 5a — Guards de rol en el router
+
+### Workspace route (`/workspace/$processId`)
+
+`client_viewer` no puede editar → redirigir al reporte:
+
+```typescript
+beforeLoad: ({ context, params }) => {
+ const user = context.auth?.user
+ if (!user) throw redirect({ to: '/login' })
+ if (user.platformRole === 'client_viewer') {
+ throw redirect({ to: '/report/$processId', params: { processId: params.processId } })
+ }
+}
+```
+
+### Import BPMN route (si existe como ruta separada)
+
+Si hay una ruta `/import` o similar: solo `platform_admin` y `member` pueden acceder. `client_editor` y `client_viewer` → redirect a `/`.
+
+```typescript
+beforeLoad: ({ context }) => {
+ const user = context.auth?.user
+ const allowed = ['platform_admin', 'member']
+ if (!user || !allowed.includes(user.platformRole)) {
+ throw redirect({ to: '/' })
+ }
+}
+```
+
+### Rutas de admin (verificar que ya están protegidas)
+
+Las rutas `/admin/*` ya tienen guard de Etapa 4. Solo verificar que siguen funcionando después de los cambios de esta etapa.
+
+---
+
+## 5b — WorkspacePage: ocultar "Importar BPMN" para roles de cliente
+
+El botón o trigger de importación de BPMN solo debe aparecer para `platform_admin` y `member`.
+
+```typescript
+const { user } = useAuth()
+const canImport = user?.platformRole === 'platform_admin' || user?.platformRole === 'member'
+
+// En el JSX:
+{canImport && } // o el componente/botón equivalente
+```
+
+No deshabilitar el botón — ocultarlo completamente. Un `client_editor` no necesita saber que esa función existe.
+
+---
+
+## 5c — LibraryPage: UI ajustada para roles de cliente
+
+### Ocultar "Nuevo proceso" para client_editor y client_viewer
+
+```typescript
+const canCreateProcess = user?.platformRole === 'platform_admin' || user?.platformRole === 'member'
+
+{canCreateProcess && }
+```
+
+### Ocultar grupos/tags internos de InQuality para roles de cliente
+
+Los `process_groups` y el sistema de grupos/tags son navegación interna de InQuality. Para `client_editor` y `client_viewer`, la Library debe mostrar directamente la lista de procesos sin el panel lateral de grupos.
+
+```typescript
+const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
+
+// Ocultar completamente el panel de grupos si es rol cliente
+{!isClientRole && }
+```
+
+Si el layout actual tiene los grupos integrados de forma que no se pueden ocultar sin romper la estructura, simplificar: mostrar todos los procesos sin agrupación para roles de cliente (la RLS ya garantiza que solo ven los suyos).
+
+### Encabezado de bienvenida para roles de cliente
+
+Opcional (versión wow): si `isClientRole`, mostrar el nombre de la organización como contexto:
+
+```tsx
+{isClientRole && user?.orgId && (
+
+ Procesos de {orgName}
+
+)}
+```
+
+Para obtener `orgName`: hacer una query a `organizations` con `user.orgId`. Si ya hay un hook que lo cargue, usarlo. Si no, una query simple al cargar el componente.
+
+---
+
+## 5d — ReportPage: tab ROI por defecto para client_viewer
+
+En `ReportPage`, la tab activa inicial debe ser `roi` cuando el usuario es `client_viewer`:
+
+```typescript
+const { user } = useAuth()
+const defaultTab = user?.platformRole === 'client_viewer' ? 'roi' : 'actual'
+const [activeTab, setActiveTab] = useState(defaultTab)
+```
+
+Las otras tabs (Actual, Automatizado) permanecen visibles — `client_viewer` puede verlas pero las actividades son de solo lectura (esto ya está garantizado por RLS: no puede hacer UPDATE/INSERT). No es necesario bloquear la UI explícitamente en esta etapa; la RLS rechazará cualquier intento de escritura.
+
+Si el componente de tab activa ya es controlado desde el router (query param o similar), adaptar según el código real.
+
+---
+
+## Tests nuevos
+
+```typescript
+// tests/features/routing/role-guards.test.ts
+// Con vitest + mocks del AuthContext:
+
+// 1. client_viewer en /workspace/$id → redirect a /report/$id
+// 2. client_editor en /workspace/$id → NO redirige (permanece)
+// 3. client_editor en /import → redirect a /
+// 4. platform_admin en /import → NO redirige
+
+// tests/features/library/library-ui.test.ts
+// 5. client_editor: "Nuevo proceso" no renderiza
+// 6. client_editor: grupos panel no renderiza
+// 7. platform_admin: "Nuevo proceso" renderiza
+// 8. platform_admin: grupos panel renderiza
+
+// tests/features/report/report-default-tab.test.ts
+// 9. client_viewer: tab inicial = 'roi'
+// 10. platform_admin: tab inicial = 'actual' (o cualquier otro default actual)
+```
+
+---
+
+## Criterios de validación
+
+- [ ] `client_viewer` que navega a `/workspace/:id` → redirige a `/report/:id`
+- [ ] `client_editor` puede entrar al workspace normalmente
+- [ ] Botón "Importar BPMN" no visible para `client_editor` ni `client_viewer`
+- [ ] Botón "Nuevo proceso" no visible para `client_editor` ni `client_viewer`
+- [ ] Panel de grupos no visible para `client_editor` ni `client_viewer`
+- [ ] `ReportPage` con `client_viewer` abre en tab ROI
+- [ ] `ReportPage` con `platform_admin` abre en la tab que ya abre actualmente (no cambiar comportamiento existente)
+- [ ] `npm run test` → 558+ tests verdes (más los nuevos ~10)
+- [ ] `npm run build` limpio
+- [ ] **Validación visual del director antes del OK formal** (ver checklist abajo)
+
+---
+
+## Checklist de validación visual (para el director)
+
+Para validar esta etapa necesitás idealmente dos sesiones: una como `platform_admin` y una como `client_editor`. Si aún no hay usuario cliente invitado, la validación puede hacerse manualmente editando el `platform_role` en Supabase Studio:
+
+```sql
+-- Cambiar temporalmente para prueba (revertir después)
+UPDATE public.users SET platform_role = 'client_editor' WHERE id = '';
+-- Después de probar:
+UPDATE public.users SET platform_role = 'platform_admin' WHERE id = '';
+```
+
+Con rol `client_editor`:
+- [ ] Library: no aparece "Nuevo proceso", no aparece el panel de grupos
+- [ ] Workspace: no aparece el botón/opción de importar BPMN
+- [ ] Workspace: todo lo demás funciona (editar actividades, simular, ver reporte)
+
+Con rol `client_viewer` (mismo mecanismo de UPDATE temporal):
+- [ ] Intentar navegar a `/workspace/$id` → redirige al reporte
+- [ ] Reporte abre directo en tab ROI
+
+---
+
+## NO modificar
+
+- Panel de admin (`/admin/*`)
+- Migraciones (001-015)
+- Edge Functions
+- Lógica de simulación, dominio, cálculo de ROI, PDF, CSV
+- `AppHeader` más allá de lo estrictamente necesario
diff --git a/src/features/import/ImportPage.tsx b/src/features/import/ImportPage.tsx
index f7fc15b..f5a0a4d 100644
--- a/src/features/import/ImportPage.tsx
+++ b/src/features/import/ImportPage.tsx
@@ -1,5 +1,5 @@
import { useNavigate, useSearch } from '@tanstack/react-router'
-import { useCallback, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
import { UploadCloud, FileText, Loader2, FlaskConical } from 'lucide-react'
import { v4 as uuidv4 } from 'uuid'
import { Card, CardContent } from '@/components/ui/card'
@@ -43,6 +43,15 @@ export function ImportPage() {
const [isProcessing, setIsProcessing] = useState(false)
const [loadingSampleId, setLoadingSampleId] = useState(null)
+ // Solo platform_admin y member pueden importar BPMN
+ useEffect(() => {
+ if (!user) return
+ const allowed = ['platform_admin', 'member']
+ if (!allowed.includes(user.platformRole)) {
+ void navigate({ to: '/' })
+ }
+ }, [user, navigate])
+
// groupId viene de la URL cuando el usuario importa desde dentro de un grupo
const search = useSearch({ from: '/import' })
const targetGroupId = (search as { groupId?: string }).groupId ?? null
@@ -190,6 +199,9 @@ export function ImportPage() {
}
}
+ const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
+ if (isClientRole) return null
+
return (
diff --git a/src/features/library/LibraryPage.tsx b/src/features/library/LibraryPage.tsx
index 35b1346..ec460ee 100644
--- a/src/features/library/LibraryPage.tsx
+++ b/src/features/library/LibraryPage.tsx
@@ -447,8 +447,19 @@ function HomeView({
)
}
+ const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
+
// Empty state: sin grupos ni procesos (ya terminó de cargar)
if (groups.length === 0 && processes.length === 0) {
+ if (isClientRole) {
+ return (
+
+
+
No tenés procesos asignados aún
+
Contactá a tu administrador para que te asigne acceso
+
+ )
+ }
return (
@@ -527,8 +538,8 @@ function HomeView({
)}
- {/* Vista normal — grupos */}
- {!isSearching && (
+ {/* Vista normal — grupos (solo roles internos) */}
+ {!isSearching && !isClientRole && (
<>
{/* Tag cloud (wow): visible cuando hay >= 3 tags distintos */}
{cloud.length >= 3 && (
@@ -564,7 +575,7 @@ function HomeView({
)}
{/* Header sección grupos */}
-
+
{settings.groupLabel}s
@@ -616,6 +627,21 @@ function HomeView({
)}
>
)}
+
+ {/* Vista cliente: lista plana de procesos (sin grupos, RLS garantiza que solo ven los suyos) */}
+ {!isSearching && isClientRole && (
+