From 4a19eda1b27882d0e2a6e4714561e94d9da24388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Ben=C3=ADtez?= Date: Sat, 20 Jun 2026 00:39:39 -0300 Subject: [PATCH] fix guardar perfil --- sprints/sprint-5/ETAPA_4B_PROMPT.md | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 sprints/sprint-5/ETAPA_4B_PROMPT.md diff --git a/sprints/sprint-5/ETAPA_4B_PROMPT.md b/sprints/sprint-5/ETAPA_4B_PROMPT.md new file mode 100644 index 0000000..2a6b013 --- /dev/null +++ b/sprints/sprint-5/ETAPA_4B_PROMPT.md @@ -0,0 +1,105 @@ +# Sprint 5 — Etapa 4B: Fix loop infinito al guardar perfil + +## Contexto + +Etapa 4 completada con validación visual parcial. Al guardar cambios en el Sheet de "Editar perfil" (nombre o empresa), la app se congela indefinidamente. El resto de Etapa 4 está OK. + +## Diagnóstico + +El síntoma (app congela, no responde) es loop infinito en React. La secuencia probable: + +1. Usuario guarda perfil → `UPDATE users SET name=?, company=? WHERE id=?` +2. `updateUser({ name, company })` actualiza el `AuthUser` en el store/context local +3. Esa actualización de estado dispara un `useEffect` o el `onAuthStateChange` callback +4. Ese callback re-lee la tabla `users` desde Supabase (`SELECT company, name FROM users WHERE id=?`) +5. Al recibir el resultado, vuelve a llamar `updateUser(...)` con los mismos valores +6. → Loop infinito + +La raíz del problema es que el `onAuthStateChange` que se implementó en Etapa 4 para leer `company` y `name` de la DB se re-dispara cuando el store local se actualiza, o que hay un `useEffect` con dependencia en `user` que no tiene guard contra actualizaciones idempotentes. + +## Fix + +### Paso 1 — Diagnosticar dónde está el loop + +Localizar el `useEffect` o callback que lee de la tabla `users` después de auth state change. Buscar en: +- `AuthContext.tsx` o equivalente +- El hook `useAuth` +- Cualquier `useEffect` que tenga `user` o `authUser` como dependencia y haga una query a `users` + +### Paso 2 — Separar el flujo de "save perfil" del flujo de auth state change + +El problema es que `updateUser` (actualización del store local) está acoplado al mecanismo de re-lectura de la DB. La solución es desacoplarlos: + +**Opción A — Flag de re-entrada (más simple):** + +Agregar un ref `isUpdatingProfile` que se activa durante el save y previene que el `onAuthStateChange` re-lea de la DB mientras está activo: + +```ts +const isUpdatingProfile = useRef(false) + +// En el save del perfil: +const saveProfile = async ({ name, company }) => { + isUpdatingProfile.current = true + try { + await supabase.from('users').update({ name, company }).eq('id', user.id) + // Actualizar store local directamente, sin pasar por onAuthStateChange: + setUser(prev => prev ? { ...prev, name, company } : prev) + } finally { + isUpdatingProfile.current = false + } +} + +// En onAuthStateChange o el useEffect que re-lee la DB: +if (isUpdatingProfile.current) return // ← guard de re-entrada +``` + +**Opción B — Actualizar solo el estado local sin re-leer DB (más limpia):** + +En el save del perfil, **no** re-disparar el mecanismo de `onAuthStateChange`. Solo actualizar el estado local con los valores que el usuario acaba de escribir — ya los tenés, no hace falta volver a leerlos de la DB: + +```ts +const saveProfile = async ({ name, company }) => { + await supabase.from('users').update({ name, company }).eq('id', user.id) + + // Actualizar el AuthUser en el store directamente con los valores conocidos. + // NO llamar a ninguna función que re-lea de la DB. + setCurrentUser(prev => prev ? { ...prev, name, company } : prev) + + // Cerrar el Sheet. + setProfileSheetOpen(false) +} +``` + +Para que esto funcione, `setCurrentUser` (o equivalente) debe ser una actualización directa del estado, sin side effects que disparen re-lectura. + +**Recomendación:** Opción B. Es más predecible: el save actualiza la DB y el estado local con los valores exactos que el usuario confirmó. No hay queries adicionales, no hay riesgo de loop. + +### Paso 3 — Verificar que el `useEffect` que lee de `users` tiene guards adecuados + +El `useEffect` o callback que lee `company` y `name` de la tabla `users` al detectar un cambio de auth debería ejecutarse solo en eventos genuinos de auth (login, token refresh), no en cada actualización del objeto `AuthUser` local. + +Verificar que la dependencia del efecto es el **ID de sesión o el evento de auth**, no el objeto `user` completo (que cambia cada vez que lo actualizamos localmente): + +```ts +// ❌ Loop: se re-ejecuta cada vez que user cambia, incluyendo el update local: +useEffect(() => { + if (user) fetchCompanyFromDB(user.id) +}, [user]) + +// ✅ Correcto: solo se ejecuta cuando cambia la sesión real: +useEffect(() => { + if (sessionId) fetchCompanyFromDB(userId) +}, [sessionId]) // sessionId es estable entre updates locales +``` + +--- + +## Criterios de validación + +- [ ] Guardar nombre en el Sheet → app no se congela → nombre actualizado en el dropdown +- [ ] Guardar empresa en el Sheet → app no se congela → empresa aparece en el dropdown +- [ ] Cmd+R después de guardar → los valores persisten (están en Supabase) +- [ ] Navegar a otra página y volver → los valores siguen correctos +- [ ] `npm run build` limpio +- [ ] `npm run test` — 528+ Vitest verdes +- [ ] **Validación visual del director** antes del OK formal