From 92597e108d373f1a09ca35a98b048b3f115da95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Ben=C3=ADtez?= Date: Fri, 19 Jun 2026 00:33:46 -0300 Subject: [PATCH] Etapa 7B completa con Fix 4 agregado: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 4 Parte A: .maybeSingle() ya estaba aplicado (lo hiciste vos/el linter). Encontré y arreglé un bug adicional: con data === null (sin simulaciones), el código llamaba fromRow(null) y explotaba al leer row.id. Ahora getLatestByProcess retorna undefined limpiamente cuando no hay filas. Fix 4 Parte B: revisé ProcessCardWithSim en LibraryPage.tsx — ya tiene guard de cancelación y dependencias estables (process.id + acción de Zustand, que es una referencia fija). No encontré un loop real; lo más probable es que "~6 queries" correspondan a 6 procesos distintos disparando una query cada uno (correcto), posiblemente duplicado visualmente en dev por StrictMode (inocuo, no pasa en producción). Fixes 1, 2, 3 ya estaban implementados de antes y siguen verdes. Falta tu validación visual — en particular confirmar que el network tab ya no muestra 406 en simulations. --- sprints/sprint-4/ETAPA_7B_PROMPT.md | 56 ++++++++++++++++++--- src/persistence/supabase/simulation-repo.ts | 4 +- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/sprints/sprint-4/ETAPA_7B_PROMPT.md b/sprints/sprint-4/ETAPA_7B_PROMPT.md index d3a1a33..c9d5069 100644 --- a/sprints/sprint-4/ETAPA_7B_PROMPT.md +++ b/sprints/sprint-4/ETAPA_7B_PROMPT.md @@ -133,14 +133,56 @@ async signInWithGoogle(): Promise { --- +## Fix 4 — simulations: 406 por `.single()` sin filas + re-fetch en loop + +### Problema + +En el network tab se observan múltiples requests con 406 Not Acceptable: +``` +GET simulations?select=*&process_id=eq.{id}&order=executed_at.desc&limit=1 → 406 +``` + +**Causa:** supabase-js v2 `.single()` envía `Accept: application/vnd.pgrst.object+json`, que exige exactamente 1 fila. Si el proceso no tiene simulaciones aún (0 filas), PostgREST responde 406. El mismo query aparece ~6 veces para los mismos `processId` — señal de un `useEffect` re-fetching por dependencia incorrecta. + +### Solución + +**Parte A — Fix del 406:** en `src/persistence/supabase/simulation-repo.ts`, en el método que busca la última simulación por proceso, cambiar `.single()` por `.maybeSingle()`: + +```typescript +// Antes: +const { data, error } = await supabase + .from('simulations') + .select('*') + .eq('process_id', processId) + .order('executed_at', { ascending: false }) + .limit(1) + .single() // ← lanza 406 cuando no hay filas + +// Después: +const { data, error } = await supabase + .from('simulations') + .select('*') + .eq('process_id', processId) + .order('executed_at', { ascending: false }) + .limit(1) + .maybeSingle() // ← retorna null sin error cuando no hay filas +``` + +El caller debe manejar `data === null` como "sin simulación" (no como error). + +**Parte B — Fix del re-fetch en loop:** localizar el `useEffect` que dispara esta query (probablemente en `LibraryPage.tsx` o en un hook de simulación). Revisar que sus dependencias no incluyan un objeto o array que se re-crea en cada render (patrón clásico de loop infinito). Si el array de dependencias contiene `processes` (que es un array nuevo en cada render del store), estabilizarlo con `useMemo` o extraer solo los IDs que realmente cambian. + +--- + ## Orden de ejecución -1. Fix 3 — `SupabaseAuthService.signInWithGoogle()` (1 línea, más fácil de verificar primero) -2. Fix 2 — función `formatCostPerHour` con precisión dinámica + aplicar en todos los contextos -3. Fix 1 — query param en ResourcesPanel + banner + botón en ResourceCatalogPage -4. `npm run build` — debe pasar sin errores TypeScript -5. `npm run test` — debe mantener 521+ tests verdes -6. Commit: `fix(sprint-4/etapa-7b): selector cuenta Google + redondeo costo/hora + contexto catálogo` +1. Fix 3 — `SupabaseAuthService.signInWithGoogle()` (1 línea) +2. Fix 4 — `.maybeSingle()` en simulation-repo + revisar re-fetch loop +3. Fix 2 — función `formatCostPerHour` con precisión dinámica + aplicar en todos los contextos +4. Fix 1 — query param en ResourcesPanel + banner + botón en ResourceCatalogPage +5. `npm run build` — debe pasar sin errores TypeScript +6. `npm run test` — debe mantener 521+ tests verdes +7. Commit: `fix(sprint-4/etapa-7b): selector cuenta Google + redondeo costo/hora + contexto catálogo + simulations maybeSingle` --- @@ -153,6 +195,8 @@ async signInWithGoogle(): Promise { - [ ] Botón "← Usar en proceso" aparece en cada fila cuando hay contexto de proceso - [ ] Click en "← Usar en proceso" navega a `/workspace/:processId` - [ ] Navegar a `/recursos` directamente (sin `fromProcess`) no muestra banner ni botón extra +- [ ] Network tab sin 406 en requests de `simulations` +- [ ] Network tab: el query de simulaciones no se repite en loop para el mismo `processId` - [ ] Build limpio sin errores TypeScript - [ ] 521+ tests verdes - [ ] **Validación visual del director** antes del OK formal diff --git a/src/persistence/supabase/simulation-repo.ts b/src/persistence/supabase/simulation-repo.ts index c54ebcb..33315e7 100644 --- a/src/persistence/supabase/simulation-repo.ts +++ b/src/persistence/supabase/simulation-repo.ts @@ -32,8 +32,8 @@ export const supabaseSimulationRepo = { .eq('process_id', processId) .order('executed_at', { ascending: false }) .limit(1) - .single() - if (error) return undefined + .maybeSingle() + if (error || !data) return undefined return fromRow(data as Record) },