140 Commits

Author SHA1 Message Date
8b839baf77 refactor(router): guards AdminLayout + ImportPage → RequireRole wrapper (Etapa 6A)
Some checks are pending
/ Deploy to Cloudflare Pages (push) Waiting to run
- Crea RequireRole component (Approach B — router sin auth context)
- AdminLayout: elimina useEffect + spinner + null check, usa <RequireRole roles={['platform_admin']}>
- ImportPage: elimina useEffect guard + return null client_viewer, usa <RequireRole roles={['platform_admin','member','client_editor']}>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 13:18:14 -03:00
1032944f8b fix import para client_editor
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-09 12:10:36 -03:00
0257dd95ec Otro fix para el import con client_editor
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-08 11:54:18 -03:00
fdbdc42470 El bug está en toRow() en process-repo.ts, no en el ImportPage:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
typescript// toRow() en process-repo.ts — líneas 4-24
// NUNCA incluye org_id en el payload del upsert
const row = {
  id: process.id,
  name: process.name,
  // ... otros campos
  // ← org_id ausente completamente
}
La cadena completa del fallo:

ImportPage.tsx sí tiene orgId: user!.orgId ?? null → el Process object tiene el valor correcto
process-repo.ts llama toRow() para construir el payload de Supabase
toRow() nunca pone org_id en el payload → Supabase recibe org_id = null (default)
La RLS chequea org_id = current_org() → null ≠ '2a956076-...' → 403
El error propaga y sale como toast genérico de archivo dañado

Esto pasó desapercibido hasta ahora porque platform_admin tiene is_platform_admin() que bypasea la verificación de org_id. La inserción con org_id = null le funciona a admins, nunca a client_editor.
El fix es quirúrgico: agregar una sola línea en toRow():
typescriptorg_id: process.orgId ?? null,
Eso es todo. Una línea en process-repo.ts. La restricción "no tocar process-repo.ts" de Etapa 5 era por razón equivocada (asumíamos que el repo no necesitaba cambios) — este es un bug que impide que Etapa 5 funcione en absoluto.
2026-07-08 11:46:29 -03:00
3044342ac1 etapa 5
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 23:38:38 -03:00
85190aef0c docs(changelog): etapa 5 sprint 8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:18:41 -03:00
789e9a34ea feat(library): botón Importar BPMN visible para client_editor; empty state diferenciado
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:18:25 -03:00
bff71f0f29 feat(import): client_editor puede importar BPMN; orgId asignado automáticamente
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:18:16 -03:00
70d35dad8b feat(db): migración 018 — INSERT policy para client_editor en processes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:18:11 -03:00
54306be1c2 Etapa 4.5 entregada.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 22:02:19 -03:00
d776af5f24 docs(changelog): etapa 4.5 sprint 8
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:57:40 -03:00
cc5563974a fix(library): scroll a sección de org al volver del workspace con ?org param
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:57:29 -03:00
c6c88501d1 fix(workspace): handleBack pasa ?org=orgId para scroll contextual en biblioteca
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:57:25 -03:00
1813a9396a fix(admin): InQuality visible en OrganizationsPage con badge — quitar filtro is_provider
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:57:20 -03:00
b149e1cd0c fix(workspace): sentinel '__none__' en selector área — Radix UI prohíbe value='' en SelectItem
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:18:58 -03:00
53f8a8d8cd etapa 4 terminada
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 21:12:00 -03:00
6e60bf4def docs(changelog): etapa 4 sprint 8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:06:59 -03:00
fe33c910ac feat(admin): CRUD de áreas en OrganizationDetailPage — tab Áreas con crear/renombrar/eliminar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:06:46 -03:00
b634d061b7 feat(workspace): topbar muestra areaName en lugar de clientName inline-editable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:06:42 -03:00
e0b4607603 feat(workspace): selector de área en GlobalSettingsPanel reemplaza campo libre "Cliente"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:06:39 -03:00
de00e3a902 etapa 3 terminada
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 20:19:45 -03:00
aa0ab72f17 docs(changelog): etapa 3 sprint 8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 20:17:19 -03:00
cbf3a88e78 feat(library): agrupación org → área reemplaza groupId en HomeView
- Admin ve procesos agrupados por org (colapsable) → sub-secciones por área
- Búsqueda muestra chip con areaName ?? orgName en lugar de nombre de grupo
- Header muestra conteo de clientes (orgs) en vez de groups
- Botón Settings ⚙ oculto para client_editor y client_viewer
- Efecto orgParam/groupId scroll obsoleto eliminado
- handleImport simplificado (sin groupId)
- data-testid="groups-header" preservado en el nuevo contenedor admin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 20:17:08 -03:00
b99cc75f80 feat(router): guard beforeLoad en /settings para client_editor y client_viewer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 20:16:56 -03:00
27336fe0c7 fix(process-repo): getAll() incluye JOIN con org y area para populear names
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 20:16:55 -03:00
e8490a5863 docs(changelog): etapa 2 sprint 8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 18:18:23 -03:00
d6e7226842 feat(process-repo): extender fromRow/toRow/getById con areaId y areaName
- fromRow: extrae areaId y areaName (vía JOIN en getById)
- toRow y updateEditable: incluyen area_id en escrituras a Supabase
- getById: JOIN con areas!area_id(name) para obtener nombre en una query
- Fixtures de tests actualizados con areaId: null (campo requerido)
- 4 tests nuevos en tests/persistence/area-repo.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 18:18:14 -03:00
f9e886e12d feat(area-repo): repositorio CRUD de áreas por organización
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 18:18:07 -03:00
a3ff6ee545 feat(domain): interface Area + campos areaId/areaName en Process
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 18:18:04 -03:00
d64078d96a Etapa 0 aprobada condicionalmente. Queda pendiente tu validación visual: abrí /library en https://inq-roi.inqualityhq.com y confirmá que el footer muestra InQ ROI · Powered by InQuality · v0.8.0 · Hecho desde Paraguay 🇵🇾.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 17:55:19 -03:00
10e477cc45 docs(sprint-8): ETAPA_0_PROMPT — versionado, CHANGELOG, AppFooter
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 17:47:43 -03:00
ec77a7fffa docs(sprint-8): ETAPA_0_PROMPT — versionado, CHANGELOG, AppFooter 2026-07-07 17:47:31 -03:00
0426f67940 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
2026-07-07 17:39:36 -03:00
9277143822 docs(sprint-7): cierre formal — TECH_DEBT guards+auth, OBSERVATIONS reset Sprint 8, decisión org→areas
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 17:29:48 -03:00
ee7cdf5981 chore(workspace): eliminar console.log de diagnóstico de orgId 2026-07-07 17:27:23 -03:00
7102d6ade6 fix(workspace): back button con orgName via JOIN + navegación simplificada a /library
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 17:21:37 -03:00
c92f4f087a fix(workspace): handleBack navega a /library sin ?org= hasta que exista vista filtrada
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 17:13:07 -03:00
df5fb55696 fix(workspace): obtener orgName via JOIN en getById, eliminar fetch separado con race condition
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 15:26:52 -03:00
6fee592e40 Resumen del fix — Prioridad 2:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Cambios en WorkspacePage.tsx:

orgName ahora es string | null | undefined. undefined = query no completó aún, null = proceso sin org (o query sin resultado), string = nombre real.
Reemplazó .single() por .maybeSingle() — .single() lanzaba PGRST116 cuando el org no existía, data quedaba en null y setOrgName nunca se llamaba, dejando el estado indefinido.
setOrgName(data?.name ?? null) se llama siempre, sin condición if (data).
backLabel pasa de orgId ? (orgName ?? 'Volver') : 'Biblioteca' a simplemente orgName ?? 'Biblioteca' — el nombre real si cargó, o el fallback genérico en cualquier otro caso. Ya no depende de orgId para decidir el label.
handleBack() no cambió — sigue usando currentProcess?.orgId para construir el URL correcto a /library?org=xxx.
Listo para validación visual en workspace con un proceso de cliente que tenga org_id en la DB.
2026-07-07 14:53:39 -03:00
f3f55a6857 Resumen de los cambios del fix de navegación:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
LibraryPage.tsx:778 — reemplazó useSearch({ from: '/' }) por useRouterState + URLSearchParams. Ahora funciona tanto desde / como desde /library.
WorkspacePage.tsx:206-209 — handleBack() navega a /library (con o sin ?org=uuid) en lugar de /.
workspace-back-button.test.ts — backTo actualizado a /library y /library?org=... en los 6 tests.
library-ui.test.tsx — mock de useSearch → useRouterState (devuelve { location: { search: '' } }).
2026-07-07 14:24:26 -03:00
ee356e5de6 Etapa 7 ya estaba completamente ejecutada en esta sesión. Todo está en su lugar:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Fix A1 — isProfileLoaded en AuthContext 

useState(false) inicial
setIsProfileLoaded(true) si onAuthStateChange devuelve null (no hay usuario que sincronizar)
setIsProfileLoaded(false) en SIGNED_IN (resetea para el nuevo sync)
setIsProfileLoaded(true) en el finally del sync effect (después de syncProfileFromDB, éxito o error)
Fix A2 — Guard en LibraryPage 

if (!user || !isProfileLoaded) devuelve skeleton animado antes de cualquier contenido rol-dependiente
Fix A3 — Campo "cliente" read-only para client_editor 

GlobalSettingsPanel.tsx: <p> estático en lugar de <Input> cuando isClientRole
WorkspacePage.tsx: topbar muestra el valor sin input ni lápiz para client_editor
Fix B — Migración 016 + aplicada a DB 

Archivo supabase/migrations/016_add_simulations_update_policy.sql creado
Policy simulations_update confirmada activa en DB (pg_policies devuelve las 4 policies de simulations)
Fix adicional — 403 en POST /processes  (ejecutado en el turno inmediato anterior)

updateEditable() en process-repo.ts usa .update() en lugar de .upsert() para todos los paths de edición
Tests: 586 verdes, build limpio.

Lo que falta según el checklist del prompt es la validación visual del director (msebem@gmail.com en la app real). Eso requiere tu OK después de verificar en el navegador.

domain/types.ts — orgId: string | null agregado a Process (campo de DB ya existente, ahora mapeado en el dominio).

process-repo.ts — fromRow incluye orgId: (row.org_id as string | null) ?? null. Ambas queries (getById y getAll) usan select('*'), por lo que el campo viene gratis.

WorkspacePage.tsx:

Import supabase para el fetch de org name
Estado orgName + useEffect que hace SELECT name FROM organizations WHERE id = orgId cuando cambia currentProcess?.orgId
Variable isClientRole (solo client_editor — client_viewer ya fue redirigido)
backLabel: "Procesos" para client_editor, nombre de org (o "Volver" mientras carga) si hay orgId, "Biblioteca" si no hay
handleBack(): navega a /?org=<orgId> para admin con proceso de cliente, a / para el resto
El Home button + Tooltip fueron reemplazados por <Button variant="ghost" size="sm"> con ArrowLeft + backLabel
LibraryPage.tsx:

Import useSearch
Lee orgParam = useSearch({ from: '/' }) as { org?: string }
useEffect que, cuando orgParam está presente y la carga terminó, busca el proceso con p.orgId === orgParam, obtiene su groupId, y hace scrollIntoView al elemento group-{groupId}
Los GroupCards en el grid están ahora envueltos en <div id="group-{group.id}"> para ser alcanzables por el scroll
Tests — workspace-back-button.test.ts con 6 casos: admin+orgName, admin+orgName loading, admin sin org, member sin org, client_editor, client_viewer. Fixtures actualizados en 13 archivos con orgId: null. Mock de useSearch agregado en library-ui.test.tsx.
2026-07-07 14:12:10 -03:00
cad8fbca4f Causa del 403: Supabase evalúa siempre la política INSERT cuando se usa .upsert(), incluso si la fila ya existe. client_editor no tiene INSERT en processes, entonces cualquier .upsert() sobre procesos existentes fallaba con 403.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Fix: nuevo método updateEditable() en process-repo.ts que usa .update().eq('id', process.id) con solo los campos editables (sin bpmn_xml, owner_id, created_by, id). Todos los paths de edición en process-store.ts y library-store.ts ahora usan updateEditable(). El .upsert() original queda únicamente para la creación inicial desde ImportPage y setCurrentProcess — rutas que client_editor nunca puede alcanzar (el import está bloqueado por el guard de rol).
2026-07-07 13:25:43 -03:00
9b89cb8669 La política simulations_update está activa en la DB de desarrollo. El fix B está completo.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Resumen de todo lo entregado en Etapa 7:

Fix A1 (src/auth/AuthContext.tsx): isProfileLoaded: boolean agregado como estado. Arranca en false, se pone true en el finally del sync effect (después de syncProfileFromDB), y cuando onAuthStateChange devuelve null (usuario no autenticado). Se resetea a false en cada SIGN_IN para gatear el re-sync.

Fix A2 (src/features/library/LibraryPage.tsx): Guard temprano después de todos los hooks — si !user || !isProfileLoaded devuelve un skeleton de tres columnas animado, idéntico al que usa HomeView. El contenido rol-dependiente solo se renderiza cuando el perfil está sincronizado.

Fix A3 (GlobalSettingsPanel.tsx + WorkspacePage.tsx): El campo "Cliente" es texto estático (<p>) para client_editor y client_viewer. En el topbar de WorkspacePage se oculta el input inline y el ícono de lápiz; si hay un clientName se muestra como texto inerte.

Fix B (supabase/migrations/016_add_simulations_update_policy.sql): Migración creada + aplicada vía Management API a inq-roi-desa. pg_policies confirma las 4 políticas: simulations_select, simulations_insert, simulations_update, simulations_delete.

Tests: tests/features/library/library-ui.test.tsx actualizado — mock incluye isProfileLoaded, 2 tests nuevos para isProfileLoaded: false. Total: 586 tests, todos verdes. Build limpio.
2026-07-07 12:27:36 -03:00
1f84997794 Qué se implementó:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
src/features/auth/ConfirmInvitePage.tsx — nuevo componente con tres estados:

idle: pantalla de bienvenida + botón "Confirmar acceso" (estado inicial con params válidos)
loading: botón con spinner mientras se llama a verifyOtp
error: mensaje descriptivo + link a /login (token vacío/incorrecto en params, o fallo en verifyOtp)
El verifyOtp solo se llama al click del usuario — nunca al montar, por eso el pre-scan del email no consume el token.

src/router.tsx — ruta /auth/confirm agregada como pública (PUBLIC_PATHS) + confirmInviteRoute en el árbol de rutas.

tests/features/auth/confirm-invite.test.tsx — 6 tests cubriendo: no-call on mount, error con params vacíos, error con type distinto, llamada correcta a verifyOtp, navegación en éxito, y error con mensaje de detalle.

ResetPasswordPage no requiere cambios — updateUser({ password }) solo necesita sesión activa, que verifyOtp crea en el paso previo.
2026-07-07 11:30:21 -03:00
074f726d62 Ambas funciones deployadas al proyecto inq-roi-desa (xgqbkxcliyjzisoccsur).
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 11:02:27 -03:00
6be9f6267f Pre-Cierre sprint 7.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 10:44:23 -03:00
ef9ec472d5 Etapa 5 completa.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 10:32:02 -03:00
bc9f70419c Resumen de lo entregado
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-07 09:47:31 -03:00
2922d4c2f3 docs(sprint-6): cierre formal — observaciones Etapas 1-3 + reflexiones estratégicas
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-06 18:52:24 -03:00
b117ad258d fix(sprint-6/etapa-3): localStorage key en ProfileSheet + eliminar setTimeout(350) + specs E2E de auth
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-07-06 18:51:52 -03:00