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,12 +199,20 @@ namespace GestionIntegral.Api.Services.Impresion
using var transaction = connection.BeginTransaction();
try
{
var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de la transacción
var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina);
if (bobina == null)
{
try { transaction.Rollback(); } catch { }
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);
if (nuevoEstado == null)

View File

@@ -7,7 +7,6 @@ import {
import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto';
import type { CambiarEstadoBobinaDto } from '../../../models/dtos/Impresion/CambiarEstadoBobinaDto';
import type { EstadoBobinaDto } from '../../../models/dtos/Impresion/EstadoBobinaDto';
// --- CAMBIO: Importar PublicacionDropdownDto ---
import type { PublicacionDropdownDto } from '../../../models/dtos/Distribucion/PublicacionDropdownDto';
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto';
import estadoBobinaService from '../../../services/Impresion/estadoBobinaService';
@@ -33,272 +32,288 @@ const ID_ESTADO_EN_USO = 2; // Usaremos este consistentemente
const ID_ESTADO_DANADA = 3;
interface StockBobinaCambioEstadoModalProps {
open: boolean;
onClose: () => void;
onSubmit: (idBobina: number, data: CambiarEstadoBobinaDto) => Promise<void>;
bobinaActual: StockBobinaDto | null;
errorMessage?: string | null;
clearErrorMessage: () => void;
open: boolean;
onClose: () => void;
onSubmit: (idBobina: number, data: CambiarEstadoBobinaDto) => Promise<void>;
bobinaActual: StockBobinaDto | null;
errorMessage?: string | null;
clearErrorMessage: () => void;
}
const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps> = ({
open,
onClose,
onSubmit,
bobinaActual,
errorMessage,
clearErrorMessage
open,
onClose,
onSubmit,
bobinaActual,
errorMessage,
clearErrorMessage
}) => {
const [nuevoEstadoId, setNuevoEstadoId] = useState<number | string>('');
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
const [idSeccion, setIdSeccion] = useState<number | string>('');
const [obs, setObs] = useState('');
const [fechaCambioEstado, setFechaCambioEstado] = useState('');
const [nuevoEstadoId, setNuevoEstadoId] = useState<number | string>('');
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
const [idSeccion, setIdSeccion] = useState<number | string>('');
const [obs, setObs] = useState('');
const [fechaCambioEstado, setFechaCambioEstado] = useState('');
const [estadosDisponibles, setEstadosDisponibles] = useState<EstadoBobinaDto[]>([]);
// --- CAMBIO: Usar PublicacionDropdownDto para el estado ---
const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDropdownDto[]>([]);
const [seccionesDisponibles, setSeccionesDisponibles] = useState<PubliSeccionDto[]>([]);
const [estadosDisponibles, setEstadosDisponibles] = useState<EstadoBobinaDto[]>([]);
const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDropdownDto[]>([]);
const [seccionesDisponibles, setSeccionesDisponibles] = useState<PubliSeccionDto[]>([]);
const [loading, setLoading] = useState(false);
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
const [loading, setLoading] = useState(false);
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
useEffect(() => {
const fetchDropdownData = async () => {
if (!bobinaActual) return;
setLoadingDropdowns(true);
try {
const todosLosEstados = await estadoBobinaService.getAllEstadosBobina();
let estadosFiltrados: EstadoBobinaDto[];
if (bobinaActual.idEstadoBobina === ID_ESTADO_DANADA) {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DISPONIBLE);
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA);
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_DISPONIBLE) {
// --- CAMBIO: Usar ID_ESTADO_EN_USO ---
estadosFiltrados = todosLosEstados.filter(
e => e.idEstadoBobina === ID_ESTADO_EN_USO || e.idEstadoBobina === ID_ESTADO_DANADA
);
} else {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina !== bobinaActual.idEstadoBobina);
}
setEstadosDisponibles(estadosFiltrados);
const sePuedePonerEnUso = estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO);
if (sePuedePonerEnUso) {
// --- CAMBIO: La data es PublicacionDropdownDto[] ---
const publicacionesData: PublicacionDropdownDto[] = await publicacionService.getPublicacionesForDropdown(true);
setPublicacionesDisponibles(publicacionesData);
} else {
setPublicacionesDisponibles([]);
setIdPublicacion('');
setIdSeccion('');
}
} catch (error) {
console.error("Error al cargar datos para dropdowns (Cambio Estado Bobina)", error);
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos necesarios.'}));
} finally {
setLoadingDropdowns(false);
}
};
if (open && bobinaActual) {
fetchDropdownData();
setNuevoEstadoId('');
// Pre-cargar basado en si la bobina actual está "En Uso"
if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
setIdPublicacion(bobinaActual.idPublicacion?.toString() || '');
// Solo pre-cargar sección si la publicación también estaba pre-cargada
if (bobinaActual.idPublicacion) {
setIdSeccion(bobinaActual.idSeccion?.toString() || '');
} else {
setIdSeccion('');
}
} else {
setIdPublicacion('');
setIdSeccion('');
}
setObs(bobinaActual.obs || '');
setFechaCambioEstado(new Date().toISOString().split('T')[0]);
setLocalErrors({});
clearErrorMessage();
}
}, [open, bobinaActual, clearErrorMessage]);
useEffect(() => {
const fetchSecciones = async () => {
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO && idPublicacion) {
useEffect(() => {
const fetchDropdownData = async () => {
if (!bobinaActual) return;
setLoadingDropdowns(true);
try {
const data = await publiSeccionService.getSeccionesPorPublicacion(Number(idPublicacion), true);
setSeccionesDisponibles(data);
const todosLosEstados = await estadoBobinaService.getAllEstadosBobina();
let estadosFiltrados: EstadoBobinaDto[];
if (bobinaActual.idEstadoBobina === ID_ESTADO_DANADA) {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DISPONIBLE);
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA);
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_DISPONIBLE) {
estadosFiltrados = todosLosEstados.filter(
e => e.idEstadoBobina === ID_ESTADO_EN_USO || e.idEstadoBobina === ID_ESTADO_DANADA
);
} else {
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina !== bobinaActual.idEstadoBobina);
}
setEstadosDisponibles(estadosFiltrados);
const sePuedePonerEnUso = estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO);
if (sePuedePonerEnUso) {
const publicacionesData: PublicacionDropdownDto[] = await publicacionService.getPublicacionesForDropdown(true);
setPublicacionesDisponibles(publicacionesData);
} else {
setPublicacionesDisponibles([]);
setIdPublicacion('');
setIdSeccion('');
}
} catch (error) {
console.error("Error al cargar secciones:", error);
setLocalErrors(prev => ({ ...prev, secciones: 'Error al cargar secciones.'}));
setSeccionesDisponibles([]);
console.error("Error al cargar datos para dropdowns (Cambio Estado Bobina)", error);
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar datos necesarios.' }));
} finally {
setLoadingDropdowns(false);
}
};
if (open && bobinaActual) {
fetchDropdownData();
setNuevoEstadoId('');
// Pre-cargar basado en si la bobina actual está "En Uso"
if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
setIdPublicacion(bobinaActual.idPublicacion?.toString() || '');
// Solo pre-cargar sección si la publicación también estaba pre-cargada
if (bobinaActual.idPublicacion) {
setIdSeccion(bobinaActual.idSeccion?.toString() || '');
} else {
setIdSeccion('');
}
} else {
setIdPublicacion('');
setIdSeccion('');
}
setObs(bobinaActual.obs || '');
setFechaCambioEstado(new Date().toISOString().split('T')[0]);
setLocalErrors({});
clearErrorMessage();
}
}, [open, bobinaActual, clearErrorMessage]);
useEffect(() => {
const fetchSecciones = async () => {
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO && idPublicacion) {
setLoadingDropdowns(true);
try {
const data = await publiSeccionService.getSeccionesPorPublicacion(Number(idPublicacion), true);
setSeccionesDisponibles(data);
} catch (error) {
console.error("Error al cargar secciones:", error);
setLocalErrors(prev => ({ ...prev, secciones: 'Error al cargar secciones.' }));
setSeccionesDisponibles([]);
} finally {
setLoadingDropdowns(false);
}
} else {
setSeccionesDisponibles([]);
// No es necesario setIdSeccion('') aquí si el useEffect de nuevoEstadoId ya lo hace.
}
};
if (idPublicacion && Number(nuevoEstadoId) === ID_ESTADO_EN_USO) { // Solo fetchear si hay idPublicacion
fetchSecciones();
} else {
setSeccionesDisponibles([]);
// No es necesario setIdSeccion('') aquí si el useEffect de nuevoEstadoId ya lo hace.
setSeccionesDisponibles([]); // Limpiar si no se cumplen condiciones
}
}, [nuevoEstadoId, idPublicacion]);
// Efecto para limpiar publicacion/seccion si el nuevo estado no es "En Uso"
useEffect(() => {
if (Number(nuevoEstadoId) !== ID_ESTADO_EN_USO) {
setIdPublicacion('');
setIdSeccion('');
}
}, [nuevoEstadoId]);
const validate = (): boolean => {
const errors: { [key: string]: string | null } = {};
if (!nuevoEstadoId) errors.nuevoEstadoId = 'Seleccione un nuevo estado.';
if (!fechaCambioEstado.trim()) {
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 (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
if (!idSeccion) errors.idSeccion = 'Seleccione una sección.';
}
setLocalErrors(errors);
return Object.keys(errors).length === 0;
};
const handleInputChange = (fieldName: string) => {
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
if (errorMessage) clearErrorMessage();
// La lógica de limpieza de pub/secc se movió a un useEffect dedicado a nuevoEstadoId
// y el de sección a un useEffect de idPublicacion
if (fieldName === 'idPublicacion') { // Si cambia la publicación, resetear seccion
setIdSeccion('');
}
};
if (idPublicacion && Number(nuevoEstadoId) === ID_ESTADO_EN_USO) { // Solo fetchear si hay idPublicacion
fetchSecciones();
} else {
setSeccionesDisponibles([]); // Limpiar si no se cumplen condiciones
}
}, [nuevoEstadoId, idPublicacion]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
clearErrorMessage();
if (!validate() || !bobinaActual) return;
// Efecto para limpiar publicacion/seccion si el nuevo estado no es "En Uso"
useEffect(() => {
if (Number(nuevoEstadoId) !== ID_ESTADO_EN_USO) {
setIdPublicacion('');
setIdSeccion('');
}
}, [nuevoEstadoId]);
setLoading(true);
try {
const esEnUso = Number(nuevoEstadoId) === ID_ESTADO_EN_USO;
const dataToSubmit: CambiarEstadoBobinaDto = {
nuevoEstadoId: Number(nuevoEstadoId),
idPublicacion: esEnUso && idPublicacion ? Number(idPublicacion) : null,
idSeccion: esEnUso && idPublicacion && idSeccion ? Number(idSeccion) : null,
obs: obs.trim() || null,
fechaCambioEstado,
};
await onSubmit(bobinaActual.idBobina, dataToSubmit);
onClose();
} catch (error: any) {
console.error("Error en submit de StockBobinaCambioEstadoModal:", error);
} finally {
setLoading(false);
}
};
if (!bobinaActual) return null;
const validate = (): boolean => {
const errors: { [key: string]: string | null } = {};
if (!nuevoEstadoId) errors.nuevoEstadoId = 'Seleccione un nuevo estado.';
if (!fechaCambioEstado.trim()) errors.fechaCambioEstado = 'La fecha es obligatoria.';
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaCambioEstado)) errors.fechaCambioEstado = 'Formato de fecha inválido.';
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO) {
if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
if (!idSeccion) errors.idSeccion = 'Seleccione una sección.';
}
setLocalErrors(errors);
return Object.keys(errors).length === 0;
};
const handleInputChange = (fieldName: string) => {
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
if (errorMessage) clearErrorMessage();
// La lógica de limpieza de pub/secc se movió a un useEffect dedicado a nuevoEstadoId
// y el de sección a un useEffect de idPublicacion
if (fieldName === 'idPublicacion') { // Si cambia la publicación, resetear seccion
setIdSeccion('');
}
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
clearErrorMessage();
if (!validate() || !bobinaActual) return;
setLoading(true);
try {
const esEnUso = Number(nuevoEstadoId) === ID_ESTADO_EN_USO;
const dataToSubmit: CambiarEstadoBobinaDto = {
nuevoEstadoId: Number(nuevoEstadoId),
idPublicacion: esEnUso && idPublicacion ? Number(idPublicacion) : null,
idSeccion: esEnUso && idPublicacion && idSeccion ? Number(idSeccion) : null,
obs: obs.trim() || null,
fechaCambioEstado,
};
await onSubmit(bobinaActual.idBobina, dataToSubmit);
onClose();
} catch (error: any) {
console.error("Error en submit de StockBobinaCambioEstadoModal:", error);
} finally {
setLoading(false);
}
};
if (!bobinaActual) return null;
return (
<Modal open={open} onClose={onClose}>
<Box sx={modalStyle}>
<Typography variant="h6" component="h2" gutterBottom>
Cambiar Estado de Bobina: {bobinaActual.nroBobina}
</Typography>
<Typography variant="body2" gutterBottom>
Estado Actual: <strong>{bobinaActual.nombreEstadoBobina}</strong>
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<FormControl fullWidth margin="dense" error={!!localErrors.nuevoEstadoId} required>
<InputLabel id="nuevo-estado-select-label">Nuevo Estado</InputLabel>
<Select
labelId="nuevo-estado-select-label"
label="Nuevo Estado"
value={nuevoEstadoId}
onChange={(e) => {
setNuevoEstadoId(e.target.value as number | string);
handleInputChange('nuevoEstadoId');
}}
disabled={loading || loadingDropdowns || estadosDisponibles.length === 0}
>
<MenuItem value="" disabled><em>Seleccione un estado</em></MenuItem>
{estadosDisponibles.map((e) => (<MenuItem key={e.idEstadoBobina} value={e.idEstadoBobina}>{e.denominacion}</MenuItem>))}
</Select>
{localErrors.nuevoEstadoId && <Typography color="error" variant="caption">{localErrors.nuevoEstadoId}</Typography>}
</FormControl>
{Number(nuevoEstadoId) === ID_ESTADO_EN_USO && (
<>
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required>
<InputLabel id="publicacion-estado-select-label">Publicación</InputLabel>
<Select labelId="publicacion-estado-select-label" label="Publicación" value={idPublicacion}
onChange={(e) => {setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion');}}
disabled={loading || loadingDropdowns || publicacionesDisponibles.length === 0}
return (
<Modal open={open} onClose={onClose}>
<Box sx={modalStyle}>
<Typography variant="h6" component="h2" gutterBottom>
Cambiar Estado de Bobina: {bobinaActual.nroBobina}
</Typography>
<Typography variant="body2" gutterBottom>
Estado Actual: <strong>{bobinaActual.nombreEstadoBobina}</strong>
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<FormControl fullWidth margin="dense" error={!!localErrors.nuevoEstadoId} required>
<InputLabel id="nuevo-estado-select-label">Nuevo Estado</InputLabel>
<Select
labelId="nuevo-estado-select-label"
label="Nuevo Estado"
value={nuevoEstadoId}
onChange={(e) => {
setNuevoEstadoId(e.target.value as number | string);
handleInputChange('nuevoEstadoId');
}}
disabled={loading || loadingDropdowns || estadosDisponibles.length === 0}
>
<MenuItem value="" disabled><em>Seleccione publicación</em></MenuItem>
{publicacionesDisponibles.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>))}
<MenuItem value="" disabled><em>Seleccione un estado</em></MenuItem>
{estadosDisponibles.map((e) => (<MenuItem key={e.idEstadoBobina} value={e.idEstadoBobina}>{e.denominacion}</MenuItem>))}
</Select>
{localErrors.idPublicacion && <Typography color="error" variant="caption">{localErrors.idPublicacion}</Typography>}
{localErrors.nuevoEstadoId && <Typography color="error" variant="caption">{localErrors.nuevoEstadoId}</Typography>}
</FormControl>
<FormControl fullWidth margin="dense" error={!!localErrors.idSeccion} required>
<InputLabel id="seccion-estado-select-label">Sección</InputLabel>
<Select labelId="seccion-estado-select-label" label="Sección" value={idSeccion}
onChange={(e) => {setIdSeccion(e.target.value as number); handleInputChange('idSeccion');}}
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>
{seccionesDisponibles.map((s) => (<MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>))}
</Select>
{localErrors.idSeccion && <Typography color="error" variant="caption">{localErrors.idSeccion}</Typography>}
{localErrors.secciones && <Alert severity="warning" sx={{mt:0.5}}>{localErrors.secciones}</Alert>}
</FormControl>
</>
)}
<TextField label="Fecha Cambio de Estado" type="date" 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 }}
/>
<TextField label="Observaciones (Opcional)" value={obs}
onChange={(e) => setObs(e.target.value)}
margin="dense" fullWidth multiline rows={3} disabled={loading}
/>
{Number(nuevoEstadoId) === ID_ESTADO_EN_USO && (
<>
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required>
<InputLabel id="publicacion-estado-select-label">Publicación</InputLabel>
<Select labelId="publicacion-estado-select-label" label="Publicación" value={idPublicacion}
onChange={(e) => { setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion'); }}
disabled={loading || loadingDropdowns || publicacionesDisponibles.length === 0}
>
<MenuItem value="" disabled><em>Seleccione publicación</em></MenuItem>
{publicacionesDisponibles.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>))}
</Select>
{localErrors.idPublicacion && <Typography color="error" variant="caption">{localErrors.idPublicacion}</Typography>}
</FormControl>
<FormControl fullWidth margin="dense" error={!!localErrors.idSeccion} required>
<InputLabel id="seccion-estado-select-label">Sección</InputLabel>
<Select labelId="seccion-estado-select-label" label="Sección" value={idSeccion}
onChange={(e) => { setIdSeccion(e.target.value as number); handleInputChange('idSeccion'); }}
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>
{seccionesDisponibles.map((s) => (<MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>))}
</Select>
{localErrors.idSeccion && <Typography color="error" variant="caption">{localErrors.idSeccion}</Typography>}
{localErrors.secciones && <Alert severity="warning" sx={{ mt: 0.5 }}>{localErrors.secciones}</Alert>}
</FormControl>
</>
)}
<TextField
label="Fecha Cambio de Estado"
type="date"
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}
onChange={(e) => setObs(e.target.value)}
margin="dense" fullWidth multiline rows={3} disabled={loading}
/>
</Box>
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</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 || loadingDropdowns || (estadosDisponibles.length === 0 && bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO)}>
{loading ? <CircularProgress size={24} /> : 'Guardar Cambio de Estado'}
</Button>
</Box>
</Box>
</Box>
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</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 || loadingDropdowns || (estadosDisponibles.length === 0 && bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) }>
{loading ? <CircularProgress size={24} /> : 'Guardar Cambio de Estado'}
</Button>
</Box>
</Box>
</Box>
</Modal>
);
</Modal>
);
};
export default StockBobinaCambioEstadoModal;