Fix: Fechas de Estado Bobinas
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 14m8s

- Las fechas de estado de las bobinas no pueden ser anterior a la fecha de remito (ingreso).
This commit is contained in:
2026-02-11 14:52:58 -03:00
parent fc27b4b43e
commit 9201d7222b
2 changed files with 266 additions and 243 deletions

View File

@@ -199,13 +199,21 @@ namespace GestionIntegral.Api.Services.Impresion
using var transaction = connection.BeginTransaction(); using var transaction = connection.BeginTransaction();
try try
{ {
var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de la transacción var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina);
if (bobina == null) if (bobina == null)
{ {
try { transaction.Rollback(); } catch { } try { transaction.Rollback(); } catch { }
return (false, "Bobina no encontrada."); return (false, "Bobina no encontrada.");
} }
// Comparamos solo las fechas (sin hora) para evitar problemas de precisión.
if (cambiarEstadoDto.FechaCambioEstado.Date < bobina.FechaRemito.Date)
{
try { transaction.Rollback(); } catch { }
return (false, $"Error de integridad: La fecha del nuevo estado ({cambiarEstadoDto.FechaCambioEstado:dd/MM/yyyy}) " +
$"no puede ser anterior a la fecha de ingreso por remito ({bobina.FechaRemito:dd/MM/yyyy}).");
}
var nuevoEstado = await _estadoBobinaRepository.GetByIdAsync(cambiarEstadoDto.NuevoEstadoId); var nuevoEstado = await _estadoBobinaRepository.GetByIdAsync(cambiarEstadoDto.NuevoEstadoId);
if (nuevoEstado == null) if (nuevoEstado == null)
{ {

View File

@@ -7,7 +7,6 @@ import {
import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto'; import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto';
import type { CambiarEstadoBobinaDto } from '../../../models/dtos/Impresion/CambiarEstadoBobinaDto'; import type { CambiarEstadoBobinaDto } from '../../../models/dtos/Impresion/CambiarEstadoBobinaDto';
import type { EstadoBobinaDto } from '../../../models/dtos/Impresion/EstadoBobinaDto'; import type { EstadoBobinaDto } from '../../../models/dtos/Impresion/EstadoBobinaDto';
// --- CAMBIO: Importar PublicacionDropdownDto ---
import type { PublicacionDropdownDto } from '../../../models/dtos/Distribucion/PublicacionDropdownDto'; import type { PublicacionDropdownDto } from '../../../models/dtos/Distribucion/PublicacionDropdownDto';
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto'; import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto';
import estadoBobinaService from '../../../services/Impresion/estadoBobinaService'; import estadoBobinaService from '../../../services/Impresion/estadoBobinaService';
@@ -56,7 +55,6 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
const [fechaCambioEstado, setFechaCambioEstado] = useState(''); const [fechaCambioEstado, setFechaCambioEstado] = useState('');
const [estadosDisponibles, setEstadosDisponibles] = useState<EstadoBobinaDto[]>([]); const [estadosDisponibles, setEstadosDisponibles] = useState<EstadoBobinaDto[]>([]);
// --- CAMBIO: Usar PublicacionDropdownDto para el estado ---
const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDropdownDto[]>([]); const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDropdownDto[]>([]);
const [seccionesDisponibles, setSeccionesDisponibles] = useState<PubliSeccionDto[]>([]); const [seccionesDisponibles, setSeccionesDisponibles] = useState<PubliSeccionDto[]>([]);
@@ -77,7 +75,6 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) { } else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA); estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA);
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_DISPONIBLE) { } else if (bobinaActual.idEstadoBobina === ID_ESTADO_DISPONIBLE) {
// --- CAMBIO: Usar ID_ESTADO_EN_USO ---
estadosFiltrados = todosLosEstados.filter( estadosFiltrados = todosLosEstados.filter(
e => e.idEstadoBobina === ID_ESTADO_EN_USO || e.idEstadoBobina === ID_ESTADO_DANADA e => e.idEstadoBobina === ID_ESTADO_EN_USO || e.idEstadoBobina === ID_ESTADO_DANADA
); );
@@ -90,7 +87,6 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
const sePuedePonerEnUso = estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO); const sePuedePonerEnUso = estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO);
if (sePuedePonerEnUso) { if (sePuedePonerEnUso) {
// --- CAMBIO: La data es PublicacionDropdownDto[] ---
const publicacionesData: PublicacionDropdownDto[] = await publicacionService.getPublicacionesForDropdown(true); const publicacionesData: PublicacionDropdownDto[] = await publicacionService.getPublicacionesForDropdown(true);
setPublicacionesDisponibles(publicacionesData); setPublicacionesDisponibles(publicacionesData);
} else { } else {
@@ -101,7 +97,7 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
} catch (error) { } catch (error) {
console.error("Error al cargar datos para dropdowns (Cambio Estado Bobina)", error); console.error("Error al cargar datos para dropdowns (Cambio Estado Bobina)", error);
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos necesarios.'})); setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar datos necesarios.' }));
} finally { } finally {
setLoadingDropdowns(false); setLoadingDropdowns(false);
} }
@@ -140,7 +136,7 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
setSeccionesDisponibles(data); setSeccionesDisponibles(data);
} catch (error) { } catch (error) {
console.error("Error al cargar secciones:", error); console.error("Error al cargar secciones:", error);
setLocalErrors(prev => ({ ...prev, secciones: 'Error al cargar secciones.'})); setLocalErrors(prev => ({ ...prev, secciones: 'Error al cargar secciones.' }));
setSeccionesDisponibles([]); setSeccionesDisponibles([]);
} finally { } finally {
setLoadingDropdowns(false); setLoadingDropdowns(false);
@@ -170,8 +166,16 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
const validate = (): boolean => { const validate = (): boolean => {
const errors: { [key: string]: string | null } = {}; const errors: { [key: string]: string | null } = {};
if (!nuevoEstadoId) errors.nuevoEstadoId = 'Seleccione un nuevo estado.'; if (!nuevoEstadoId) errors.nuevoEstadoId = 'Seleccione un nuevo estado.';
if (!fechaCambioEstado.trim()) errors.fechaCambioEstado = 'La fecha es obligatoria.'; if (!fechaCambioEstado.trim()) {
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaCambioEstado)) errors.fechaCambioEstado = 'Formato de fecha inválido.'; errors.fechaCambioEstado = 'La fecha es obligatoria.';
} else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaCambioEstado)) {
errors.fechaCambioEstado = 'Formato de fecha inválido.';
} else if (bobinaActual) {
const fechaRemitoSimple = bobinaActual.fechaRemito.split('T')[0];
if (fechaCambioEstado < fechaRemitoSimple) {
errors.fechaCambioEstado = `La fecha no puede ser anterior al ingreso (${fechaRemitoSimple}).`;
}
}
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO) { if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO) {
if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.'; if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
@@ -251,7 +255,7 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required> <FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required>
<InputLabel id="publicacion-estado-select-label">Publicación</InputLabel> <InputLabel id="publicacion-estado-select-label">Publicación</InputLabel>
<Select labelId="publicacion-estado-select-label" label="Publicación" value={idPublicacion} <Select labelId="publicacion-estado-select-label" label="Publicación" value={idPublicacion}
onChange={(e) => {setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion');}} onChange={(e) => { setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion'); }}
disabled={loading || loadingDropdowns || publicacionesDisponibles.length === 0} disabled={loading || loadingDropdowns || publicacionesDisponibles.length === 0}
> >
<MenuItem value="" disabled><em>Seleccione publicación</em></MenuItem> <MenuItem value="" disabled><em>Seleccione publicación</em></MenuItem>
@@ -262,22 +266,33 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
<FormControl fullWidth margin="dense" error={!!localErrors.idSeccion} required> <FormControl fullWidth margin="dense" error={!!localErrors.idSeccion} required>
<InputLabel id="seccion-estado-select-label">Sección</InputLabel> <InputLabel id="seccion-estado-select-label">Sección</InputLabel>
<Select labelId="seccion-estado-select-label" label="Sección" value={idSeccion} <Select labelId="seccion-estado-select-label" label="Sección" value={idSeccion}
onChange={(e) => {setIdSeccion(e.target.value as number); handleInputChange('idSeccion');}} onChange={(e) => { setIdSeccion(e.target.value as number); handleInputChange('idSeccion'); }}
disabled={loading || loadingDropdowns || !idPublicacion || seccionesDisponibles.length === 0} disabled={loading || loadingDropdowns || !idPublicacion || seccionesDisponibles.length === 0}
> >
<MenuItem value="" disabled><em>{idPublicacion ? (seccionesDisponibles.length > 0 ? 'Seleccione sección' : 'No hay secciones para esta pub.') : 'Seleccione publicación primero'}</em></MenuItem> <MenuItem value="" disabled><em>{idPublicacion ? (seccionesDisponibles.length > 0 ? 'Seleccione sección' : 'No hay secciones para esta pub.') : 'Seleccione publicación primero'}</em></MenuItem>
{seccionesDisponibles.map((s) => (<MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>))} {seccionesDisponibles.map((s) => (<MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>))}
</Select> </Select>
{localErrors.idSeccion && <Typography color="error" variant="caption">{localErrors.idSeccion}</Typography>} {localErrors.idSeccion && <Typography color="error" variant="caption">{localErrors.idSeccion}</Typography>}
{localErrors.secciones && <Alert severity="warning" sx={{mt:0.5}}>{localErrors.secciones}</Alert>} {localErrors.secciones && <Alert severity="warning" sx={{ mt: 0.5 }}>{localErrors.secciones}</Alert>}
</FormControl> </FormControl>
</> </>
)} )}
<TextField label="Fecha Cambio de Estado" type="date" value={fechaCambioEstado} required <TextField
onChange={(e) => {setFechaCambioEstado(e.target.value); handleInputChange('fechaCambioEstado');}} label="Fecha Cambio de Estado"
margin="dense" fullWidth error={!!localErrors.fechaCambioEstado} helperText={localErrors.fechaCambioEstado || ''} type="date"
disabled={loading} InputLabelProps={{ shrink: true }} value={fechaCambioEstado}
required
onChange={(e) => { setFechaCambioEstado(e.target.value); handleInputChange('fechaCambioEstado'); }}
margin="dense"
fullWidth
error={!!localErrors.fechaCambioEstado}
helperText={localErrors.fechaCambioEstado || ''}
disabled={loading}
InputLabelProps={{ shrink: true }}
inputProps={{
min: bobinaActual?.fechaRemito.split('T')[0]
}}
/> />
<TextField label="Observaciones (Opcional)" value={obs} <TextField label="Observaciones (Opcional)" value={obs}
onChange={(e) => setObs(e.target.value)} onChange={(e) => setObs(e.target.value)}
@@ -291,7 +306,7 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}> <Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button> <Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
<Button type="submit" variant="contained" <Button type="submit" variant="contained"
disabled={loading || loadingDropdowns || (estadosDisponibles.length === 0 && bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) }> disabled={loading || loadingDropdowns || (estadosDisponibles.length === 0 && bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO)}>
{loading ? <CircularProgress size={24} /> : 'Guardar Cambio de Estado'} {loading ? <CircularProgress size={24} /> : 'Guardar Cambio de Estado'}
</Button> </Button>
</Box> </Box>