Implementación AnomalIA - Fix de dropdowns y permisos.
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m17s

This commit is contained in:
2025-06-30 15:26:14 -03:00
parent 95aa09d62a
commit c96d259892
59 changed files with 1430 additions and 337 deletions

View File

@@ -1,8 +1,7 @@
import React, { createContext, useState, useContext, type ReactNode, useEffect } from 'react';
import type { LoginResponseDto } from '../models/dtos/Usuarios/LoginResponseDto';
import React, { createContext, useState, useContext, type ReactNode, useEffect, useCallback } from 'react';
import { jwtDecode } from 'jwt-decode';
import { getAlertas, marcarAlertaLeida, marcarGrupoComoLeido, type AlertaGenericaDto } from '../services/Anomalia/alertaService';
// Interfaz para los datos del usuario que guardaremos en el contexto
export interface UserContextData {
userId: number;
username: string;
@@ -11,33 +10,37 @@ export interface UserContextData {
debeCambiarClave: boolean;
perfil: string;
idPerfil: number;
permissions: string[]; // Guardamos los codAcc
permissions: string[];
}
// Interfaz para el payload decodificado del JWT
interface DecodedJwtPayload {
sub: string; // User ID (viene como string)
name: string; // Username
given_name?: string; // Nombre (estándar, pero verifica tu token)
family_name?: string; // Apellido (estándar, pero verifica tu token)
role: string | string[]; // Puede ser uno o varios roles
sub: string;
name: string;
given_name?: string;
family_name?: string;
role: string | string[];
perfil: string;
idPerfil: string; // (viene como string)
debeCambiarClave: string; // (viene como string "True" o "False")
permission?: string | string[]; // Nuestros claims de permiso (codAcc)
[key: string]: any; // Para otros claims
idPerfil: string;
debeCambiarClave: string;
permission?: string | string[];
[key: string]: any;
}
interface AuthContextType {
isAuthenticated: boolean;
user: UserContextData | null; // Usar el tipo extendido
user: UserContextData | null;
token: string | null;
isLoading: boolean;
alertas: AlertaGenericaDto[];
showForcedPasswordChangeModal: boolean;
isPasswordChangeForced: boolean;
marcarAlertaComoLeida: (idAlerta: number) => Promise<void>;
marcarGrupoDeAlertasLeido: (tipoAlerta: string, idEntidad: number) => Promise<void>;
setShowForcedPasswordChangeModal: (show: boolean) => void;
passwordChangeCompleted: () => void;
login: (apiLoginResponse: LoginResponseDto) => void; // Recibe el DTO de la API
login: (apiLoginResponse: any) => void; // DTO no definido aquí, usamos any
logout: () => void;
}
@@ -50,24 +53,57 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
const [isLoading, setIsLoading] = useState<boolean>(true);
const [showForcedPasswordChangeModal, setShowForcedPasswordChangeModal] = useState<boolean>(false);
const [isPasswordChangeForced, setIsPasswordChangeForced] = useState<boolean>(false);
const [alertas, setAlertas] = useState<AlertaGenericaDto[]>([]);
const processTokenAndSetUser = (jwtToken: string) => {
const fetchAlertas = useCallback(async (currentUser: UserContextData | null) => {
if (currentUser && (currentUser.esSuperAdmin || currentUser.permissions.includes('AL001'))) {
try {
const data = await getAlertas();
setAlertas(data || []);
} catch (error) {
console.error("Error al obtener alertas en AuthContext:", error);
setAlertas([]);
}
} else {
setAlertas([]);
}
}, []);
const marcarAlertaComoLeida = async (idAlerta: number) => {
try {
await marcarAlertaLeida(idAlerta);
await fetchAlertas(user); // Refresca el estado global
} catch (error) {
console.error("Error al marcar alerta como leída:", error);
}
};
const marcarGrupoDeAlertasLeido = async (tipoAlerta: string, idEntidad: number) => {
try {
await marcarGrupoComoLeido({ tipoAlerta, idEntidad });
await fetchAlertas(user); // Refresca el estado global
} catch (error) {
console.error(`Error al marcar grupo ${tipoAlerta}/${idEntidad} como leído:`, error);
}
};
const logout = useCallback(() => {
localStorage.removeItem('authToken');
setToken(null);
setUser(null);
setIsAuthenticated(false);
setShowForcedPasswordChangeModal(false);
setIsPasswordChangeForced(false);
setAlertas([]);
}, []);
const processTokenAndSetUser = useCallback((jwtToken: string) => {
try {
const decodedToken = jwtDecode<DecodedJwtPayload>(jwtToken);
// Verificar expiración (opcional, pero buena práctica aquí también)
const currentTime = Date.now() / 1000;
if (decodedToken.exp && decodedToken.exp < currentTime) {
console.warn("Token expirado al procesar.");
logout(); // Llama a logout que limpia todo
return;
logout(); return;
}
let permissions: string[] = [];
if (decodedToken.permission) {
permissions = Array.isArray(decodedToken.permission) ? decodedToken.permission : [decodedToken.permission];
}
const userForContext: UserContextData = {
userId: parseInt(decodedToken.sub, 10),
username: decodedToken.name,
@@ -75,27 +111,23 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
esSuperAdmin: decodedToken.role === "SuperAdmin" || (Array.isArray(decodedToken.role) && decodedToken.role.includes("SuperAdmin")),
debeCambiarClave: decodedToken.debeCambiarClave?.toLowerCase() === 'true',
idPerfil: decodedToken.idPerfil ? parseInt(decodedToken.idPerfil, 10) : 0,
permissions: permissions,
perfil: decodedToken.perfil || 'Usuario' // Asignar un valor por defecto si no existe
permissions: Array.isArray(decodedToken.permission) ? decodedToken.permission : (decodedToken.permission ? [decodedToken.permission] : []),
perfil: decodedToken.perfil || 'Usuario'
};
setToken(jwtToken);
setUser(userForContext);
setIsAuthenticated(true);
localStorage.setItem('authToken', jwtToken);
localStorage.setItem('authUser', JSON.stringify(userForContext)); // Guardar el usuario procesado
// Lógica para el modal de cambio de clave
if (userForContext.debeCambiarClave) {
setShowForcedPasswordChangeModal(true);
setIsPasswordChangeForced(true);
}
} catch (error) {
console.error("Error al decodificar o procesar token:", error);
logout(); // Limpiar estado si el token es inválido
console.error("Error al decodificar token:", error);
logout();
}
};
}, [logout]);
useEffect(() => {
setIsLoading(true);
@@ -104,20 +136,18 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
processTokenAndSetUser(storedToken);
}
setIsLoading(false);
}, []);
}, [processTokenAndSetUser]);
const login = (apiLoginResponse: LoginResponseDto) => {
processTokenAndSetUser(apiLoginResponse.token); // Procesar el token recibido
};
useEffect(() => {
if (user && isAuthenticated) {
fetchAlertas(user);
const intervalId = setInterval(() => fetchAlertas(user), 300000); // Refresca cada 5 mins
return () => clearInterval(intervalId);
}
}, [user, isAuthenticated, fetchAlertas]);
const logout = () => {
localStorage.removeItem('authToken');
localStorage.removeItem('authUser');
setToken(null);
setUser(null);
setIsAuthenticated(false);
setShowForcedPasswordChangeModal(false);
setIsPasswordChangeForced(false);
const login = (apiLoginResponse: any) => {
processTokenAndSetUser(apiLoginResponse.token);
};
const passwordChangeCompleted = () => {
@@ -138,6 +168,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
<AuthContext.Provider value={{
isAuthenticated, user, token, isLoading,
showForcedPasswordChangeModal, isPasswordChangeForced,
alertas, marcarAlertaComoLeida, marcarGrupoDeAlertasLeido,
setShowForcedPasswordChangeModal, passwordChangeCompleted,
login, logout
}}>