Files
inq-roi-simulador-web/sprints/sprint-5/ETAPA_4B_PROMPT.md
Marcos Benítez 4a19eda1b2
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
fix guardar perfil
2026-06-20 00:39:39 -03:00

106 lines
4.7 KiB
Markdown

# 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