diff --git a/src/auth/AuthContext.tsx b/src/auth/AuthContext.tsx index d18cc78..421aa18 100644 --- a/src/auth/AuthContext.tsx +++ b/src/auth/AuthContext.tsx @@ -77,25 +77,40 @@ export function AuthProvider({ children }: { children: ReactNode }) { // user?.id es primitivo estable: no cambia cuando se edita nombre o empresa. useEffect(() => { if (!user?.id) return + const userId = user.id + const capturedUser = user + + // 6s timeout: si el servidor no responde, la seƱal de abort cancela las queries + // y libera el processLock de Supabase antes de que bloquee loads de biblioteca/workspace. + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 6000) ;(async () => { try { // Upsert idempotente: crea la fila si no existe (primer login), - // ignoraDuplicates evita sobreescribir name/company editados posteriormente. - await authService.upsertUserOnLogin(user) + // ignoreDuplicates evita sobreescribir name/company editados posteriormente. + await authService.upsertUserOnLogin(capturedUser, controller.signal) // Leer company y name editado desde la tabla (pueden diferir del user_metadata de Google). - const profile = await authService.syncProfileFromDB(user.id) + const profile = await authService.syncProfileFromDB(userId, controller.signal) if (profile) { setUser((prev) => - prev?.id === user.id + prev?.id === userId ? { ...prev, company: profile.company, name: profile.name ?? prev.name } : prev ) } } catch (err) { + if (err instanceof Error && err.name === 'AbortError') return console.error('Error al sincronizar perfil:', err) + } finally { + clearTimeout(timeoutId) } })() + + return () => { + controller.abort() + clearTimeout(timeoutId) + } }, [user?.id]) // eslint-disable-line react-hooks/exhaustive-deps const signIn = useCallback(() => authService.signInWithGoogle(), []) diff --git a/src/auth/SupabaseAuthService.ts b/src/auth/SupabaseAuthService.ts index 73172d7..d88212f 100644 --- a/src/auth/SupabaseAuthService.ts +++ b/src/auth/SupabaseAuthService.ts @@ -59,12 +59,10 @@ export class SupabaseAuthService implements AuthService { // Leer company y name editados desde la tabla users (pueden diferir del user_metadata de Google). // Se llama desde AuthContext FUERA del lock de auth, en un useEffect normal. - async syncProfileFromDB(userId: string): Promise | null> { - const { data } = await supabase - .from('users') - .select('company, name') - .eq('id', userId) - .maybeSingle() + // signal permite cancelar si el servidor tarda demasiado (libera el processLock). + async syncProfileFromDB(userId: string, signal?: AbortSignal): Promise | null> { + const base = supabase.from('users').select('company, name').eq('id', userId) + const { data } = await (signal ? base.abortSignal(signal) : base).maybeSingle() if (!data) return null return { name: (data.name as string | null) ?? undefined as unknown as string, @@ -74,8 +72,9 @@ export class SupabaseAuthService implements AuthService { // Upsert idempotente al login: crea la fila si no existe, ignora si ya existe. // Se llama desde AuthContext FUERA del lock de auth, en un useEffect normal. - async upsertUserOnLogin(user: AuthUser): Promise { - const { error } = await supabase.from('users').upsert( + // signal permite cancelar si el servidor tarda demasiado (libera el processLock). + async upsertUserOnLogin(user: AuthUser, signal?: AbortSignal): Promise { + const builder = supabase.from('users').upsert( { id: user.id, name: user.name, @@ -84,6 +83,7 @@ export class SupabaseAuthService implements AuthService { }, { onConflict: 'id', ignoreDuplicates: true } ) + const { error } = await (signal ? builder.abortSignal(signal) : builder) if (error) console.error('Error al crear usuario en DB:', error) }