198 lines
7.4 KiB
Markdown
198 lines
7.4 KiB
Markdown
# 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 && <ImportBpmnButton />} // 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 && <CreateProcessButton />}
|
|
```
|
|
|
|
### 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 && <ProcessGroupsPanel />}
|
|
```
|
|
|
|
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 && (
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
Procesos de <span className="font-medium">{orgName}</span>
|
|
</p>
|
|
)}
|
|
```
|
|
|
|
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 = '<tu-id>';
|
|
-- Después de probar:
|
|
UPDATE public.users SET platform_role = 'platform_admin' WHERE id = '<tu-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
|