Diseño de un AuditoriaController con un patrón para añadir endpoints de historial para diferentes entidades.
Implementación de la lógica de servicio y repositorio para obtener datos de las tablas _H para:
Usuarios (gral_Usuarios_H)
Pagos de Distribuidores (cue_PagosDistribuidor_H)
Notas de Crédito/Débito (cue_CreditosDebitos_H)
Entradas/Salidas de Distribuidores (dist_EntradasSalidas_H)
Entradas/Salidas de Canillitas (dist_EntradasSalidasCanillas_H)
Novedades de Canillitas (dist_dtNovedadesCanillas_H)
Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial)
Tipos de Pago (cue_dtTipopago_H)
Canillitas (Maestro) (dist_dtCanillas_H)
Distribuidores (Maestro) (dist_dtDistribuidores_H)
Empresas (Maestro) (dist_dtEmpresas_H)
DTOs específicos para cada tipo de historial, incluyendo NombreUsuarioModifico.
Frontend:
Servicio auditoriaService.ts con métodos para llamar a cada endpoint de historial.
Página AuditoriaGeneralPage.tsx con:
Selector de "Tipo de Entidad a Auditar".
Filtros comunes (Fechas, Usuario Modificador, Tipo de Modificación, ID Entidad).
Un DataGrid que muestra las columnas dinámicamente según el tipo de entidad seleccionada.
Lógica para cargar los datos correspondientes.
DTOs de historial en TypeScript.
Actualizaciones en AppRoutes.tsx y MainLayout.tsx para la nueva sección de Auditoría (restringida a SuperAdmin).
This commit is contained in:
2025-06-09 19:37:07 -03:00
parent 35e24ab7d2
commit 437b1e8864
98 changed files with 3683 additions and 325 deletions

View File

@@ -12,7 +12,7 @@ import LogoutIcon from '@mui/icons-material/Logout';
import { useAuth } from '../contexts/AuthContext';
import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal';
import { useNavigate, useLocation } from 'react-router-dom';
import { usePermissions } from '../hooks/usePermissions'; // <<--- AÑADIR ESTA LÍNEA
import { usePermissions } from '../hooks/usePermissions';
interface MainLayoutProps {
children: ReactNode;
@@ -27,6 +27,7 @@ const allAppModules = [
{ label: 'Reportes', path: '/reportes', requiredPermission: 'SS004' },
{ label: 'Radios', path: '/radios', requiredPermission: 'SS005' },
{ label: 'Usuarios', path: '/usuarios', requiredPermission: 'SS006' },
{ label: 'Auditoría', path: '/auditoria', requiredPermission: null, onlySuperAdmin: true },
];
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
@@ -47,33 +48,41 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const [selectedTab, setSelectedTab] = useState<number | false>(false);
const [anchorElUserMenu, setAnchorElUserMenu] = useState<null | HTMLElement>(null);
// --- INICIO DE CAMBIO: Filtrar módulos basados en permisos ---
const accessibleModules = useMemo(() => {
if (!isAuthenticated) return []; // Si no está autenticado, ningún módulo excepto quizás login (que no está aquí)
if (!isAuthenticated) return [];
return allAppModules.filter(module => {
if (module.requiredPermission === null) return true; // Inicio siempre accesible
if (module.onlySuperAdmin) { // Si el módulo es solo para SuperAdmin
return isSuperAdmin;
}
if (module.requiredPermission === null) return true;
return isSuperAdmin || tienePermiso(module.requiredPermission);
});
}, [isAuthenticated, isSuperAdmin, tienePermiso]);
// --- FIN DE CAMBIO ---
useEffect(() => {
// --- INICIO DE CAMBIO: Usar accessibleModules para encontrar el tab ---
const currentModulePath = accessibleModules.findIndex(module =>
location.pathname === module.path || (module.path !== '/' && location.pathname.startsWith(module.path + '/'))
);
if (currentModulePath !== -1) {
setSelectedTab(currentModulePath);
} else if (location.pathname === '/') {
// Asegurar que Inicio se seleccione si es accesible
const inicioIndex = accessibleModules.findIndex(m => m.path === '/');
if (inicioIndex !== -1) setSelectedTab(inicioIndex);
else setSelectedTab(false);
} else {
setSelectedTab(false);
// Si la ruta actual no coincide con ningún módulo accesible,
// y no es la raíz, podría ser una subruta de un módulo no accesible.
// O podría ser una ruta inválida.
// Podríamos intentar encontrar el módulo base y ver si es accesible.
const basePath = "/" + (location.pathname.split('/')[1] || "");
const parentModuleIndex = accessibleModules.findIndex(m => m.path === basePath);
if (parentModuleIndex !== -1) {
setSelectedTab(parentModuleIndex);
} else {
setSelectedTab(false);
}
}
// --- FIN DE CAMBIO ---
}, [location.pathname, accessibleModules]); // << CAMBIO: dependencia a accessibleModules
}, [location.pathname, accessibleModules]);
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUserMenu(event.currentTarget);
@@ -100,25 +109,20 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
if (isPasswordChangeForced) {
logout(); // Si es forzado y cancela/falla, desloguear
} else {
setShowForcedPasswordChangeModal(false); // Si no es forzado, solo cerrar modal
setShowForcedPasswordChangeModal(false); // Si no es forzado, solo cerrar modal
}
}
};
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
// --- INICIO DE CAMBIO: Navegar usando accessibleModules ---
if (accessibleModules[newValue]) {
setSelectedTab(newValue);
navigate(accessibleModules[newValue].path);
}
// --- FIN DE CAMBIO ---
};
const isReportesModule = location.pathname.startsWith('/reportes');
if (showForcedPasswordChangeModal && isPasswordChangeForced) {
// ... (sin cambios)
return (
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
<ChangePasswordModal
open={showForcedPasswordChangeModal}
@@ -151,8 +155,7 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
Sistema de Gestión - El Día
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{/* ... (Menú de usuario sin cambios) ... */}
{user && (
{user && (
<Typography sx={{ mr: 2, display: { xs: 'none', sm: 'block' } }} >
Hola, {user.nombreCompleto}
</Typography>
@@ -168,7 +171,7 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
onClick={handleOpenUserMenu}
color="inherit"
>
<AccountCircle sx={{ fontSize: 36 }} />
<AccountCircle sx={{ fontSize: 36 }} />
</IconButton>
<Menu
id="menu-appbar"
@@ -202,7 +205,6 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
)}
</Box>
</Toolbar>
{/* --- INICIO DE CAMBIO: Renderizar Tabs solo si hay módulos accesibles y está autenticado --- */}
{isAuthenticated && accessibleModules.length > 0 && (
<Paper square elevation={0} >
<Tabs
@@ -225,22 +227,20 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
}
}}
>
{/* Mapear sobre accessibleModules en lugar de allAppModules */}
{accessibleModules.map((module) => (
<Tab key={module.path} label={module.label} />
))}
</Tabs>
</Paper>
)}
{/* --- FIN DE CAMBIO --- */}
</AppBar>
<Box
component="main"
sx={{ /* ... (estilos sin cambios) ... */
sx={{
flexGrow: 1,
py: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 },
px: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 },
py: location.pathname.startsWith('/reportes') ? 0 : { xs: 1.5, sm: 2, md: 2.5 },
px: location.pathname.startsWith('/reportes') ? 0 : { xs: 1.5, sm: 2, md: 2.5 },
display: 'flex',
flexDirection: 'column'
}}
@@ -248,9 +248,9 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
{children}
</Box>
<Box component="footer" sx={{ /* ... (estilos sin cambios) ... */
p: 1, backgroundColor: 'grey.200', color: 'text.secondary',
textAlign: 'left', borderTop: (theme) => `1px solid ${theme.palette.divider}`
<Box component="footer" sx={{
p: 1, backgroundColor: 'grey.200', color: 'text.secondary',
textAlign: 'left', borderTop: (theme) => `1px solid ${theme.palette.divider}`
}}>
<Typography variant="caption">
Usuario: {user?.username} | Acceso: {user?.esSuperAdmin ? 'Super Administrador' : (user?.perfil || `ID ${user?.idPerfil}`)}
@@ -265,5 +265,4 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
</Box>
);
};
export default MainLayout;