Fix: Menú Reportes
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m12s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m12s
				
			- Fix del menú de reportes que impedía el recorrido del mismo. - Se quita la apertura predeterminada de una opción del menú de Reportes.
This commit is contained in:
		| @@ -1,22 +1,22 @@ | ||||
| // src/hooks/usePermissions.ts | ||||
| import { useAuth } from '../contexts/AuthContext'; | ||||
| import { useCallback } from 'react'; | ||||
|  | ||||
| export const usePermissions = () => { | ||||
|   const { user } = useAuth(); // user aquí es de tipo UserContextData | null | ||||
|   const { user } = useAuth(); | ||||
|  | ||||
|   const tienePermiso = (codigoPermisoRequerido: string): boolean => { | ||||
|     if (!user) { // Si no hay usuario logueado | ||||
|   // Envolvemos la función en useCallback. | ||||
|   // Su dependencia es [user], por lo que la función solo se | ||||
|   // volverá a crear si el objeto 'user' cambia (ej. al iniciar/cerrar sesión). | ||||
|   const tienePermiso = useCallback((codigoPermisoRequerido: string): boolean => { | ||||
|     if (!user) { | ||||
|       return false; | ||||
|     } | ||||
|     if (user.esSuperAdmin) { // SuperAdmin tiene todos los permisos | ||||
|     if (user.esSuperAdmin) { | ||||
|       return true; | ||||
|     } | ||||
|     // Verificar si la lista de permisos del usuario incluye el código requerido | ||||
|     return user.permissions?.includes(codigoPermisoRequerido) ?? false; | ||||
|   }; | ||||
|   }, [user]); | ||||
|  | ||||
|   // También puede exportar el objeto user completo si se necesita en otros lugares | ||||
|   // o propiedades específicas como idPerfil, esSuperAdmin. | ||||
|   return { | ||||
|     tienePermiso, | ||||
|     isSuperAdmin: user?.esSuperAdmin ?? false, | ||||
|   | ||||
| @@ -40,14 +40,12 @@ const ReportesIndexPage: React.FC = () => { | ||||
|   const [isLoadingInitialNavigation, setIsLoadingInitialNavigation] = useState(true); | ||||
|   const { tienePermiso, isSuperAdmin } = usePermissions(); | ||||
|  | ||||
|   // 1. Creamos una lista memoizada de reportes a los que el usuario SÍ tiene acceso. | ||||
|   const accessibleReportModules = useMemo(() => { | ||||
|     return allReportModules.filter(module => | ||||
|       isSuperAdmin || tienePermiso(module.requiredPermission) | ||||
|     ); | ||||
|   }, [isSuperAdmin, tienePermiso]); | ||||
|    | ||||
|   // 2. Creamos una lista de categorías que SÍ tienen al menos un reporte accesible. | ||||
|   const accessibleCategories = useMemo(() => { | ||||
|     const categoriesWithAccess = new Set(accessibleReportModules.map(r => r.category)); | ||||
|     return predefinedCategoryOrder.filter(category => categoriesWithAccess.has(category)); | ||||
| @@ -62,18 +60,23 @@ const ReportesIndexPage: React.FC = () => { | ||||
|       const activeReport = accessibleReportModules.find(module => module.path === subPathSegment); | ||||
|       if (activeReport) { | ||||
|         setExpandedCategory(activeReport.category); | ||||
|       } else { | ||||
|         // Si la URL apunta a un reporte que no es accesible, no expandimos nada | ||||
|         setExpandedCategory(false); | ||||
|       } | ||||
|     } else { | ||||
|       // Si estamos en la página base (/reportes), expandimos la primera categoría disponible. | ||||
|       if (accessibleCategories.length > 0) { | ||||
|         setExpandedCategory(accessibleCategories[0]); | ||||
|       } else { | ||||
|         setExpandedCategory(false); | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     // 4. Navegamos al PRIMER REPORTE ACCESIBLE si estamos en la ruta base. | ||||
|     if (location.pathname === currentBasePath && accessibleReportModules.length > 0 && isLoadingInitialNavigation) { | ||||
|         const firstReportToNavigate = accessibleReportModules[0]; | ||||
|         navigate(firstReportToNavigate.path, { replace: true }); | ||||
|     } | ||||
|      | ||||
|     // No hay navegación automática, solo manejamos el estado de carga. | ||||
|     setIsLoadingInitialNavigation(false); | ||||
|  | ||||
|   }, [location.pathname, navigate, accessibleReportModules, isLoadingInitialNavigation]); | ||||
|   }, [location.pathname, accessibleReportModules, accessibleCategories]); | ||||
|  | ||||
|   const handleCategoryClick = (categoryName: string) => { | ||||
|     setExpandedCategory(prev => (prev === categoryName ? false : categoryName)); | ||||
| @@ -84,12 +87,10 @@ const ReportesIndexPage: React.FC = () => { | ||||
|   }; | ||||
|  | ||||
|   const isReportActive = (reportPath: string) => { | ||||
|     return location.pathname === `/reportes/${reportPath}` || location.pathname.startsWith(`/reportes/${reportPath}/`); | ||||
|     return location.pathname.startsWith(`/reportes/${reportPath}`); | ||||
|   }; | ||||
|  | ||||
|   // Si isLoadingInitialNavigation es true Y estamos en /reportes, mostrar loader | ||||
|   // Esto evita mostrar el loader si se navega directamente a un sub-reporte. | ||||
|   if (isLoadingInitialNavigation && (location.pathname === '/reportes' || location.pathname === '/reportes/')) { | ||||
|   if (isLoadingInitialNavigation) { | ||||
|     return ( | ||||
|         <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}> | ||||
|             <CircularProgress /> | ||||
| @@ -98,48 +99,25 @@ const ReportesIndexPage: React.FC = () => { | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     // Contenedor principal que se adaptará a su padre | ||||
|     // Eliminamos 'height: calc(100vh - 64px)' y cualquier margen/padding que controle el espacio exterior | ||||
|     <Box sx={{ display: 'flex', width: '100%', height: '100%' }}> | ||||
|       {/* Panel Lateral para Navegación */} | ||||
|       <Paper | ||||
|         elevation={0} // Sin elevación para que se sienta más integrado si el fondo es el mismo | ||||
|         square // Bordes rectos | ||||
|         sx={{ | ||||
|           width: { xs: 220, sm: 250, md: 280 }, // Ancho responsivo del panel lateral | ||||
|       <Paper elevation={0} square sx={{ | ||||
|           width: { xs: 220, sm: 250, md: 280 }, | ||||
|           minWidth: { xs: 200, sm: 220 }, | ||||
|           height: '100%', // Ocupa toda la altura del Box padre | ||||
|           height: '100%', | ||||
|           borderRight: (theme) => `1px solid ${theme.palette.divider}`, | ||||
|           overflowY: 'auto', | ||||
|           bgcolor: 'background.paper', // O el color que desees para el menú | ||||
|           // display: 'flex', flexDirection: 'column' // Para que el título y la lista usen el espacio vertical | ||||
|         }} | ||||
|       > | ||||
|         {/* Título del Menú Lateral */} | ||||
|         <Box | ||||
|           sx={{ | ||||
|             p: 1.5, // Padding interno para el título | ||||
|             // borderBottom: (theme) => `1px solid ${theme.palette.divider}`, // Opcional: separador | ||||
|             // position: 'sticky', // Si quieres que el título quede fijo al hacer scroll en la lista | ||||
|             // top: 0, | ||||
|             // zIndex: 1, | ||||
|             // bgcolor: 'background.paper' // Necesario si es sticky y tiene scroll la lista | ||||
|           }} | ||||
|         > | ||||
|             <Typography variant="h6" component="div" sx={{ fontWeight: 'medium', ml:1 /* Pequeño margen para alinear con items */ }}> | ||||
|           bgcolor: 'background.paper', | ||||
|         }}> | ||||
|         <Box sx={{ p: 1.5 }}> | ||||
|             <Typography variant="h6" component="div" sx={{ fontWeight: 'medium', ml:1 }}> | ||||
|             Reportes | ||||
|             </Typography> | ||||
|         </Box> | ||||
|          | ||||
|         {/* 5. Renderizamos el menú usando la lista de categorías ACCESIBLES. */} | ||||
|         {accessibleCategories.length > 0 ? ( | ||||
|         <List component="nav" dense sx={{ pt: 0 }}> | ||||
|           {accessibleCategories.map((category) => { | ||||
|             // 6. Obtenemos los reportes de esta categoría de la lista ACCESIBLE. | ||||
|             const reportsInCategory = accessibleReportModules.filter(r => r.category === category); | ||||
|              | ||||
|             // Ya no es necesario el `if (reportsInCategory.length === 0) return null;` porque `accessibleCategories` ya está filtrado. | ||||
|              | ||||
|             const isExpanded = expandedCategory === category; | ||||
|  | ||||
|             return ( | ||||
| @@ -147,15 +125,10 @@ const ReportesIndexPage: React.FC = () => { | ||||
|                 <ListItemButton | ||||
|                   onClick={() => handleCategoryClick(category)} | ||||
|                   sx={{ | ||||
|                     // py: 1.2, // Ajustar padding vertical de items de categoría | ||||
|                     // backgroundColor: isExpanded ? 'action.selected' : 'transparent', | ||||
|                     borderLeft: isExpanded ? (theme) => `4px solid ${theme.palette.primary.main}` : '4px solid transparent', | ||||
|                     pr: 1, // Menos padding a la derecha para dar espacio al ícono expander | ||||
|                     '&:hover': { | ||||
|                         backgroundColor: 'action.hover' | ||||
|                     } | ||||
|                   }} | ||||
|                 > | ||||
|                     pr: 1, | ||||
|                     '&:hover': { backgroundColor: 'action.hover' } | ||||
|                   }}> | ||||
|                   <ListItemText primary={category} primaryTypographyProps={{ fontWeight: isExpanded ? 'bold' : 'normal' }}/> | ||||
|                   {isExpanded ? <ExpandLess /> : <ExpandMore />} | ||||
|                 </ListItemButton> | ||||
| @@ -167,21 +140,14 @@ const ReportesIndexPage: React.FC = () => { | ||||
|                           selected={isReportActive(report.path)} | ||||
|                           onClick={() => handleReportClick(report.path)} | ||||
|                           sx={{  | ||||
|                             pl: 3.5, // Indentación para los reportes (ajustar si se cambió el padding del título) | ||||
|                             py: 0.8, // Padding vertical de items de reporte | ||||
|                             pl: 3.5, py: 0.8, | ||||
|                             ...(isReportActive(report.path) && { | ||||
|                                 backgroundColor: (theme) => theme.palette.action.selected, // Un color de fondo sutil | ||||
|                                 borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`, // Un borde para el activo | ||||
|                                 '& .MuiListItemText-primary': { | ||||
|                                     fontWeight: 'medium', // O 'bold' | ||||
|                                     // color: 'primary.main' | ||||
|                                 }, | ||||
|                                 backgroundColor: (theme) => theme.palette.action.selected, | ||||
|                                 borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`, | ||||
|                                 '& .MuiListItemText-primary': { fontWeight: 'medium' }, | ||||
|                             }), | ||||
|                             '&:hover': { | ||||
|                                 backgroundColor: (theme) => theme.palette.action.hover | ||||
|                             } | ||||
|                           }} | ||||
|                         > | ||||
|                             '&:hover': { backgroundColor: (theme) => theme.palette.action.hover } | ||||
|                           }}> | ||||
|                           <ListItemText primary={report.label} primaryTypographyProps={{ variant: 'body2' }}/> | ||||
|                         </ListItemButton> | ||||
|                       ))} | ||||
| @@ -196,26 +162,17 @@ const ReportesIndexPage: React.FC = () => { | ||||
|         )} | ||||
|       </Paper> | ||||
|  | ||||
|       {/* Área Principal para el Contenido del Reporte */} | ||||
|       <Box | ||||
|         component="main" | ||||
|         sx={{ | ||||
|           flexGrow: 1, // Ocupa el espacio restante | ||||
|           p: { xs: 1, sm: 2, md: 3 }, // Padding interno para el contenido, responsivo | ||||
|           overflowY: 'auto', | ||||
|           height: '100%', // Ocupa toda la altura del Box padre | ||||
|           bgcolor: 'grey.100' // Un color de fondo diferente para distinguir el área de contenido | ||||
|         }} | ||||
|       > | ||||
|         {/* El Outlet renderiza el componente del reporte específico */} | ||||
|         {(!location.pathname.startsWith('/reportes/') || !allReportModules.some(r => isReportActive(r.path))) && location.pathname !== '/reportes/' && location.pathname !== '/reportes' && !isLoadingInitialNavigation && ( | ||||
|             <Typography sx={{p:2, textAlign:'center', mt: 4, color: 'text.secondary'}}> | ||||
|                 El reporte solicitado no existe o la ruta no es válida. | ||||
|             </Typography> | ||||
|         )} | ||||
|         {(location.pathname === '/reportes/' || location.pathname === '/reportes') && !allReportModules.some(r => isReportActive(r.path)) && !isLoadingInitialNavigation && ( | ||||
|       <Box component="main" sx={{ | ||||
|           flexGrow: 1, p: { xs: 1, sm: 2, md: 3 }, | ||||
|           overflowY: 'auto', height: '100%', bgcolor: 'grey.100' | ||||
|         }}> | ||||
|         {/* Lógica para mostrar el mensaje de bienvenida */} | ||||
|         {location.pathname === '/reportes' && !isLoadingInitialNavigation && ( | ||||
|              <Typography sx={{p:2, textAlign:'center', mt: 4, color: 'text.secondary'}}> | ||||
|                 {allReportModules.length > 0 ? "Seleccione una categoría y un reporte del menú lateral." : "No hay reportes configurados."} | ||||
|                 {accessibleReportModules.length > 0  | ||||
|                     ? "Seleccione una categoría y un reporte del menú lateral."  | ||||
|                     : "No tiene acceso a ningún reporte." | ||||
|                 } | ||||
|             </Typography> | ||||
|         )} | ||||
|         <Outlet /> | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| // src/routes/AppRoutes.tsx | ||||
| import React, { type JSX } from 'react'; | ||||
| import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom'; | ||||
| import LoginPage from '../pages/LoginPage'; | ||||
| import HomePage from '../pages/HomePage'; | ||||
| import { useAuth } from '../contexts/AuthContext'; | ||||
| import MainLayout from '../layouts/MainLayout'; | ||||
| import { Typography } from '@mui/material'; | ||||
| import SectionProtectedRoute from './SectionProtectedRoute'; | ||||
|  | ||||
| // Distribución | ||||
| @@ -267,7 +265,6 @@ const AppRoutes = () => { | ||||
|                 <ReportesIndexPage /> | ||||
|               </SectionProtectedRoute>} | ||||
|           > | ||||
|             <Route index element={<Typography sx={{ p: 2 }}>Seleccione un reporte del menú lateral.</Typography>} /> {/* Placeholder */} | ||||
|             <Route path="existencia-papel" element={<ReporteExistenciaPapelPage />} /> | ||||
|             <Route path="movimiento-bobinas" element={<ReporteMovimientoBobinasPage />} /> | ||||
|             <Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} /> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user