Files
inq-roi-simulador-web/sprints/sprint-8/ETAPA_5_PROMPT.md
Marcos Benítez 3044342ac1
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
etapa 5
2026-07-07 23:38:38 -03:00

12 KiB
Raw Blame History

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 14 completadas (tabla areas y columna org_id en processes ya existen, isClientRole ya 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:
    1. useEffect con allowed = ['platform_admin', 'member'] → redirige a /
    2. if (isClientRole) return null antes del return final → nunca llega al JSX Además, el proceso se crea con orgId: 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ítica insert_processes permite solo platform_admin y member. 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: usa NOT IN ('client_viewer', 'guest') → client_editor puede
  • gateways_write: usa NOT 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 necesita bpmn_xml para este flujo.
  • process-store.ts — no tocar.
  • WorkspacePage.tsx — sin cambios. client_editor ya tiene acceso al workspace desde Sprint 7 (la RLS de update_processes ya 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_processes en la DB incluye la cláusula de client_editor (verificar con pg_policies)
  • npm run build sin errores TypeScript
  • npm run test sin 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 /)
  • /import muestra 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

  1. Contenido exacto de 018_client_editor_insert.sql
  2. Resultado de la query de verificación en pg_policies para insert_processes
  3. Lista de cambios en ImportPage.tsx (resumen compacto)
  4. Lista de cambios en LibraryPage.tsx (resumen compacto)
  5. npm run build y npm run test (con conteo)
  6. Solicitar validación visual del director — describir el estado de la UI para client_editor antes del OK formal