Files
inq-roi-simulador-web/sprints/sprint-8/BRIEF.md
2026-07-07 17:39:36 -03:00

611 lines
26 KiB
Markdown

# Sprint 8 BRIEF — InQ ROI
> **Estado:** borrador pendiente aprobación del director
> **Abierto:** 2026-07-07
> **Sprint anterior:** Sprint 7 — multi-tenancy (organizations, 4 roles, Edge Functions, Admin UI)
> **Versión base:** v0.8.0 (según SemVer: Sprint 8 arranca en v0.8.0)
---
## Contexto y dirección estratégica
Sprint 7 entregó la fundación de multi-tenancy. El producto puede invitar clientes, asignarles roles, y que vean solo sus procesos. Ahora hay tres gaps que bloquean el uso real con clientes:
1. **El modelo de "cliente" en la UI es inconsistente**`process_groups` (agrupador visual), `clientName` (texto libre), y `org_id` (FK real) son tres conceptos que intentan modelar lo mismo sin coordinación. Un admin que invita a Solar Banco no puede ver sus procesos agrupados como "Solar Banco" en la biblioteca.
2. **client_editor no puede subir su propio BPMN** — si Solar Banco tiene un proceso que quiere modelar, necesita pedirle a InQuality que lo importe. Esto no escala.
3. **No hay versión visible ni changelog** — el producto está en producción sin que nadie sepa qué versión está corriendo ni qué cambió.
**Objetivo del sprint:** consolidar el modelo de cliente, habilitar autonomía de client_editor para BPMN, y establecer el ciclo profesional de versiones. Sprint relativamente arquitectónico — ~40% DB/repos, ~40% UI/features, ~20% infra/deuda.
---
## Prioridades del sprint
### Bloque A — Comprometidas (must have)
| # | Feature | Tipo |
|---|---|---|
| Etapa 0 | Versionado: Conventional Commits + CHANGELOG + footer versión | Infra |
| Etapa 1 | DB: tabla `areas`, `area_id` en `processes`, drop `process_groups` y `clientName` | DB/migración |
| Etapa 2 | Repositorios: `area-repo.ts`, actualizar `process-repo.ts`, tipos y stores | Backend |
| Etapa 3 | LibraryPage: agrupar por org → área + ocultar Settings para roles cliente | UI |
| Etapa 4 | Workspace sidebar: selector de área reemplaza `clientName` + Admin CRUD de áreas | UI + Admin |
| Etapa 5 | client_editor BPMN: política INSERT + UPDATE bpmn_xml, botón importar visible | DB/UI |
### Bloque B — Si hay tiempo (prioridad 2)
| # | Feature | Tipo |
|---|---|---|
| Etapa 6 | Deuda técnica: guards → `beforeLoad` + hard delete + email en admin | Refactor |
| Etapa 7 | Página `/help` con documentación de uso para todos los roles | UI |
**Nota del director al Bloque B:** si las Etapas 0-5 consumen el sprint, Etapas 6-7 pasan a BACKLOG. No forzar a sprint siguiente sin capacidad real.
---
## Reglas del sprint (no negociables)
- `simulador-web/CLAUDE.md` tiene la guía completa de stack, reglas de marca, y la nueva sección de versionado (agregada como Etapa 0)
- **Validación visual mandatoria** en cada etapa que modifique UI antes del OK formal
- **Brief como contrato:** ninguna etapa arranca sin `ETAPA_X_PROMPT.md` aprobado
- **git diff antes de reportar:** incluir en cada reporte la lista de archivos modificados vs. los autorizados
- **Iniciativa agéntica clasificada** — Nivel 3 (marca, arquitectura, datos, TECH_DEBT.md) requiere consulta antes de ejecutar
- **`isProfileLoaded` como prerequisito** en cualquier componente nuevo condicionado por rol (aprendizaje Sprint 7)
- **JOIN vs fetch separado** — cuando un componente necesita datos relacionales para display, preferir JOIN en repo (aprendizaje Sprint 7)
---
## Etapa 0: Versionado profesional
**Objetivo:** establecer el ciclo de versiones antes de abrir cualquier feature nueva.
### Alcance
**1. Actualizar `simulador-web/CLAUDE.md`** — agregar sección "Gestión de versiones y commits" con:
```
Conventional Commits:
feat(scope): descripción
fix(scope): descripción
refactor(scope): descripción
test(scope): descripción
docs(scope): descripción
chore(scope): descripción
scopes comunes: auth, workspace, library, report, pdf, admin, db, e2e, help
SemVer:
MAJOR: cambio incompatible (nuevo modelo de datos, cambio de rol fundamental)
MINOR: feature nueva visible al usuario
PATCH: fix de bug o mejora de UX sin feature nueva
Sprint 8 arranca en v0.8.0
```
**2. Crear `CHANGELOG.md`** en raíz de `simulador-web/`, formato Keep a Changelog:
```markdown
# Changelog
All notable changes to InQ ROI will be documented in this file.
## [Unreleased]
## [0.7.0] - 2026-07-07
### Added
- Multi-tenancy: tabla organizations, 4 roles de plataforma
- Edge Functions invite-user y revoke-user
- Panel de administración /admin (Organizations, Users)
- Login email/password + página /reset-password
- back button contextual en workspace (orgName via JOIN en getById)
- Política simulations_update (fix para client_editor)
### Fixed
- Race condition AuthContext: isProfileLoaded guard
- syncProfileFromDB leyendo platform_role desde DB (no inferido por email)
- normalizeGroupLabel: limpia artículos iniciales de labels de grupos
## [0.6.0] - 2026-06-24
...
```
Completar el historial con los sprints anteriores hasta v0.1.0 (MVP).
**3. Actualizar `package.json`**`"version": "0.8.0"` (actualmente tiene algún valor anterior, verificar).
**4. Footer con versión visible** — crear `src/components/AppFooter.tsx`:
- Muestra `InQ ROI v{version}` leyendo de `src/lib/version.ts` (que importa de `package.json`)
- Visible en todas las páginas (LibraryPage, ReportPage, al menos)
- Texto pequeño, discreto, esquina inferior
**5. Tag de cierre de sprint:** al finalizar Sprint 8, `git tag -a v0.8.0 -m "Release v0.8.0: Sprint 8"`.
### Criterios de validación
- [ ] `CHANGELOG.md` existe con historial desde v0.1.0 hasta v0.7.0
- [ ] `package.json` tiene `"version": "0.8.0"`
- [ ] Footer visible en LibraryPage mostrando "InQ ROI v0.8.0"
- [ ] Build sin errores
### NO entra
- Página de "qué hay de nuevo" o modal de release notes
- Versionado de procesos (FASE-2)
---
## Etapa 1: Migración DB — tabla `areas`
**Objetivo:** crear la infraestructura de datos para el modelo `organizations → areas → processes`.
### Contexto
Los tres conceptos de "cliente" actuales:
| Concepto | Tabla/Campo | Problema |
|---|---|---|
| Agrupador visual | `process_groups` | Nombre confuso, sin FK a organizations |
| Campo libre | `clientName` (text en processes) | Sin normalización, sin validación |
| Tenant real | `org_id` en processes | Correcto pero desconectado de la UI |
### Alcance — Migración 017
```sql
-- 1. Crear tabla areas
CREATE TABLE public.areas (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
org_id uuid REFERENCES public.organizations(id) ON DELETE CASCADE NOT NULL,
name text NOT NULL,
description text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
-- 2. Agregar area_id en processes (reemplaza group_id)
ALTER TABLE public.processes ADD COLUMN area_id uuid REFERENCES public.areas(id) ON DELETE SET NULL;
-- 3. Eliminar client_name (texto libre) de processes
-- Verificar que clientName existe con su nombre exacto en la tabla
ALTER TABLE public.processes DROP COLUMN IF EXISTS client_name;
-- Nota: verificar nombre exacto antes de ejecutar (puede ser "client_name" o "clientName" en postgres)
-- 4. RLS para areas
ALTER TABLE public.areas ENABLE ROW LEVEL SECURITY;
CREATE POLICY "areas_select" ON public.areas
FOR SELECT USING (
-- InQuality ve todas; otros ven solo las de su org
public.is_inquality()
OR org_id = public.current_org()
);
CREATE POLICY "areas_insert" ON public.areas
FOR INSERT WITH CHECK (
-- Solo platform_admin puede crear áreas
public.current_platform_role() = 'platform_admin'
);
CREATE POLICY "areas_update" ON public.areas
FOR UPDATE USING (
public.current_platform_role() = 'platform_admin'
);
CREATE POLICY "areas_delete" ON public.areas
FOR DELETE USING (
public.current_platform_role() = 'platform_admin'
);
-- 5. Eliminar process_groups si existe
-- Primero verificar que processes.group_id existe y hacer SET NULL antes de drop
UPDATE public.processes SET area_id = NULL WHERE area_id IS NULL; -- no-op, sanity check
-- Si process_groups existe:
-- ALTER TABLE public.processes DROP COLUMN IF EXISTS group_id;
-- DROP TABLE IF EXISTS public.areas_old; -- nombre provisional
-- DROP TABLE IF EXISTS public.process_groups;
-- 6. Migración de datos: bulk-assign procesos sin org a InQuality
-- UPDATE public.processes SET org_id = '<inq_uuid>' WHERE org_id IS NULL;
-- (ejecutar con el UUID real de InQuality — buscar en SELECT id FROM organizations WHERE name = 'InQuality')
```
**Precaución importante:** antes de escribir los DROP/ALTER, auditar el schema real con:
```sql
SELECT column_name, data_type FROM information_schema.columns
WHERE table_name = 'processes' AND table_schema = 'public';
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name LIKE '%group%';
```
Reportar nombres exactos antes de ejecutar cualquier DROP.
### Criterios de validación
- [ ] Tabla `areas` creada con RLS activa
- [ ] `area_id` en `processes` nullable con FK
- [ ] `client_name` (o equivalente) eliminado de `processes`
- [ ] `process_groups` eliminada (verificar si existe)
- [ ] `group_id` en `processes` eliminado (si existía)
- [ ] Bulk-assign: todos los procesos tienen `org_id NOT NULL` (o decision explícita sobre los NULL)
- [ ] Tests unitarios de RLS: platform_admin puede CRUD en areas, client_editor no puede
### NO entra
- CRUD en la UI (eso es Etapa 4)
- Cambios en los repositorios de TypeScript (eso es Etapa 2)
---
## Etapa 2: Repositorios y tipos
**Objetivo:** crear `area-repo.ts` y actualizar `process-repo.ts` y los tipos de dominio para reflejar el nuevo modelo.
### Alcance
**1. Actualizar `src/domain/types.ts`:**
```typescript
// Nuevo tipo
export interface Area {
id: string
orgId: string
name: string
description: string | null
createdAt: string
}
// Actualizar Process — eliminar clientName, agregar areaId y areaName
export interface Process {
// ... campos existentes ...
orgId: string | null
orgName?: string | null // poblado via JOIN, no se persiste
areaId: string | null // ← nuevo, reemplaza groupId
areaName?: string | null // ← nuevo, poblado via JOIN
// eliminar: clientName, groupId (si existían)
}
```
**2. Crear `src/persistence/supabase/area-repo.ts`:**
```typescript
export interface AreaRepo {
getByOrg(orgId: string): Promise<Area[]>
create(data: { orgId: string; name: string; description?: string }): Promise<Area>
update(id: string, data: { name?: string; description?: string }): Promise<Area>
delete(id: string): Promise<void>
}
```
**3. Actualizar `src/persistence/supabase/process-repo.ts`:**
- `fromRow()`: extraer `areaId` y `areaName` (via JOIN cuando el query lo incluya)
- `getById()`: JOIN con `areas` para traer `areaName` embebido (patrón ya establecido con `orgName`)
- `getAll()` / `getByOrg()`: incluir `area_id` en select, agregar optional JOIN para `areaName`
- `create()`: incluir `area_id` en el objeto de insert (si está presente)
- `updateEditable()`: incluir `area_id` en los campos actualizables
- Eliminar cualquier referencia a `clientName` o `groupId`
**4. Actualizar Zustand store** (`src/store/process-store.ts` o equivalente):
- Si el store tenía lógica de `clientName`, actualizarla
- Verificar que el tipo `Process` del store usa los nuevos campos
**5. Actualizar `src/persistence/supabase/settings-repo.ts`** (si mantenía `process_groups`):
- Identificar si hay lógica ligada a `process_groups` y eliminarla o migrarla
### Criterios de validación
- [ ] `Area` type definido en `domain/types.ts`
- [ ] `Process.areaId` y `Process.areaName` presentes; `clientName`/`groupId` eliminados
- [ ] `area-repo.ts` con CRUD completo
- [ ] `process-repo.ts` actualizado: fromRow, getById (con JOIN), create, updateEditable
- [ ] Build TypeScript sin errores
- [ ] Tests unitarios de `area-repo.ts`
### NO entra
- Cambios en componentes React (Etapas 3 y 4)
- Settings page todavía puede romper — se verá en Etapa 3
---
## Etapa 3: LibraryPage — agrupación org → área
**Objetivo:** reemplazar la agrupación actual por `clientName` con agrupación real por `org → área`.
### Contexto de la UI actual
LibraryPage muestra procesos agrupados por `clientName` (texto libre). Hay un panel de "Clientes" (renderizado desde `process_groups`). Para roles cliente, muestra lista plana. El ícono de Settings (⚙) lleva a la página de configuración de grupos — visible para todos los roles.
### Alcance
**1. Lógica de agrupación:**
- Para `platform_admin` (InQuality): agrupar por `orgName` → dentro de cada org, sub-agrupar por `areaName`
- Para `client_editor` / `client_viewer`: mostrar lista plana de sus procesos (sin agrupación — la org es implícita)
- Grupos sin área asignada: mostrar en sección "Sin área" dentro de la org correspondiente
**2. Estructura visual (admin view):**
```
┌─ Solar Banco ───────────────────────────────────────────┐
│ ┌─ Área: Operaciones ──────────────────────────────┐ │
│ │ [proceso A] [proceso B] │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌─ Área: Recursos Humanos ─────────────────────────┐ │
│ │ [proceso C] │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌─ Sin área ───────────────────────────────────────┐ │
│ │ [proceso D] │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─ InQuality ─────────────────────────────────────────────┐
│ ... │
└─────────────────────────────────────────────────────────┘
```
**3. Eliminar el panel de "Clientes" (process_groups):**
- El componente o sección que renderizaba grupos desde `process_groups` se elimina
- Verificar qué componentes lo referencian
**4. Ocultar Settings para roles cliente:**
- El ícono ⚙ en LibraryPage se oculta para `client_editor` y `client_viewer`
- Agregar guard en la ruta `/settings` (o el path real): `client_editor` y `client_viewer` redirigen a `/library`
- Verificar si `/recursos` también debe ocultarse para clientes (reportar, no decidir unilateralmente)
**5. Criterio de wow:** al colapsar/expandir grupos, usar animación suave (Tailwind `transition-all`). Los grupos de org colapsan con click en el header.
### Criterios de validación
- [ ] Admin ve procesos agrupados por org → área (validación visual del director)
- [ ] client_editor ve lista plana de sus procesos (sin panel de grupos)
- [ ] ⚙ Settings no visible para client_editor y client_viewer
- [ ] Ruta `/settings` tiene guard de rol
- [ ] Animación de colapso funciona
- [ ] Build sin errores, tests existentes pasan
### NO entra
- CRUD de áreas (Etapa 4)
- Workspace sidebar (Etapa 4)
---
## Etapa 4: Workspace sidebar + Admin CRUD de áreas
**Objetivo:** (a) reemplazar el campo `clientName` libre en el workspace por un selector de área normalizado; (b) habilitar a admin para crear/editar/eliminar áreas desde `/admin`.
### 4A — Workspace sidebar
**Campo "Área" en el panel de configuración del proceso:**
- Reemplaza el campo de texto libre "Cliente" (`clientName`)
- Para `platform_admin`: dropdown de todas las áreas de la org del proceso (si tiene org asignada) + opción "Sin área". Si el proceso no tiene org, mostrar selector de org primero.
- Para `client_editor`: dropdown de áreas de su propia org (su org es implícita — no puede cambiarla)
- Para `client_viewer`: read-only, muestra nombre del área si existe
- Al cambiar área, actualizar `processStore` y hacer persist en Supabase vía `updateEditable()`
**Campo "Organización" en el workspace:**
- Para `platform_admin`: selector de org (dropdown de todas las orgs) — permite reasignar un proceso a otra org
- Para `client_editor`: read-only, muestra el nombre de su org
- Actualizar `org_id` en Supabase cuando el admin cambia la org
**Eliminar campo "Cliente" (clientName):**
- El campo de texto libre que existía en el sidebar se elimina completamente
### 4B — Admin CRUD de áreas
En `/admin/organizations/[orgId]` (o en la página de detalle de org que exista):
- Agregar sección "Áreas" con lista de áreas de la org
- CRUD: crear área (nombre + descripción opcional), renombrar, eliminar
- Al eliminar: si hay procesos con esa área, mostrar confirmación "X procesos quedarán sin área asignada"
- RLS garantiza que solo platform_admin puede hacer CRUD
**Estructura sugerida:**
```
/admin
/organizations
→ lista de orgs (ya existe)
/[orgId]
→ detalle de org: nombre, miembros, ÁREAS (nuevo)
```
Si `/admin/organizations/[orgId]` no existe, crear la página. Si ya existe (verificar), agregar la sección de Áreas.
### Criterios de validación
- [ ] Campo "Cliente" (texto libre) eliminado del workspace sidebar
- [ ] Selector de área funciona para platform_admin y client_editor (validación visual)
- [ ] Cambio de área persiste en Supabase (verificar Network tab)
- [ ] CRUD de áreas en admin funciona (crear, editar, eliminar con confirmación)
- [ ] Tests E2E: admin crea área, asigna proceso, client_editor ve sus procesos con área correcta
### NO entra
- Jerarquía de áreas (sub-áreas) — FASE-2
- Migrar todos los procesos existentes a áreas manualmente — eso se hace en bulk en la migración DB
---
## Etapa 5: client_editor — permisos de BPMN
**Objetivo:** habilitar a `client_editor` para importar nuevos procesos y actualizar el BPMN de procesos existentes de su org.
### Contexto
En Sprint 7 Etapa 5, `client_editor` fue bloqueado explícitamente de:
- Ver el botón "Importar BPMN" (redireccionado desde `/import`)
- Actualizar `bpmn_xml` vía `updateEditable()` (el campo no estaba en la lista de campos editables para ese rol)
Esta decisión fue provisional. Sprint 8 la revierte con las restricciones correctas.
### Alcance
**1. Migración 018 — política INSERT en `processes` para client_editor:**
```sql
-- Ampliar la política de INSERT para incluir client_editor
-- (auditar el nombre real de la política INSERT actual antes de DROP)
DROP POLICY IF EXISTS "processes_insert" ON public.processes;
CREATE POLICY "processes_insert" ON public.processes
FOR INSERT WITH CHECK (
-- platform_admin y member pueden insertar sin restricción de org
public.current_platform_role() IN ('platform_admin', 'member')
OR (
-- client_editor puede crear procesos solo en su propia org
public.current_platform_role() = 'client_editor'
AND org_id = public.current_org()
)
);
```
**2. Actualizar `updateEditable()` en `process-repo.ts`:**
- Incluir `bpmn_xml` en los campos actualizables cuando el caller tiene permisos
- Verificar que la política UPDATE existente ya permite que `client_editor` actualice `bpmn_xml` (debería estar cubierta por la política general de UPDATE que verifica org_id)
- Si no, agregar rama en la política UPDATE
**3. Botón "Importar BPMN" visible para client_editor:**
- En `ImportPage.tsx`: eliminar el guard que redirigía a `client_editor`
- `client_editor` accede a `/import`, selecciona BPMN, el proceso se crea con `org_id = current_org()`
- El `owner_id` se asigna como `auth.uid()` (el client_editor que importó)
**4. Restricciones que NO cambian:**
- `client_editor` no puede mover procesos entre orgs (no puede cambiar `org_id` a otra org)
- `client_editor` no puede cambiar `owner_id`
- `client_editor` no puede ver ni modificar procesos de otra org
### Criterios de validación
- [ ] Validar con el director usando cambio temporal de rol en Supabase Studio (técnica de Sprint 7)
- [ ] `client_editor` puede importar un BPMN nuevo → proceso creado con `org_id` correcto (verificar en Supabase Studio)
- [ ] `client_editor` puede abrir un proceso de su org y actualizar el BPMN (re-import)
- [ ] `client_editor` NO puede crear procesos con `org_id` de otra org (test E2E adversarial)
- [ ] Tests E2E: flujo completo import → workspace → update bpmn como client_editor
### NO entra
- client_editor con permisos en `resources` o en `admin` — no cambia
- Eliminar procesos (sigue siendo solo platform_admin)
---
## Etapa 6: Deuda técnica consolidada
**Objetivo:** resolver items de TECH_DEBT.md acumulados de Sprint 7. Bloque B — solo si hay tiempo.
### 6A — Guards de rol → `beforeLoad` (3 componentes)
Migrar de `useEffect` a `beforeLoad` de TanStack Router en:
- `AdminLayout.tsx`
- `WorkspacePage.tsx`
- `ImportPage.tsx`
Ejemplo de patrón correcto:
```typescript
// En el router (router.tsx o route file)
export const adminRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/admin',
beforeLoad: ({ context }) => {
if (!context.auth.isProfileLoaded) return // esperar
if (context.auth.user?.platformRole !== 'platform_admin') {
throw redirect({ to: '/library' })
}
},
})
```
**Restricción:** no cambiar el comportamiento de los guards, solo el mecanismo. Los tests E2E existentes deben seguir pasando.
### 6B — Hard delete de usuarios
Crear Edge Function `delete-user`:
- Llama a Supabase Auth Admin API: `supabase.auth.admin.deleteUser(userId)`
- Elimina registro de `public.users`
- Solo accesible para `platform_admin`
- Agregar botón "Eliminar permanentemente" en Admin → Users (con AlertDialog de confirmación doble: "Esta acción no puede deshacerse. El usuario perderá acceso permanentemente.")
- Diferencia respecto al soft delete (revoke-user): hard delete borra el auth record
### 6C — Email visible en Admin → Users
La tabla de usuarios en admin no muestra el email (que está en `auth.users`, no en `public.users`). Fix:
- En la Edge Function de listado de usuarios (o crear una nueva `list-users`), hacer join con `auth.users` via service_role
- Mostrar email en la tabla de Admin → Users
### Criterios de validación
- [ ] Guards migrados: no hay flash visual al navegar a ruta protegida (verificar con conexión lenta)
- [ ] Hard delete funciona y el usuario desaparece de la auth
- [ ] Email visible en Admin → Users
---
## Etapa 7: Página `/help`
**Objetivo:** documentación de uso del producto para todos los roles. Bloque B — solo si hay tiempo.
### Alcance
**Nueva ruta `/help`** accesible para todos los roles autenticados.
**Contenido mínimo (4 secciones):**
1. **Cómo parametrizar actividades**
- Tiempos de ejecución (tiempo nominal y AS-IS/TO-BE)
- Probabilidad de ejecución
- Recursos asignados y minutos de utilización
- Costo fijo por actividad
2. **Cómo simular y leer el reporte**
- Qué hace el botón "Simular"
- Indicador de simulación desactualizada
- Qué significa cada KPI del reporte
- Cómo leer el heatmap
- Columnas de la tabla comparativa (ahorro, eficiencia)
3. **Cómo exportar**
- PDF: qué incluye cada documento (Actual, Automatizado, ROI)
- CSV: estructura del archivo exportado
- Cuándo usar PDF vs. CSV
4. **Configuración del proceso (panel Global)**
- Campos del panel: nombre, descripción, volumen mensual, horas laborables
- Escenario automatizado: costo fijo TO-BE, inversión inicial, ROI
**UX:**
- Layout con sidebar de navegación a las 4 secciones
- Link a `/help` en el footer (junto a la versión)
- Criterio de wow: buscador simple de texto en la página
### Criterios de validación
- [ ] Las 4 secciones tienen contenido completo y preciso (validación visual del director)
- [ ] Link desde footer funciona
- [ ] Accesible para todos los roles
---
## Definiciones estables durante Sprint 8 (no reabrir)
- Stack tecnológico (sin cambios)
- Paleta cromática y sistema de componentes (sin cambios)
- Modelo de escenarios Modelo A (sin cambios)
- ROI: cálculos nominales simples (sin cambios)
- 4 roles de plataforma: `platform_admin`, `member`, `client_editor`, `client_viewer` (sin cambios)
---
## Convención de commits para Sprint 8
```
feat(areas): tabla areas + RLS en migración 017
feat(area-repo): CRUD de áreas por organización
feat(library): agrupación por org → área reemplaza clientName
feat(workspace): selector de área reemplaza campo clientName libre
feat(admin): CRUD de áreas en panel de organización
feat(client-editor): permisos BPMN insert + update vía migración 018
feat(help): página /help con 4 secciones de documentación de uso
fix(import): botón importar visible para client_editor
refactor(router): guards AdminLayout + WorkspacePage + ImportPage → beforeLoad
chore(version): v0.8.0, CHANGELOG.md, footer con versión
docs(changelog): etapa N sprint 8
```
---
## Archivos de referencia
| Archivo | Por qué leerlo antes |
|---|---|
| `simulador-web/CLAUDE.md` | Reglas de implementación, stack, marca, versionado |
| `simulador-web/STRATEGIC_DIRECTION.md` | Decisiones estratégicas para Sprint 8 (sección final) |
| `simulador-web/BACKLOG.md` | Item `[SPRINT-8]` con scope de org→areas |
| `simulador-web/TECH_DEBT.md` | Items guards + auth para Etapa 6 |
| `supabase/migrations/` | Auditar schema actual antes de escribir migraciones 017-018 |
| `src/persistence/supabase/process-repo.ts` | Entender fromRow y getById para extender con areaId |
| `src/features/library/LibraryPage.tsx` | Entender agrupación actual para reemplazarla |
| `src/features/workspace/WorkspacePage.tsx` | Entender sidebar actual para reemplazar clientName |
| `src/features/admin/` | Entender estructura de Admin UI para agregar CRUD de áreas |