Ya perdí el hilo de los cambios pero ahi van.

This commit is contained in:
2025-05-23 15:47:39 -03:00
parent e7e185a9cb
commit 3c1fe15b1f
141 changed files with 9764 additions and 190 deletions

View File

@@ -0,0 +1,256 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
Box, Typography, TextField, Button, Paper,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
CircularProgress, Alert, TablePagination, Tooltip, Autocomplete,
MenuItem,
FormControl,
InputLabel,
Select
} from '@mui/material';
import type { UsuarioDto } from '../../../models/dtos/Usuarios/UsuarioDto';
import FilterListIcon from '@mui/icons-material/FilterList';
import usuarioService from '../../../services/Usuarios/usuarioService';
import type { UsuarioHistorialDto } from '../../../models/dtos/Usuarios/Auditoria/UsuarioHistorialDto';
import { usePermissions } from '../../../hooks/usePermissions';
const GestionarAuditoriaUsuariosPage: React.FC = () => {
const [historial, setHistorial] = useState<UsuarioHistorialDto[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Filtros
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
const [filtroIdUsuarioAfectado, setFiltroIdUsuarioAfectado] = useState<UsuarioDto | null>(null);
const [filtroIdUsuarioModifico, setFiltroIdUsuarioModifico] = useState<UsuarioDto | null>(null);
const [filtroTipoMod, setFiltroTipoMod] = useState('');
const [usuariosParaDropdown, setUsuariosParaDropdown] = useState<UsuarioDto[]>([]);
const [tiposModificacionParaDropdown, setTiposModificacionParaDropdown] = useState<string[]>([]);
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const { tienePermiso, isSuperAdmin } = usePermissions();
const puedeVerAuditoria = isSuperAdmin || tienePermiso("AU001"); // O el permiso que definas
const fetchDropdownData = useCallback(async () => {
if (!puedeVerAuditoria) return;
setLoadingDropdowns(true);
try {
const usuariosData = await usuarioService.getAllUsuarios(); // Asumiendo que tienes este método
setUsuariosParaDropdown(usuariosData);
// Opción B para Tipos de Modificación (desde backend)
// const tiposModData = await apiClient.get<string[]>('/auditoria/tipos-modificacion'); // Ajusta el endpoint si lo creas
// setTiposModificacionParaDropdown(tiposModData.data);
// Opción A (Hardcodeado en Frontend - más simple para empezar)
setTiposModificacionParaDropdown([
"Creado", "Insertada",
"Actualizado", "Modificada",
"Eliminado", "Eliminada",
"Baja", "Alta",
"Liquidada",
"Eliminado (Cascada)"
].sort());
} catch (err) {
console.error("Error al cargar datos para dropdowns de auditoría:", err);
setError("Error al cargar opciones de filtro."); // O un error más específico
} finally {
setLoadingDropdowns(false);
}
}, [puedeVerAuditoria]);
const cargarHistorial = useCallback(async () => {
if (!puedeVerAuditoria) {
setError("No tiene permiso para ver el historial de auditoría.");
setLoading(false);
return;
}
setLoading(true); setError(null);
try {
let data;
// Ahora usamos los IDs de los objetos UsuarioDto seleccionados
const idAfectado = filtroIdUsuarioAfectado ? filtroIdUsuarioAfectado.id : null;
const idModifico = filtroIdUsuarioModifico ? filtroIdUsuarioModifico.id : null;
if (idAfectado) { // Si se seleccionó un usuario afectado específico
data = await usuarioService.getHistorialDeUsuario(idAfectado, {
fechaDesde: filtroFechaDesde || undefined,
fechaHasta: filtroFechaHasta || undefined,
});
} else { // Sino, buscar en todo el historial con los otros filtros
data = await usuarioService.getTodoElHistorialDeUsuarios({
fechaDesde: filtroFechaDesde || undefined,
fechaHasta: filtroFechaHasta || undefined,
idUsuarioModifico: idModifico || undefined,
tipoModificacion: filtroTipoMod || undefined,
});
}
setHistorial(data);
} catch (err: any) {
console.error(err);
setError(err.response?.data?.message || 'Error al cargar el historial de usuarios.');
} finally {
setLoading(false);
}
}, [puedeVerAuditoria, filtroFechaDesde, filtroFechaHasta, filtroIdUsuarioAfectado, filtroIdUsuarioModifico, filtroTipoMod]);
useEffect(() => {
fetchDropdownData();
}, [fetchDropdownData]); // Cargar al montar
useEffect(() => {
// Cargar historial cuando los filtros cambian o al inicio si puedeVerAuditoria está listo
if (puedeVerAuditoria) {
cargarHistorial();
}
}, [cargarHistorial, puedeVerAuditoria]); // Quitar dependencias de filtro directo para evitar llamadas múltiples, handleFiltrar se encarga.
const handleFiltrar = () => {
setPage(0);
cargarHistorial(); // cargarHistorial ahora usa los estados de filtro directamente
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString('es-AR', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
};
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
};
const displayData = historial.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
if (!loading && !puedeVerAuditoria) {
return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
}
return (
<Box sx={{ p: 2 }}>
<Typography variant="h4" gutterBottom>Auditoría de Usuarios</Typography>
<Paper sx={{ p: 2, mb: 2 }}>
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
<Autocomplete
options={usuariosParaDropdown}
getOptionLabel={(option) => `${option.nombre} ${option.apellido} (${option.user})`}
value={filtroIdUsuarioAfectado}
onChange={(_event, newValue) => {
setFiltroIdUsuarioAfectado(newValue);
}}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params) => (
<TextField {...params} label="Usuario Afectado (Opcional)" size="small" sx={{ minWidth: 250 }} />
)}
loading={loadingDropdowns}
disabled={loadingDropdowns}
sx={{ flexGrow: 1 }}
/>
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} />
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} />
<Autocomplete
options={usuariosParaDropdown}
getOptionLabel={(option) => `${option.nombre} ${option.apellido} (${option.user})`}
value={filtroIdUsuarioModifico}
onChange={(_event, newValue) => {
setFiltroIdUsuarioModifico(newValue);
}}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params) => (
<TextField {...params} label="Usuario Modificó (Opcional)" size="small" sx={{ minWidth: 250 }} />
)}
loading={loadingDropdowns}
disabled={loadingDropdowns}
sx={{ flexGrow: 1 }}
/>
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingDropdowns}>
<InputLabel>Tipo Modificación</InputLabel>
<Select
value={filtroTipoMod}
label="Tipo Modificación"
onChange={(e) => setFiltroTipoMod(e.target.value as string)}
>
<MenuItem value=""><em>Todos</em></MenuItem>
{tiposModificacionParaDropdown.map((tipo) => (
<MenuItem key={tipo} value={tipo}>{tipo}</MenuItem>
))}
</Select>
</FormControl>
<Button variant="outlined" onClick={handleFiltrar} size="small" disabled={loading || loadingDropdowns}>Filtrar</Button>
</Box>
</Paper>
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && (
<TableContainer component={Paper}>
<Table size="small">
<TableHead>
<TableRow sx={{ backgroundColor: 'action.hover' }}>
<TableCell>Fecha</TableCell>
<TableCell>Usuario Afectado (ID)</TableCell>
<TableCell>Username Afectado</TableCell>
<TableCell>Acción</TableCell>
<TableCell>Modificado Por (ID)</TableCell>
<TableCell>Nombre Modificador</TableCell>
<TableCell>Detalles (Simplificado)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{displayData.length === 0 ? (
<TableRow><TableCell colSpan={7} align="center">No se encontraron registros de historial.</TableCell></TableRow>
) : (
displayData.map((h) => (
<TableRow key={h.idHist} hover>
<TableCell><Tooltip title={h.fechaModificacion}><Box>{formatDate(h.fechaModificacion)}</Box></Tooltip></TableCell>
<TableCell>{h.idUsuarioAfectado}</TableCell>
<TableCell>{h.userAfectado}</TableCell>
<TableCell>{h.tipoModificacion}</TableCell>
<TableCell>{h.idUsuarioModifico}</TableCell>
<TableCell>{h.nombreUsuarioModifico}</TableCell>
<TableCell>
<Tooltip title={
`User: ${h.userAnt || '-'} -> ${h.userNvo}\n` +
`Nombre: ${h.nombreAnt || '-'} -> ${h.nombreNvo}\n` +
`Apellido: ${h.apellidoAnt || '-'} -> ${h.apellidoNvo}\n` +
`Habilitado: ${h.habilitadaAnt ?? '-'} -> ${h.habilitadaNva}\n` +
`SupAdmin: ${h.supAdminAnt ?? '-'} -> ${h.supAdminNvo}\n` +
`Perfil: ${h.nombrePerfilAnt || h.idPerfilAnt || '-'} -> ${h.nombrePerfilNvo} (${h.idPerfilNvo})\n` +
`CambiaClave: ${h.debeCambiarClaveAnt ?? '-'} -> ${h.debeCambiarClaveNva}`
}>
<Box sx={{ maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{`User: ${h.userAnt?.substring(0, 5)}..->${h.userNvo.substring(0, 5)}.., Nom: ${h.nombreAnt?.substring(0, 3)}..->${h.nombreNvo.substring(0, 3)}..`}
</Box>
</Tooltip>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[10, 25, 50, 100]} component="div" count={historial.length}
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
/>
</TableContainer>
)}
</Box>
);
};
export default GestionarAuditoriaUsuariosPage;

View File

@@ -6,6 +6,7 @@ const usuariosSubModules = [
{ label: 'Perfiles', path: 'perfiles' },
{ label: 'Permisos (Definición)', path: 'permisos' },
{ label: 'Usuarios', path: 'gestion-usuarios' },
{ label: 'Auditoría Usuarios', path: 'auditoria-usuarios' },
];
const UsuariosIndexPage: React.FC = () => {