Ambas funciones deployadas al proyecto inq-roi-desa (xgqbkxcliyjzisoccsur).
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# OBSERVATIONS — Sprint 6
|
||||
# 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 5 cerrado el 2026-06-23.** Observaciones archivadas — ítems migrados a BACKLOG.md y TECH_DEBT.md.
|
||||
> **Sprint 6 cerrado el 2026-06-24.** Observaciones archivadas — ítems migrados a BACKLOG.md y TECH_DEBT.md.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,32 +24,75 @@
|
||||
|
||||
## Hallazgos resueltos durante el sprint
|
||||
|
||||
### [RESOLVED] Etapa 3 — Fix localStorage key + setTimeout cleanup + specs E2E de auth
|
||||
**Bug real confirmado:** `ProfileSheet.getAccessToken()` leía `'supabase.auth.token'` (clave inexistente en supabase-js v2). El guardado de nombre y empresa nunca llegó a Supabase desde que se implementó ProfileSheet en Sprint 4. Fix: calcular `sb-${projectRef}-auth-token` igual que auth-setup.ts. Verificado end-to-end: PATCH 204 + reload confirma persistencia.
|
||||
**setTimeout(350) eliminado:** fix de sheet.tsx es suficiente por sí solo.
|
||||
**Specs E2E:** `auth-localStorage-key.spec.ts` (diagnóstico de clave) y `auth-real.spec.ts` (validación de auth real). 552 tests, 11/11 E2E verdes.
|
||||
**OK formal del director:** 2026-06-24.
|
||||
### [RESOLVED] Etapa 1 — Migraciones DB + SECURITY DEFINER helpers
|
||||
|
||||
### [RESOLVED] Etapa 1 — Logo InQuality en header de PDFs (págs 2+)
|
||||
**Observación 1:** Claude Code implementó inicialmente el logo con dimensiones hardcodeadas (32×9mm, ratio 3.56:1) cuando el PNG real es 724×474px (ratio 1.53:1) → logo aplastado horizontalmente. Fix: pasar dimensiones reales del bitmap y calcular el alto proporcional en `drawSharedHeader`.
|
||||
**Observación 2:** Claude Code agregó "UN PRODUCTO DE InQ InQuality" y logo en la portada (página 1) sin que estuviera en el scope del prompt — violación de la restricción explícita "No agregar el logo en páginas 1 con su propio diseño de portada." Revertido en fix posterior.
|
||||
**Aprendizaje del método:** cuando el prompt incluye restricciones del tipo "NO hacer X en archivo Y", el agente puede introducir el cambio equivalente en archivos relacionados no listados. Auditar activamente los archivos adyacentes al scope declarado.
|
||||
**Resultado final:** logo isotipo a 15mm de ancho, alto proporcional (~9.8mm), páginas 2+ de los 3 PDFs. Portada intacta. whitespace-nowrap en MetricCell de Eficiencia incluido como fold-in.
|
||||
**OK formal del director:** 2026-06-24.
|
||||
**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 `client` → `org_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 — Panel de análisis de sensibilidad
|
||||
**Resultado:** SensitivityPanel con dos sliders nativos (volumen -50%/+100%, inversión -50%/+100%), badge "Análisis activo", botón Restablecer. adjustedRoi via useMemo en ReportPage, sin re-simulación. Versión wow implementada: doble línea en CumulativeSavingsChart (base gris dashed + área sombreada + ajustada naranja). 552 tests (542 + 10 nuevos).
|
||||
**Fix post-validación:** layout del tab ROI reordenado — CumulativeSavingsChart sube por encima de ComparisonTable para mantener adyacencia con los sliders y la doble línea del gráfico. ComparisonTable queda como detalle granular al final.
|
||||
**Nota técnica a documentar:** CumulativeSavingsChart deriva la inversión original del base case desde `roiBase.cumulativeSavingsHorizon` algebraicamente en lugar de recibir prop `investmentBase`. Correcto hoy (horizonYears del chart = horizonYears de calculateRoi), frágil si divergen. Registrar en TECH_DEBT.md al cerrar el sprint.
|
||||
**OK formal del director:** 2026-06-24.
|
||||
### [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] Selector frágil en auth-real.spec.ts
|
||||
`tests/e2e/auth-real.spec.ts` usa `header button[title]` para encontrar el avatar del usuario en AppHeader. Si el atributo `title` cambia, el test rompe sin advertencia. Candidato a agregar `data-testid="app-header-user-btn"` en una etapa futura (mismo patrón que `save-activity-btn`).
|
||||
**Migrar a:** TECH_DEBT.md al cerrar Sprint 6.
|
||||
### [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.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,12 +106,10 @@
|
||||
|
||||
## Cierre del sprint (checklist)
|
||||
|
||||
Cuando el sprint cierre, ejecutar:
|
||||
|
||||
```
|
||||
[ ] Verificar que todos los items [CURRENT-SPRINT] están RESOLVED
|
||||
[ ] Mover items [DEFERRED-TECH-DEBT] a TECH_DEBT.md (si no están ya)
|
||||
[ ] Mover items [DEFERRED-BACKLOG] a BACKLOG.md (si no están ya)
|
||||
[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
|
||||
```
|
||||
|
||||
@@ -323,3 +323,66 @@ Sprint orientado a "client-readiness": cerrar los dos gaps que impedían usar In
|
||||
**Sprint 7 abierto:** multi-usuario y acceso de clientes. Modelo de 4 roles (`platform_admin`, `member`, `client_editor`, `client_viewer`). Patrón de InQ Sizing (documento de referencia: `cowork/InQ ROI - Simulador de procesos/autenticacion-y-usuarios.md`). InQuality como organización administradora de la plataforma.
|
||||
|
||||
**Cerrado:** 2026-06-24.
|
||||
|
||||
---
|
||||
|
||||
## Reflexiones del cierre de Sprint 7
|
||||
|
||||
> Agregado el 2026-07-06 al cerrar formalmente el sprint.
|
||||
|
||||
### Decisión estratégica
|
||||
|
||||
Sprint de mayor escala arquitectónica desde Sprint 4 (refactorización Dexie→Supabase). Sprint 7 abrió el producto a usuarios externos con control de acceso real: tabla `organizations`, 4 roles de plataforma, Edge Functions de invitación, panel de administración de InQuality, y ajustes de UX para cada rol. La fundación de multi-tenancy está operativa.
|
||||
|
||||
### Resultado
|
||||
|
||||
**Etapa 1 (DB):** migración 014 — tabla `organizations`, `org_id` en `users` y `processes`, constraint de roles ampliado (platform_admin, member, client_editor, client_viewer, guest). Migración 015 — SECURITY DEFINER helpers (`current_org()`, `is_inquality()`, `current_platform_role()`), trigger `handle_new_user`, RLS completa con 4 roles en 7 tablas.
|
||||
|
||||
**Etapa 2 (Edge Functions):** `invite-user` (invita via Supabase Auth Admin API + UPSERT en public.users) y `revoke-user` (baja a 'guest', suspensión suave). Diseño simplificado respecto al BRIEF: 2 funciones en lugar de 3 separadas (create/suspend/delete). Limitación documentada: "eliminar" es soft (baja a guest), hard delete queda para sprint futuro.
|
||||
|
||||
**Etapa 3 (Auth layer):** fix crítico — `syncProfileFromDB` ahora lee `platform_role` y `org_id` desde DB en lugar de inferir del email. `AuthUser` extendido con `orgId`. Login email/password. Página `/reset-password` para flujo de invite. Guard `TOKEN_REFRESHED` en `onAuthStateChange`. 558 tests.
|
||||
|
||||
**Etapa 4 (Admin UI):** panel `/admin` con guard `platform_admin`, páginas de Organizations y Users con CRUD completo, invitación de usuarios via Edge Function, suspensión/reactivación, badges de rol, skeletons, alertdialogs para acciones destructivas, ícono Shield en AppHeader. 558 tests.
|
||||
|
||||
**Etapa 5 (Client UX):** routing por rol — `client_viewer` redirigido desde workspace al reporte, `client_editor` bloqueado de importar BPMNs. Library con lista plana para clientes (sin panel de grupos). Reporte abre en tab ROI para `client_viewer`. Fix bug: `normalizeGroupLabel()` limpia artículos iniciales del label de grupos (dato corrupto en Supabase). 578 tests.
|
||||
|
||||
### Decisiones arquitectónicas tomadas en el sprint
|
||||
|
||||
| Decisión | Estado al cierre |
|
||||
|---|---|
|
||||
| Estrategia de migración: Opción A (auto-crear orgs desde `processes.client`) | Definitiva — migración 014 aplicada |
|
||||
| Trigger `handle_new_user`: rol `member` por defecto (no `platform_admin`) | Definitiva — confirmada por director |
|
||||
| UPSERT en Edge Function invite-user (race condition handle_new_user) | Definitiva — patrón de InQ Sizing |
|
||||
| "Eliminar" usuario como soft delete (baja a 'guest') | Provisional — hard delete en sprint futuro |
|
||||
| "Reactivar" restaura a 'client_editor' por defecto | Provisional — limitación documentada |
|
||||
|
||||
### Patrón recurrente identificado: guards de rol via `useEffect`
|
||||
|
||||
En tres etapas (AdminLayout, WorkspacePage, ImportPage), Claude Code implementó guards de rol como efectos de componente en lugar de `beforeLoad` de TanStack Router. El patrón es funcional (RLS garantiza seguridad de datos) pero subóptimo para TanStack Router, que provee `beforeLoad` precisamente para evitar que rutas protegidas monten. Este patrón surgió como heurística del agente cuando el guard se especificó en la descripción del comportamiento en lugar de en la spec de la ruta. **Aprendizaje para Sprint 8+:** especificar guards siempre en la sección de router con el código exacto de `beforeLoad`.
|
||||
|
||||
### Lo que funcionó bien
|
||||
|
||||
**La auditoría previa de policy names por parte de Claude Code (Etapa 1).** Antes de escribir los DROP POLICY, Claude Code leyó las migraciones 001-013 y reportó los nombres exactos con discrepancias respecto al brief. Sin esta auditoría, `"all_activity_resource_assignments"` habría dejado la policy permisiva original activa. Este comportamiento proactivo de verificación debería solicitarse explícitamente en futuros prompts de migración RLS.
|
||||
|
||||
**El flujo de validación con cambio temporal de rol en Supabase Studio.** El director pudo validar el comportamiento de `client_editor` y `client_viewer` sin necesidad de crear usuarios reales de prueba, editando su propio `platform_role` temporalmente. Técnica útil para validación rápida pre-onboarding de clientes reales.
|
||||
|
||||
**La normalización de datos en capa de repositorio.** `normalizeGroupLabel()` en `settings-repo.ts` resolvió un dato corrupto en Supabase sin migración ni intervención manual — limpieza transparente al leer/escribir. Patrón replicable para otros datos con formato variable en la DB.
|
||||
|
||||
### Estado del producto al cierre de Sprint 7
|
||||
|
||||
- Auth completo con 4 roles, organizaciones, y panel de administración operativo
|
||||
- Primer acceso de cliente posible: invitación → magic link → set password → library filtrada por org → workspace (editor) o reporte (viewer)
|
||||
- Datos: 578 tests Vitest, build limpio
|
||||
- Deuda técnica identificada: 2 ítems (guards via useEffect × 3 componentes, doble suscripción onAuthStateChange)
|
||||
- Backlog identificado: 1 ítem (Settings accesible para roles de cliente)
|
||||
|
||||
### Próxima revisión — Sprint 8
|
||||
|
||||
**Prioridades candidatas (sin decisión tomada aún):**
|
||||
- Primer onboarding real de cliente (Banco Solar) — smoke test end-to-end del flujo de invite
|
||||
- Hard delete de usuarios (Edge Function `delete-user`)
|
||||
- Migrar guards de rol a `beforeLoad` de TanStack Router
|
||||
- Email visible en panel admin (requiere join con `auth.users` via service_role)
|
||||
- Historial de simulaciones (diferido desde Sprint 3)
|
||||
|
||||
**Cerrado:** 2026-07-06.
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.109.0
|
||||
v2.109.1
|
||||
1
supabase/.temp/gotrue-version
Normal file
1
supabase/.temp/gotrue-version
Normal file
@@ -0,0 +1 @@
|
||||
v2.192.0
|
||||
1
supabase/.temp/pooler-url
Normal file
1
supabase/.temp/pooler-url
Normal file
@@ -0,0 +1 @@
|
||||
postgresql://postgres.xgqbkxcliyjzisoccsur@aws-1-us-east-2.pooler.supabase.com:5432/postgres
|
||||
1
supabase/.temp/postgres-version
Normal file
1
supabase/.temp/postgres-version
Normal file
@@ -0,0 +1 @@
|
||||
17.6.1.127
|
||||
1
supabase/.temp/project-ref
Normal file
1
supabase/.temp/project-ref
Normal file
@@ -0,0 +1 @@
|
||||
xgqbkxcliyjzisoccsur
|
||||
1
supabase/.temp/rest-version
Normal file
1
supabase/.temp/rest-version
Normal file
@@ -0,0 +1 @@
|
||||
v14.5
|
||||
1
supabase/.temp/storage-migration
Normal file
1
supabase/.temp/storage-migration
Normal file
@@ -0,0 +1 @@
|
||||
optimize-existing-functions-again
|
||||
1
supabase/.temp/storage-version
Normal file
1
supabase/.temp/storage-version
Normal file
@@ -0,0 +1 @@
|
||||
v1.61.10
|
||||
@@ -24,7 +24,8 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
Deno.serve(async (req: Request) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': Deno.env.get('SITE_URL') ?? '*',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ interface RevokeUserRequest {
|
||||
Deno.serve(async (req: Request) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': Deno.env.get('SITE_URL') ?? '*',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user