12 KiB
Sprint 8 — Etapa 5: client_editor puede importar BPMN
Tipo: DB + UI — habilitar flujo de importación para rol cliente editor Rama: main Estimación: 1.5 horas Dependencias: Etapas 1–4 completadas ✅ (tabla
areasy columnaorg_iden processes ya existen,isClientRoleya implementado en workspace)
Objetivo
Habilitar que un usuario con rol client_editor pueda importar archivos BPMN propios. Actualmente la ImportPage redirige a todos los roles cliente a /. Con esta etapa, client_editor pasa a ser un ciudadano de primera clase del flujo de importación: puede subir un BPMN, el proceso se asigna automáticamente a su org, y puede trabajar desde el workspace como lo hace InQuality.
client_viewer no obtiene acceso de importación — eso no cambia.
Contexto del código actual
Leer estos archivos antes de modificar:
src/features/import/ImportPage.tsx— tiene dos guards que bloquean a client_editor:useEffectconallowed = ['platform_admin', 'member']→ redirige a/if (isClientRole) return nullantes del return final → nunca llega al JSX Además, el proceso se crea conorgId: null, lo que rompe las RLS.
src/features/library/LibraryPage.tsx— el botón "Importar BPMN" del header está envuelto en{!isClientRole && ...}(líneas ~841-860). El empty state de la vista cliente muestra "Contactá a tu administrador" sin botón de importar para nadie.supabase/migrations/015_client_roles_and_rls.sql— la políticainsert_processespermite soloplatform_adminymember. La línea de comentario lo dice explícitamente:-- client_editor y client_viewer NO pueden crear procesos nuevos.
Parte 1: Migration 018
Crear supabase/migrations/018_client_editor_insert.sql.
El único cambio de esta migración: extender la política insert_processes para incluir a client_editor cuando inserta en su propia org.
-- Permite que client_editor inserte procesos en su propia org
-- Los demás roles mantienen sus permisos actuales
DROP POLICY IF EXISTS "insert_processes" ON public.processes;
CREATE POLICY "insert_processes" ON public.processes
FOR INSERT WITH CHECK (
public.is_platform_admin()
OR public.current_platform_role() = 'member'
OR (
public.current_platform_role() = 'client_editor'
AND org_id = public.current_org()
)
-- client_viewer y guest siguen sin poder crear procesos
);
Verificar que las políticas de actividades y gateways ya permiten a client_editor:
Buscar en 015_client_roles_and_rls.sql:
activities_insert: usaNOT IN ('client_viewer', 'guest')→ client_editor puede ✅gateways_write: usaNOT IN ('client_viewer', 'guest')→ client_editor puede ✅
Si alguna de esas dos NO incluye a client_editor, corregir en esta misma migración. Verificar antes de cerrar el archivo.
Aplicar la migración
Aplicar en Supabase y verificar:
-- Verificar que la nueva política existe
SELECT policyname, cmd, qual
FROM pg_policies
WHERE schemaname = 'public' AND tablename = 'processes';
-- Esperado: ver "insert_processes" con la nueva definición que incluye client_editor
Parte 2: ImportPage.tsx
2A — Eliminar los dos guards que bloquean a client_editor
Guard 1 — cambiar la lista de roles permitidos en el useEffect:
// Antes:
const allowed = ['platform_admin', 'member']
if (!allowed.includes(user.platformRole)) {
void navigate({ to: '/' })
}
// Después:
const allowed = ['platform_admin', 'member', 'client_editor']
if (!allowed.includes(user.platformRole)) {
void navigate({ to: '/' })
}
Guard 2 — reemplazar el bloqueo total por uno solo para client_viewer:
// Antes (bloquea a todos los client roles):
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
if (isClientRole) return null
// Después (solo bloquea client_viewer):
if (user?.platformRole === 'client_viewer') return null
2B — Asignar orgId del usuario en el proceso creado
Al construir el objeto process dentro de processFile, el campo orgId actualmente es null. Para que la RLS de insert_processes valide org_id = current_org(), el proceso insertado debe tener el org_id del usuario.
// Antes:
const process: Process = {
// ...
orgId: null,
// ...
}
// Después:
const process: Process = {
// ...
orgId: user!.orgId, // ← org_id del usuario autenticado; null solo si no tiene org (no debería pasar para client_editor)
// ...
}
Verificar que user.orgId está disponible en el tipo User del AuthContext (debería estar — se pobló en Sprint 7).
2C — Ocultar los procesos de ejemplo para client_editor
Los "procesos de ejemplo" (SAMPLE_PROCESSES) son demostraciones internas de InQuality. Un client_editor no debería importar datos de prueba de InQuality en su propio espacio.
En el JSX, la sección de ejemplos está bajo el drop zone. Envolverla en una condición:
// Antes: siempre visible
<div className="mt-8 w-full max-w-lg">
<p ...>O cargá un proceso de ejemplo con un click</p>
<div className="grid grid-cols-3 gap-3">
{SAMPLE_PROCESSES.map(...)}
</div>
</div>
// Después: solo visible para InQuality (platform_admin y member)
{user?.platformRole !== 'client_editor' && (
<div className="mt-8 w-full max-w-lg">
<p ...>O cargá un proceso de ejemplo con un click</p>
<div className="grid grid-cols-3 gap-3">
{SAMPLE_PROCESSES.map(...)}
</div>
</div>
)}
2D — Corregir texto de marca en ImportPage
El header tiene texto desactualizado que viola las reglas de marca del producto:
// Antes (incorrecto — nombre viejo del producto):
<div className="inline-flex items-center gap-2 ...">
<FlaskConical className="h-3.5 w-3.5" />
Process Cost Platform · MVP Fase 0
</div>
// Después:
<div className="inline-flex items-center gap-2 ...">
<FlaskConical className="h-3.5 w-3.5" />
InQ ROI
</div>
El tagline del final de la página también está desactualizado:
// Antes:
<p className="mt-8 text-xs text-slate-300">
100% en tu navegador · Sin backend · Sin registro · Datos guardados localmente
</p>
// Después: eliminar este párrafo (el producto ya tiene backend y registro)
Parte 3: LibraryPage.tsx
3A — Botón "Importar BPMN" en el header visible para client_editor
El botón en el header de LibraryPage (líneas ~841-860) está condicionado con !isClientRole, lo que lo oculta para todos los roles cliente. Cambiar para mostrarlo a client_editor:
// Antes:
{!isClientRole && (
<button
onClick={handleImport}
className="..."
style={{ background: '#F59845' }}
>
<Upload className="h-4 w-4" />
Importar BPMN
</button>
)}
// Después:
{user?.platformRole !== 'client_viewer' && (
<button
onClick={handleImport}
className="..."
style={{ background: '#F59845' }}
>
<Upload className="h-4 w-4" />
Importar BPMN
</button>
)}
3B — Empty state diferenciado para client_editor vs client_viewer
En HomeView, el empty state para roles cliente (cuando groups.length === 0 && processes.length === 0) actualmente muestra "Contactá a tu administrador" para todos. Diferenciarlo:
// Antes (igual para todos los client roles):
if (isClientRole) {
return (
<div ...>
<Building2 ... />
<p>No tenés procesos asignados aún</p>
<p>Contactá a tu administrador para que te asigne acceso</p>
</div>
)
}
// Después (diferenciado):
if (isClientRole) {
if (user?.platformRole === 'client_editor') {
return (
<div className="flex flex-col items-center justify-center py-24 gap-4">
<Building2 className="h-16 w-16 text-muted-foreground/30" />
<p className="text-base font-medium">Tu biblioteca está vacía</p>
<p className="text-[13px] text-muted-foreground">
Importá tu primer BPMN para empezar a cuantificar costos
</p>
<button
onClick={() => onImport()}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-white transition-opacity hover:opacity-90"
style={{ background: '#F59845' }}
>
<Upload className="h-4 w-4" />
Importar BPMN
</button>
</div>
)
}
// client_viewer: mantener mensaje existente
return (
<div className="flex flex-col items-center justify-center py-24 gap-4">
<Building2 className="h-16 w-16 text-muted-foreground/30" />
<p className="text-base font-medium">No tenés procesos asignados aún</p>
<p className="text-[13px] text-muted-foreground">Contactá a tu administrador para que te asigne acceso</p>
</div>
)
}
user está disponible en el scope de HomeView como prop (o via useAuth() — verificar cuál aplica).
Lo que NO cambia en esta etapa
process-repo.ts— no tocar.updateEditable()no necesitabpmn_xmlpara este flujo.process-store.ts— no tocar.WorkspacePage.tsx— sin cambios. client_editor ya tiene acceso al workspace desde Sprint 7 (la RLS deupdate_processesya lo cubre).GlobalSettingsPanel.tsx— sin cambios. El selector de área ya funciona para client_editor desde Etapa 4.- Rol
client_viewer— sigue sin acceso a importación en todo momento.
Criterios de validación
Técnicos
- Migración 018 creada y aplicada en Supabase
insert_processesen la DB incluye la cláusula de client_editor (verificar conpg_policies)npm run buildsin errores TypeScriptnpm run testsin regresiones (596 tests)
Funcionales (validación visual mandatoria del director)
- Loguear como
client_editor→/library→ botón "Importar BPMN" visible en el header - Hacer click en "Importar BPMN" → lleva a
/import(no redirige a/) /importmuestra el drop zone; la sección de "procesos de ejemplo" NO aparece- Subir un archivo BPMN real → proceso creado → workspace abre correctamente
- En Supabase: verificar que el proceso creado tiene
org_id= org del client_editor (no null) - Loguear como
client_viewer→/library→ botón "Importar BPMN" NO visible - Si client_viewer navega manualmente a
/import→ redirige a/ - La biblioteca vacía de client_editor muestra botón de importar; la de client_viewer muestra "Contactá a tu administrador"
Restricciones explícitas
- NO tocar políticas RLS de tablas distintas a
processes(activities y gateways ya permiten client_editor) - NO crear procesos de muestra en la DB para client_editor — el flujo es importar BPMNs reales
- NO modificar el workspace ni el reporte — client_editor ya puede usarlos
- NO agregar tests en esta etapa
Archivos autorizados a modificar/crear
supabase/migrations/
└── 018_client_editor_insert.sql ← crear y aplicar
src/features/
├── import/
│ └── ImportPage.tsx ← guards + orgId + ejemplos + texto marca
└── library/
└── LibraryPage.tsx ← botón header + empty state diferenciado
Commits esperados
feat(db): migración 018 — INSERT policy para client_editor en processes
feat(import): client_editor puede importar BPMN; orgId asignado automáticamente
feat(library): botón Importar BPMN visible para client_editor; empty state diferenciado
fix(import): corregir texto de marca — "Process Cost Platform" → "InQ ROI"
Reporte esperado
- Contenido exacto de
018_client_editor_insert.sql - Resultado de la query de verificación en
pg_policiesparainsert_processes - Lista de cambios en
ImportPage.tsx(resumen compacto) - Lista de cambios en
LibraryPage.tsx(resumen compacto) npm run buildynpm run test(con conteo)- Solicitar validación visual del director — describir el estado de la UI para client_editor antes del OK formal