Commit Graph

127 Commits

Author SHA1 Message Date
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
b42cdc7e80 fix(sprint-6/etapa-2): mover gráficos antes de ComparisonTable en tab ROI
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
SensitivityPanel y CumulativeSavingsChart deben ser adyacentes: el slider afecta
el gráfico (doble línea) y el usuario no debe scrollear a través de la tabla de
comparación para ver el efecto. Reordenamiento de JSX puro, sin cambios de lógica.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 20:45:24 -03:00
bae8dc11c7
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-24 20:06:23 -03:00
1e4dbc0bed feat(sprint-6/etapa-2): panel de análisis de sensibilidad + doble línea en CumulativeSavingsChart
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-24 16:42:32 -03:00
b9cd57d148 feat(sprint-6/etapa-1): logo InQuality en header PDFs (págs 2+) + whitespace-nowrap eficiencia
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-24 16:22:37 -03:00
dc610f8a1e Resumen del fix:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
loadImageBase64 ahora retorna { base64, pixelWidth, pixelHeight } (320×210px tras el resize) en vez de solo el string.
PdfHeaderOptions agrega logoPixelWidth/logoPixelHeight.
drawSharedHeader: logo a 15mm de ancho con alto calculado proporcionalmente desde las dimensiones reales (≈9.8mm, ratio real ~1.53:1, no el 3.56:1 hardcodeado anterior). El logo se posiciona arriba (MARGIN - 1), y processName + fecha bajan a la línea siguiente sin superponerse. clientName baja una línea adicional cuando hay logo (para no chocar con la fecha), ajustando y antes del border-bottom.
Fallback a texto "InQ ROI" intacto cuando no hay logo.
2026-06-24 12:16:25 -03:00
f47cf82abf chore(sprint-6): abrir sprint — BRIEF + OBSERVATIONS reset
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 22:54:48 -03:00
5ca5d7ea31 chore(sprint-5/etapa-6): limpieza técnica — E2E Supabase, fix flakiness guardar, docs cierre sprint
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 22:33:01 -03:00
485cc91d73 feat(sprint-5/etapa-5): columnas Ahorro esperado + Eficiencia en ComparisonTable — A/B para focus group
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 21:58:40 -03:00
e9073ae732 Único cambio: la celda de "Eficiencia" ahora muestra ${value.toFixed(2)} ${currency}/min.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 17:20:30 -03:00
bf200b72f9 Los cambios en reporte de eficiencia
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 17:09:54 -03:00
ecfb72c3f1 docs(sprint-5): actualizar OBSERVATIONS/TECH_DEBT/BACKLOG — Etapa 4 documentada, items resueltos marcados, logo PDF corregido
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 16:02:47 -03:00
db82be4e8c fix(lint): ReportPage.tsx — useMemo dep simulation en lugar de simulation?.result + eslint-disable intencional en useEffect
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-23 18:47:35 +00:00
c93d8e824f fix(sprint-5/etapa-4h-2): observar prop open en vez de onOpenChange — fix anterior no se disparaba
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
El fix anterior (1aa1486) interceptaba onOpenChange de SheetPrimitive.Root, pero ese
callback solo se dispara cuando RADIX decide cerrar internamente (Escape, overlay,
botón X). Cuando el padre cierra el Sheet cambiando el prop `open` directamente —que
es lo que hace ProfileSheet.handleSave(): llama onOpenChange(false), que es
setProfileOpen(false) del padre AppHeader— Radix nunca invoca su propio onOpenChange.
El wrapper nunca se ejecutaba para este caso, exactamente el que reportó el usuario
en producción (body queda con pointer-events:none stuck tras guardar perfil).

Fix: useEffect que observa el prop `open` en sí, cubre ambos canales de cierre
(interno de Radix y externo del padre) sin depender de qué disparó el cambio.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:01:57 -03:00