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

@@ -0,0 +1,170 @@
import React, { useState, useEffect } from 'react';
import {
Modal, Box, Typography, TextField, Button, CircularProgress, Alert
} from '@mui/material';
import type { CambioParadaDto } from '../../../models/dtos/Distribucion/CambioParadaDto';
import type { CreateCambioParadaDto } from '../../../models/dtos/Distribucion/CreateCambioParadaDto';
import type { UpdateCambioParadaDto } from '../../../models/dtos/Distribucion/UpdateCambioParadaDto';
const modalStyle = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: { xs: '90%', sm: 500 },
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 3,
};
interface CambioParadaFormModalProps {
open: boolean;
onClose: () => void;
// onSubmit se usará para crear o para cerrar una parada.
// El ID del registro solo es relevante al cerrar.
onSubmit: (data: CreateCambioParadaDto | UpdateCambioParadaDto, idRegistroParada?: number) => Promise<void>;
idCanilla: number | null; // Necesario para crear una nueva parada para este canillita
nombreCanilla?: string; // Para mostrar en el título
paradaParaCerrar?: CambioParadaDto | null; // Si se está cerrando una parada existente
errorMessage?: string | null;
clearErrorMessage: () => void;
}
const CambioParadaFormModal: React.FC<CambioParadaFormModalProps> = ({
open,
onClose,
onSubmit,
idCanilla,
nombreCanilla,
paradaParaCerrar, // Si este tiene valor, estamos en modo "Cerrar Vigencia"
errorMessage,
clearErrorMessage
}) => {
const [parada, setParada] = useState(''); // Solo para nueva parada
const [vigenciaD, setVigenciaD] = useState(''); // Solo para nueva parada
const [vigenciaHCierre, setVigenciaHCierre] = useState(''); // Solo para cerrar parada existente
const [loading, setLoading] = useState(false);
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
const isModoCerrar = Boolean(paradaParaCerrar && paradaParaCerrar.idRegistro);
useEffect(() => {
if (open) {
clearErrorMessage();
setLocalErrors({});
if (isModoCerrar && paradaParaCerrar) {
setParada(paradaParaCerrar.parada); // Mostrar la parada que se está cerrando (readonly)
setVigenciaD(paradaParaCerrar.vigenciaD.split('T')[0]); // Mostrar VigenciaD (readonly)
setVigenciaHCierre(''); // Limpiar para nuevo input
} else { // Modo Crear Nueva Parada
setParada('');
setVigenciaD(new Date().toISOString().split('T')[0]); // Default a hoy
setVigenciaHCierre('');
}
}
}, [open, paradaParaCerrar, isModoCerrar, clearErrorMessage]);
const validate = (): boolean => {
const errors: { [key: string]: string | null } = {};
if (isModoCerrar) {
if (!vigenciaHCierre.trim()) errors.vigenciaHCierre = 'La Vigencia Hasta es obligatoria para cerrar.';
else if (!/^\d{4}-\d{2}-\d{2}$/.test(vigenciaHCierre)) errors.vigenciaHCierre = 'Formato de fecha inválido.';
else if (paradaParaCerrar && new Date(vigenciaHCierre) < new Date(paradaParaCerrar.vigenciaD.split('T')[0])) {
errors.vigenciaHCierre = 'Vigencia Hasta no puede ser anterior a la Vigencia Desde de esta parada.';
}
} else { // Modo Crear
if (!idCanilla) errors.general = "ID de Canillita no especificado."; // Error si no hay idCanilla
if (!parada.trim()) errors.parada = 'La dirección de parada es obligatoria.';
else if (parada.trim().length > 150) errors.parada = 'Máximo 150 caracteres.';
if (!vigenciaD.trim()) errors.vigenciaD = 'La Vigencia Desde es obligatoria.';
else if (!/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) errors.vigenciaD = 'Formato de fecha inválido.';
}
setLocalErrors(errors);
return Object.keys(errors).length === 0;
};
const handleInputChange = (fieldName: string) => {
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
if (errorMessage) clearErrorMessage();
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
clearErrorMessage();
if (!validate()) return;
setLoading(true);
try {
if (isModoCerrar && paradaParaCerrar) {
const dataToSubmit: UpdateCambioParadaDto = { vigenciaH: vigenciaHCierre };
await onSubmit(dataToSubmit, paradaParaCerrar.idRegistro);
} else if (idCanilla) { // Modo Crear
const dataToSubmit: CreateCambioParadaDto = { parada, vigenciaD };
// El idCanilla se pasará al servicio desde la página padre o ya está en el contexto del servicio
await onSubmit(dataToSubmit); // El onSubmit de la página se encargará de pasar idCanilla al servicio correcto
}
onClose();
} catch (error: any) {
console.error("Error en submit de CambioParadaFormModal:", error);
} finally {
setLoading(false);
}
};
return (
<Modal open={open} onClose={onClose}>
<Box sx={modalStyle}>
<Typography variant="h6" component="h2" gutterBottom>
{isModoCerrar ? 'Cerrar Vigencia de Parada' : `Nueva Parada para ${nombreCanilla || 'Canillita'}`}
</Typography>
{isModoCerrar && paradaParaCerrar && (
<Box sx={{mb:2, p:1, backgroundColor: 'grey.100', borderRadius:1}}>
<Typography variant="body2">Parada Actual: <strong>{paradaParaCerrar.parada}</strong></Typography>
<Typography variant="body2">Vigente Desde: <strong>{new Date(paradaParaCerrar.vigenciaD + 'T00:00:00Z').toLocaleDateString('es-AR', {timeZone:'UTC'})}</strong></Typography>
</Box>
)}
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
{!isModoCerrar && (
<>
<TextField label="Nueva Dirección de Parada" value={parada} required
onChange={(e) => {setParada(e.target.value); handleInputChange('parada');}}
margin="normal" fullWidth multiline rows={2}
error={!!localErrors.parada} helperText={localErrors.parada || (parada ? `${150 - parada.length} caracteres restantes` : '')}
disabled={loading} autoFocus inputProps={{maxLength: 150}}
/>
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required
onChange={(e) => {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}}
margin="normal" fullWidth
error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
disabled={loading} InputLabelProps={{ shrink: true }}
/>
</>
)}
{isModoCerrar && (
<TextField label="Vigencia Hasta (Cierre)" type="date" value={vigenciaHCierre} required
onChange={(e) => {setVigenciaHCierre(e.target.value); handleInputChange('vigenciaHCierre');}}
margin="normal" fullWidth
error={!!localErrors.vigenciaHCierre} helperText={localErrors.vigenciaHCierre || ''}
disabled={loading} InputLabelProps={{ shrink: true }} autoFocus
/>
)}
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
{localErrors.general && <Alert severity="error" sx={{ mt: 1 }}>{localErrors.general}</Alert>}
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
<Button type="submit" variant="contained" disabled={loading}>
{loading ? <CircularProgress size={24} /> : (isModoCerrar ? 'Confirmar Cierre' : 'Agregar Parada')}
</Button>
</Box>
</Box>
</Box>
</Modal>
);
};
export default CambioParadaFormModal;