# 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
