Commit Graph

104 Commits

Author SHA1 Message Date
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
1aa1486d88 fix(sprint-5/etapa-4h): safety net en Sheet — body queda con pointer-events:none stuck tras cerrar
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Bug de Radix/react-remove-scroll: el cleanup que restaura pointer-events del body
al cerrar un Dialog/Sheet puede no ejecutarse (race con re-renders externos durante
la animación de salida). Resultado: toda la página queda no interactiva hasta
refrescar manualmente — confirmado en producción tras guardar perfil pese al
defer de 350ms ya aplicado en ProfileSheet (mitigaba una causa, no era estructural).

Fix a nivel del primitivo Sheet (cubre todos los usos, no solo ProfileSheet):
al cerrar, espera 400ms (> 300ms de la animación) y si no queda ningún
[role="dialog"][data-state="open"] en el DOM pero el body sigue con
pointer-events:none, lo limpia explícitamente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 09:53:14 -03:00
aaf1f79519 fix(sprint-5/etapa-4g): defer updateUser 350ms — espera fin de animacion Radix antes de re-render AuthContext 2026-06-23 10:17:47 +00:00
985c655f6a fix(sprint-5/etapa-4g): defer updateUser — evita overlay Radix stuck en DOM
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 20:26:47 -03:00
a1a8b1c651 fix(sprint-5/etapa-4g): corregir clave localStorage en getAccessToken — supabase.auth.token
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 23:15:29 +00:00
7da39c7f1e prompt
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 20:08:23 -03:00
7f1f83382d fix(sprint-5/etapa-4g): revert ProfileSheet a raw fetch — evita processLock contention
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
supabase.from().update() adquiere processLock internamente via getSession(), bloqueando
otras queries Supabase mientras espera. Raw fetch con token de localStorage evita el
lock completamente. Mantiene el patrón optimista: updateUser + onOpenChange(false)
antes del fetch, que corre en background fire-and-forget con AbortSignal.timeout(10s).

getAccessToken() ahora deriva el projectRef desde el hostname de SUPABASE_URL en lugar
de buscar con startsWith/endsWith — más robusto y sin iteración de todas las keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 20:06:51 -03:00
1a4650d186 a ver si ahora sí
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 19:56:02 -03:00
69f744ae3f fix(sprint-5): optimistic update en ProfileSheet — cierra Sheet antes del fetch, UI nunca bloqueada
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 19:35:04 -03:00
27ce96ca1e fix(sprint-5): AbortSignal.timeout(8s) en fetch de ProfileSheet — evita freeze por modal bloqueada
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 22:19:20 +00:00
882edd230d a ver si ahora sí guarda el perfil
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 19:06:58 -03:00
1c5329915a fix(sprint-5/etapa-4f): || fallback en name vacío — botón Guardar habilitado cuando Google devuelve full_name: ""
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
El operador ?? solo hace fallback con null/undefined, no con cadena vacía.
Si Google devuelve user_metadata.full_name: "", la cadena de propagación es:
  buildAuthUser → name=""  →  upsertUserOnLogin inserta ""  →  syncProfileFromDB
  devuelve ""  →  AuthContext setUser → "" ?? prev.name = ""  →  ProfileSheet
  setName("") → name.trim() === '' → botón disabled → click no hace nada.

Fix: cambiar ?? por || en todos los puntos donde full_name o name de DB
se propagan al estado: buildAuthUser, syncProfileFromDB, getUserFromStorage,
useEffect IIFE en AuthContext. Agregar fallback a email en ProfileSheet
setName() para usuarios cuya fila en DB ya tiene name="" de sesiones anteriores.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 19:06:30 -03:00
3ebfd65a90 fix(sprint-5/etapa-4e): lockAcquireTimeout 30s + eliminar getSessionSafe — corta freeze en guardar perfil
getSessionSafe() tenía un problema de diseño: Promise.race([getSession(), timeout])
abandona la Promise de getSession() cuando gana el timeout. La Promise abandonada
deja una operación en PROCESS_LOCKS[name] que bloquea el chain. Con lockAcquireTimeout=5s
(default), un token refresh lento dispara ProcessLockAcquireTimeoutError cuyo handler
hace await previousOperation (colgada) → freeze permanente.

Fix: lockAcquireTimeout: 30000 (margen para redes lentas) + eliminar getSessionSafe()
de supabase.ts, library-store, process-store y useReportData. Las queries Supabase
llaman getSession() internamente vía _getAccessToken() que espera initializePromise
automáticamente — la barrera era redundante.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 19:05:23 -03:00
8142b07525 a ver si ahora sí
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 02:26:33 -03:00
602957b6c9 fix(sprint-5/etapa-4b): timeout AbortController en sync de perfil al login
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
upsertUserOnLogin y syncProfileFromDB adquieren el processLock de Supabase para
leer/escribir la sesión. Si el servidor GoTrue está lento (token refresh), ese
lock puede quedar ocupado varios segundos bloqueando loads de biblioteca/workspace.

Agrega AbortController con timeout de 6s a las dos queries de auth sync en
AuthContext. Si el servidor no responde a tiempo, la señal de abort cancela
el fetch, postgrest-js lanza AbortError, el lock se libera, y el IIFE termina
silenciosamente. El profile save ya usa fetch nativo (no le afecta el lock).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 02:13:53 -03:00
3d3ae99125 fix(sprint-5/etapa-4b): save perfil via fetch nativo — bypasea processLock compartido
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
upsertUserOnLogin + syncProfileFromDB (AuthContext useEffect) adquieren el
processLock de supabase-js al cargar la página. Si el servidor tarda, ese lock
queda ocupado y supabase.from('users').update() en handleSave espera para siempre
en la misma cola — congelando la UI indefinidamente.

Fix: reemplazar el cliente Supabase por fetch nativo en ProfileSheet. El token
se lee directamente de localStorage (donde GoTrueClient lo escribe tras cada
login/refresh), eliminando toda dependencia del processLock. Si el token no
está disponible o el servidor responde con error, se muestra el mensaje inline
en lugar de colgar el botón.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 02:08:25 -03:00
a626ff040c fix de guardar perfil - ahora si
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 01:31:02 -03:00
496c227c88 fix(sprint-5/etapa-4d): mover queries DB fuera de onAuthStateChange — corta deadlock lockAcquired permanente
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
El callback de onAuthStateChange en GoTrueClient adquiere un lock interno (lockAcquired=true).
Hacer await supabase.from('users').select() dentro de ese callback genera un deadlock circular:
el select necesita getSession() → _acquireLock() → lockAcquired=true → espera al callback →
callback espera al select → deadlock permanente. Toda query posterior a Supabase cuelga.

Fix: el callback de onAuthStateChange pasa a ser síncrono y solo llama buildAuthUser() (sin
queries). El upsert y la lectura de company/name se mueven a un useEffect([user?.id]) en
AuthContext, que corre FUERA del lock de auth y solo se dispara al cambiar de usuario real
(distinto id), no en cada updateUser() local.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 01:28:26 -03:00
dd361b1b12 fix(sprint-5/etapa-4b): library useEffect usa user.id no user ref — corta contención processLock en guardar perfil
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 00:57:16 -03:00
224b3bc5e7 debug(sprint-5/etapa-4b): logs temporales en ProfileSheet para diagnosticar loop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 00:52:21 -03:00
4a19eda1b2 fix guardar perfil
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 00:39:39 -03:00
735294db06 fix(sprint-5/etapa-4b): prevenir loop en onAuthStateChange al guardar perfil
- SupabaseAuthService: solo leer company/name de DB en INITIAL_SESSION y SIGNED_IN;
  ignorar TOKEN_REFRESHED y USER_UPDATED para no sobreescribir ediciones locales
- ProfileSheet: useEffect con solo [open] como dep — inicializa el form al abrir
  el sheet, sin resetearlo en cada cambio del objeto user durante la edición

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 00:35:01 -03:00
7ffab83caf Feature A — Procesos por recurso (/recursos)
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
2026-06-20 00:07:46 -03:00
6d14e64f33 feat(sprint-5/etapa-4): procesos por recurso + campo empresa en perfil
Feature A — Ver procesos asociados a un recurso:
- resource-repo.ts: getProcessCountsPerResource() (una query, N+1 evitado)
- resource-repo.ts: getProcessesForResource() (on-demand al expandir)
- ResourceCatalogPage: carga conteos en paralelo, columna "Procesos" con
  badge naranja clicable, fila expandida con links a /workspace/:id

Feature B — Campo empresa en perfil:
- 013_add_company_to_users.sql: ALTER TABLE users ADD COLUMN IF NOT EXISTS company text
- AuthUser: agrega company? opcional
- SupabaseAuthService: lee company y name de tabla users en onAuthStateChange
- AuthContext: agrega updateUser() para reflejar cambios sin recargar
- ProfileSheet.tsx: Sheet con nombre/empresa editables, email read-only
- AppHeader: muestra empresa en dropdown + item "Editar perfil"

528 tests Vitest verdes. Build limpio.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:56:13 -03:00