Backend:
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:
@@ -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;
|
||||
Reference in New Issue
Block a user