26 KiB
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:
-
El modelo de "cliente" en la UI es inconsistente —
process_groups(agrupador visual),clientName(texto libre), yorg_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. -
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.
-
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.mdtiene 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.mdaprobado - 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
isProfileLoadedcomo 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:
# 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 desrc/lib/version.ts(que importa depackage.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.mdexiste con historial desde v0.1.0 hasta v0.7.0package.jsontiene"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
-- 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:
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
areascreada con RLS activa area_idenprocessesnullable con FKclient_name(o equivalente) eliminado deprocessesprocess_groupseliminada (verificar si existe)group_idenprocesseseliminado (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:
// 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:
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(): extraerareaIdyareaName(via JOIN cuando el query lo incluya)getById(): JOIN conareaspara traerareaNameembebido (patrón ya establecido conorgName)getAll()/getByOrg(): incluirarea_iden select, agregar optional JOIN paraareaNamecreate(): incluirarea_iden el objeto de insert (si está presente)updateEditable(): incluirarea_iden los campos actualizables- Eliminar cualquier referencia a
clientNameogroupId
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
Processdel 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_groupsy eliminarla o migrarla
Criterios de validación
Areatype definido endomain/types.tsProcess.areaIdyProcess.areaNamepresentes;clientName/groupIdeliminadosarea-repo.tscon CRUD completoprocess-repo.tsactualizado: 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 pororgName→ dentro de cada org, sub-agrupar porareaName - 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_groupsse elimina - Verificar qué componentes lo referencian
4. Ocultar Settings para roles cliente:
- El ícono ⚙ en LibraryPage se oculta para
client_editoryclient_viewer - Agregar guard en la ruta
/settings(o el path real):client_editoryclient_viewerredirigen a/library - Verificar si
/recursostambié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
/settingstiene 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
processStorey hacer persist en Supabase víaupdateEditable()
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_iden 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_xmlvíaupdateEditable()(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:
-- 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_xmlen los campos actualizables cuando el caller tiene permisos - Verificar que la política UPDATE existente ya permite que
client_editoractualicebpmn_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 aclient_editor client_editoraccede a/import, selecciona BPMN, el proceso se crea conorg_id = current_org()- El
owner_idse asigna comoauth.uid()(el client_editor que importó)
4. Restricciones que NO cambian:
client_editorno puede mover procesos entre orgs (no puede cambiarorg_ida otra org)client_editorno puede cambiarowner_idclient_editorno 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_editorpuede importar un BPMN nuevo → proceso creado conorg_idcorrecto (verificar en Supabase Studio)client_editorpuede abrir un proceso de su org y actualizar el BPMN (re-import)client_editorNO puede crear procesos conorg_idde otra org (test E2E adversarial)- Tests E2E: flujo completo import → workspace → update bpmn como client_editor
NO entra
- client_editor con permisos en
resourceso enadmin— 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.tsxWorkspacePage.tsxImportPage.tsx
Ejemplo de patrón correcto:
// 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 conauth.usersvia 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):
-
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
-
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)
-
Cómo exportar
- PDF: qué incluye cada documento (Actual, Automatizado, ROI)
- CSV: estructura del archivo exportado
- Cuándo usar PDF vs. CSV
-
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
/helpen 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 |