This commit is contained in:
105
sprints/sprint-5/ETAPA_4B_PROMPT.md
Normal file
105
sprints/sprint-5/ETAPA_4B_PROMPT.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user