# 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 |
