This repository has been archived on 2026-01-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Acad-IA/src/auth/supabase.tsx
T
Guillermo.Arrieta ce2cd6b397 Eliminada dependencia de llave de servicio para el manejo de usuarios y eliminado el hard-code de los roles
supabase.tsx
Se añadieron los roles al contexto de autenticación. Se modificó la interfaz de UserClaims que consiste en la información que se obtiene de los usuarios. Se obtienen los roles desde la base de datos.

_authenticated.tsx
Ya todos pueden ver el enlace a la página de facultades.

login.tsx
Se movió el enlace de '¿Olvidaste tu contraseña?' a después del input de la contraseña, para mejorar la usabilidad.

usuarios.tsx
- La obtención de los usuarios ahora se hace a con el cliente de llave anónima de supabase y se obtiene de tablas en el esquema public a través de una función de PostgreSQL.
- La información de los roles se obtiene del contexto de autenticación para mostrarla en la página.
- El RolePill se movió a dentro del componente para poder usar la información del contexto.
- Se añadieron validaciones para poder crear un usuario.
- Se muestra la información para editar los usuarios y se actualiza en la BDD con una función de PostgreSQL.
2025-10-20 17:09:14 -06:00

163 lines
4.2 KiB
TypeScript

import { createClient, type User, type Session } from '@supabase/supabase-js'
import { createContext, useContext, useEffect, useState } from 'react'
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
export interface SupabaseAuthState {
isAuthenticated: boolean
user: User | null
claims: UserClaims | null
roles: RolCatalogo[] | null
login: (email: string, password: string) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
}
export interface RolCatalogo {
id: string
nombre: string
icono: string
nombre_clase: string
label: string
}
export type Role = string;
export type UserClaims = {
id: string | null
clave?: string
nombre: string
apellidos: string
title?: string
avatar?: string | null
carrera_id?: string | null
facultad_id?: string | null
facultad_color?: string | null // 🎨 NEW
role: Role
}
const SupabaseAuthContext = createContext<SupabaseAuthState | undefined>(undefined)
export function SupabaseAuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [claims, setClaims] = useState<UserClaims | null>(null)
const [roles, setRoles] = useState<RolCatalogo[] | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Función para manejar la sesión
const handleSession = async (session: Session | null) => {
const u = session?.user ?? null
setUser(u)
setIsAuthenticated(!!u)
setClaims(await buildClaims(session))
setIsLoading(false)
}
// Carga inicial
supabase.auth.getSession().then(({ data: { session } }) => {
handleSession(session)
})
// Carga roles catálogo
fetchRoles().then(fetchedRoles => {
setRoles(fetchedRoles);
});
// Suscripción a cambios de sesión
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
handleSession(session)
})
return () => subscription.unsubscribe()
}, [])
const login = async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error?.code === 'invalid_credentials') throw new Error('Credenciales inválidas')
else if (error) throw error
}
const logout = async () => {
const { error } = await supabase.auth.signOut()
if (error) throw error
location.href = "/login"
}
return (
<SupabaseAuthContext.Provider
value={{ isAuthenticated, user, claims, roles, login, logout, isLoading }}
>
{children}
</SupabaseAuthContext.Provider>
)
}
export function useSupabaseAuth() {
const context = useContext(SupabaseAuthContext)
if (context === undefined) {
throw new Error('useSupabaseAuth must be used within SupabaseAuthProvider')
}
return context
}
/* ===================== *
* Helpers
* ===================== */
// Obtiene los claims del usuario desde la base de datos a partir de una función en la BDD
async function buildClaims(session: Session | null): Promise<UserClaims | null> {
// Validar sesión
if (!session || !session.user) {
console.warn('No session or user found');
return null;
}
const u = session.user;
try{
const result = await supabase.rpc('obtener_claims_usuario', {
p_user_id: u.id,
});
const data: UserClaims[] | null = result.data;
const error = result.error;
if (error) {
console.error('Error al obtener la información:', error);
throw new Error('Error al obtener la información del usuario');
}
console.log(data);
if (!data || data.length === 0) {
console.warn('No se encontró información para el usuario');
return null;
}
return {
...data[0],
id: null
};
} catch (e) {
console.error('Error inesperado:', e);
return null;
}
}
async function fetchRoles(): Promise<RolCatalogo[]> {
const { data, error } = await supabase
.from("roles_catalogo")
.select("id, nombre, icono, nombre_clase, label");
if (error) {
console.error("Error al obtener los roles:", error.message);
return [];
}
return data || [];
}