docs(sprint-8): BRIEF.md — consolidación org→areas, client_editor BPMN, versionado, Bloque B
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
This commit is contained in:
610
sprints/sprint-8/BRIEF.md
Normal file
610
sprints/sprint-8/BRIEF.md
Normal file
@@ -0,0 +1,610 @@
|
|||||||
|
# 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 |
|
||||||
Reference in New Issue
Block a user