import React, { createContext, useContext, useEffect, useMemo, useState, useCallback, type ReactNode, } from "react"; import type { User as SupabaseUser } from "@supabase/supabase-js"; import { supabase } from "@/lib/supabase"; import { getActiveTableroCdcAccess, type TableroCdcUserAccess } from "@/lib/accessControl"; import { recordTableroCdcOpen } from "@/lib/appMetrics"; function getRedirectTo(): string { if (typeof window === "undefined") return "/tablero-cdc/"; return new URL(import.meta.env.BASE_URL || "/", window.location.origin).toString(); } export interface AuthUser { id: string; uid: string; email: string | null; displayName: string | null; photoURL: string | null; raw: SupabaseUser; } function normalizeSupabaseUser(user: SupabaseUser): AuthUser { const metadata = user.user_metadata || {}; const displayName = metadata.full_name || metadata.name || metadata.display_name || user.email?.split("@")[0] || null; const photoURL = metadata.avatar_url || metadata.picture || null; return { id: user.id, uid: user.id, email: user.email ?? null, displayName, photoURL, raw: user, }; } interface AuthContextValue { user: AuthUser | null; loading: boolean; error: string | null; isGerardo: boolean; canDeleteProjects: boolean; canManageInternalPricing: boolean; canControlPricingSummary: boolean; canManageTariffCatalog: boolean; canViewTeamHours: boolean; canCreateProjects: boolean; loginWithGoogle: () => Promise; logout: () => Promise; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [access, setAccess] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const applyUser = useCallback(async (supabaseUser: SupabaseUser | null) => { if (!supabaseUser) { setUser(null); setAccess(null); return; } let nextAccess: TableroCdcUserAccess | null = null; try { nextAccess = await getActiveTableroCdcAccess(supabaseUser.email); } catch (accessError) { console.error("Error al validar acceso al Tablero CDC:", accessError); await supabase.auth.signOut(); setUser(null); setAccess(null); setError( "No se pudo validar tu acceso al Tablero CDC. Verifica que el SQL de accesos esté ejecutado o contacta a IT.", ); return; } if (!nextAccess) { await supabase.auth.signOut(); setUser(null); setAccess(null); setError( "Tu correo no está autorizado para acceder al Tablero CDC. Solicita acceso a IT o al equipo CDC.", ); return; } setUser(normalizeSupabaseUser(supabaseUser)); setAccess(nextAccess); setError(null); // Métrica no bloqueante: una apertura por sesión autenticada/autorizada. void recordTableroCdcOpen(); }, []); useEffect(() => { let mounted = true; supabase.auth .getSession() .then(async ({ data, error: sessionError }) => { if (!mounted) return; if (sessionError) { console.error("Error al recuperar sesión de Supabase:", sessionError); setUser(null); setError("No se pudo recuperar la sesión. Intenta iniciar sesión de nuevo."); return; } await applyUser(data.session?.user ?? null); }) .catch((err: unknown) => { if (!mounted) return; console.error("Error inesperado al recuperar sesión:", err); setUser(null); setError("No se pudo recuperar la sesión. Intenta iniciar sesión de nuevo."); }) .finally(() => { if (mounted) setLoading(false); }); const { data: listener } = supabase.auth.onAuthStateChange((_event, session) => { setLoading(true); void applyUser(session?.user ?? null).finally(() => { if (mounted) setLoading(false); }); }); return () => { mounted = false; listener.subscription.unsubscribe(); }; }, [applyUser]); const loginWithGoogle = useCallback(async () => { setError(null); const { error: loginError } = await supabase.auth.signInWithOAuth({ provider: "google", options: { redirectTo: getRedirectTo(), queryParams: { prompt: "select_account", }, }, }); if (loginError) { console.error("Error al iniciar sesión con Google:", loginError); setError("No se pudo iniciar sesión. Intenta de nuevo."); } }, []); const logout = useCallback(async () => { const { error: logoutError } = await supabase.auth.signOut(); if (logoutError) { console.error("Error al cerrar sesión:", logoutError); setError("No se pudo cerrar sesión. Intenta de nuevo."); return; } setUser(null); setAccess(null); setError(null); }, []); const isGerardo = useMemo(() => access?.role === "director_creativo", [access?.role]); const canManageInternalPricing = useMemo( () => access?.canManageInternalPricing === true, [access?.canManageInternalPricing], ); const canDeleteProjects = useMemo( () => access?.canDeleteProjects === true, [access?.canDeleteProjects], ); const canControlPricingSummary = useMemo( () => access?.canControlPricingSummary === true, [access?.canControlPricingSummary], ); const canManageTariffCatalog = useMemo( () => access?.canManageTariffCatalog === true, [access?.canManageTariffCatalog], ); const canViewTeamHours = useMemo( () => access?.canViewTeamHours === true, [access?.canViewTeamHours], ); const canCreateProjects = useMemo( () => access?.canCreateProjects === true, [access?.canCreateProjects], ); return ( {children} ); } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) { throw new Error("useAuth debe usarse dentro de "); } return ctx; }