Files
inq-roi-simulador-web/OBSERVATIONS.md
Marcos Benítez 074f726d62
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Ambas funciones deployadas al proyecto inq-roi-desa (xgqbkxcliyjzisoccsur).
2026-07-07 11:02:27 -03:00

7.5 KiB

OBSERVATIONS — Sprint 7

Hallazgos descubiertos durante el sprint actual. Este archivo se vacía al cerrar el sprint: los hallazgos se promocionan a TECH_DEBT.md o BACKLOG.md, o quedan resueltos.

Sprint 6 cerrado el 2026-06-24. Observaciones archivadas — ítems migrados a BACKLOG.md y TECH_DEBT.md.


Convención de etiquetas

  • [CURRENT-SPRINT] — Se resuelve en este sprint
  • [DEFERRED-TECH-DEBT] — Se va a TECH_DEBT.md al cerrar el sprint
  • [DEFERRED-BACKLOG] — Se va a BACKLOG.md al cerrar el sprint
  • [RESOLVED] — Resuelto durante el sprint, archivar al cierre

Hallazgos activos

(vacío)


Hallazgos resueltos durante el sprint

[RESOLVED] Etapa 1 — Migraciones DB + SECURITY DEFINER helpers

Policy activity_resource_assignments: el BRIEF asumía "all_activity_resource_assignments" como nombre de la policy. La migration 007 la creó como "all_assignments". Claude Code auditó las migraciones 001-013 antes de escribir los DROP POLICY y corrigió el nombre. Sin la corrección, el DROP habría pasado silenciosamente (IF EXISTS) sin eliminar la policy permisiva original, manteniendo acceso sin restricción de rol.

Tabla resources sin process_id: el brief del ETAPA_1_PROMPT incluía una policy resources_select con condición resources.process_id IS NULL. La columna no existe desde Sprint 3 (catálogo global). Claude Code detectó la discrepancia y adaptó la policy a auth.uid() IS NOT NULL, equivalente funcional para catálogo sin filtro de proceso.

Auto-migración clientorg_id: en el entorno de dev, los 158 procesos existentes no tienen valor en el campo client → 0 procesos vinculados a org. Esto es esperado: el campo client es opcional y en dev no está poblado con clientes reales. En producción, si hay procesos con client no vacío, la auto-migración los vinculará automáticamente al primer deploy.

OK formal del director: 2026-07-06.


[RESOLVED] Etapa 2 — Edge Functions invite-user y revoke-user

Funciones creadas en supabase/functions/. El diseño simplificó el scope del BRIEF (que pedía delete-user + suspend-user + unsuspend-user separadas) a invite-user + revoke-user (soft suspension a 'guest'). Las limitaciones quedaron documentadas en UsersPage.tsx:

  • "Eliminar" llama revoke-user (baja a guest) — eliminación real requiere función futura
  • "Reactivar" restaura siempre a 'client_editor' — rol previo no se guarda OK formal implícito del director: 2026-07-06.

[RESOLVED] Etapa 3 — Auth layer: fix rol desde DB + email/password + reset-password

Fix crítico: syncProfileFromDB ahora lee platform_role y org_id desde DB en lugar de inferir el rol del dominio del email. AuthUser tiene orgId?: string. signInWithEmailPassword implementado. TOKEN_REFRESHED no dispara loading. LoginPage con form email/password. ResetPasswordPage en /reset-password. 558 tests (+ 6 nuevos).

Deuda identificada: PASSWORD_RECOVERY maneja vía segunda suscripción a onAuthStateChange (evitar importación circular). No es bug, pero el patrón de doble suscripción es frágil. Ver ítem en TECH_DEBT.md. OK formal del director: 2026-07-06.


[RESOLVED] Etapa 4 — Panel de administración

/admin/organizations, /admin/organizations/$orgId, /admin/users con guard de platform_admin. Invitación de usuarios via Edge Function invite-user. Suspensión/reactivación via revoke-user. Skeletons durante carga. Badge "Tú" en fila propia. Acciones deshabilitadas para self-action. RoleBadge con colores por rol.

Deuda identificada: guard implementado en AdminLayout como componente (no en beforeLoad de TanStack Router) — puede haber flash breve del contenido antes del redirect. Ver ítem recurrente en TECH_DEBT.md. OK formal del director: 2026-07-06 (validación visual con DOM).


[RESOLVED] Etapa 5 — UX de roles de cliente y routing

Guards de rol en WorkspacePage e ImportPage vía useEffect. client_viewer redirigido a /report/$processId al entrar a workspace. client_editor bloqueado de /import. Botón "Importar BPMN" oculto para roles de cliente. Botón "Nuevo proceso" oculto para roles de cliente. Panel de grupos oculto para roles de cliente (lista plana). ReportPage con defaultTab = 'roi' para client_viewer. 578 tests (+ 20 nuevos).

Bug encontrado en validación: group_label = "un Cliente" guardado como dato corrupto en Supabase → "2 un clientes" en UI. Causa: el campo era editable desde Settings y en algún momento se guardó con el artículo. Fix: normalizeGroupLabel() en settings-repo.ts que limpia artículos iniciales (un, una, el, la) al leer y al guardar. Solución transparente sin migración de datos.

Deuda identificada (BACKLOG): Settings page (ícono de tuerca) accesible para client_editor. La página es cosmética (solo label de grupos), pero no debería ser visible para roles de cliente. Ítem agregado en BACKLOG.md.

Patrón recurrente identificado: guards de rol implementados tres veces via useEffect en lugar de beforeLoad de TanStack Router (AdminLayout, WorkspacePage, ImportPage). Funcional pero subóptimo — posible flash antes del redirect. Candidato a TECH_DEBT sprint siguiente.

OK formal del director: 2026-07-06 (validación visual con cambio temporal de rol en Supabase Studio).


Hallazgos diferidos (promocionar al cerrar sprint)

[DEFERRED-TECH-DEBT] Guards de rol via useEffect en lugar de beforeLoad (3 componentes)

AdminLayout, WorkspacePage, ImportPage — los tres usan useEffect para detectar el rol y redirigir. Con beforeLoad de TanStack Router, la ruta nunca monta para usuarios sin acceso. Con useEffect, el componente monta brevemente antes del redirect (posible flash). No es bug de seguridad (RLS protege los datos), pero es anti-patrón UX y arquitectónico para TanStack Router. Migrar a: TECH_DEBT.md al cerrar Sprint 7.

[DEFERRED-TECH-DEBT] Doble suscripción onAuthStateChange en AuthContext

AuthContext tiene dos listeners de onAuthStateChange: uno principal (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED) y uno secundario para PASSWORD_RECOVERY. Fue implementado para evitar una importación circular. El patrón correcto es un único listener con switch/case completo. Refactorizar cuando se resuelva la dependencia circular. Migrar a: TECH_DEBT.md al cerrar Sprint 7.

[DEFERRED-BACKLOG] Settings page accesible para client_editor/client_viewer

El ícono de configuración (tuerca) en LibraryPage y la ruta /settings (si existe) son accesibles para todos los roles. La página solo configura el label de grupos (preferencia cosmética), pero un usuario cliente no debería verla. Ocultar el ícono de configuración y agregar guard de rol en la ruta. Migrar a: BACKLOG.md al cerrar Sprint 7 — ya agregado por Claude Code.


Notas operativas

  • Los hallazgos resueltos quedan acá hasta el cierre del sprint. Al cerrar, se archiva o purga.
  • Los hallazgos sin resolver se promocionan a TECH_DEBT.md o BACKLOG.md según corresponda.
  • Este archivo NO contiene roadmap futuro ni deuda histórica — solo hallazgos del sprint en curso.

Cierre del sprint (checklist)

[x] Verificar que todos los items [CURRENT-SPRINT] están RESOLVED
[x] Mover items [DEFERRED-TECH-DEBT] a TECH_DEBT.md (pendiente commit)
[x] Mover items [DEFERRED-BACKLOG] a BACKLOG.md (ya agregado por Claude Code en Etapa 5)
[ ] Archivar o purgar este archivo
[ ] Crear OBSERVATIONS.md vacío para el sprint siguiente