Se agrego progress bar en procesos

This commit is contained in:
2025-08-06 16:56:32 -06:00
parent a49db17607
commit f5087a45fc

View File

@@ -40,7 +40,7 @@ export default function Procesos() {
const [isSelectAll, setIsSelectAll] = useState(false);
// Función para mostrar toast
const showToast = (type, title, message, details = '') => {
const showToast = (type, title, message, details = '', persistent = false, progress = null) => {
const id = Date.now();
const newToast = {
id,
@@ -48,16 +48,29 @@ export default function Procesos() {
title,
message,
details,
isVisible: true
isVisible: true,
persistent,
progress
};
setToasts(prev => [...prev, newToast]);
// Auto remover el toast después de 5 segundos (8 segundos para info)
const timeout = type === 'info' ? 8000 : 5000;
setTimeout(() => {
removeToast(id);
}, timeout);
// Auto remover el toast después de 5 segundos (8 segundos para info) solo si no es persistente
if (!persistent) {
const timeout = type === 'info' ? 8000 : 5000;
setTimeout(() => {
removeToast(id);
}, timeout);
}
return id; // Retornar el ID para poder actualizar el toast
};
// Función para actualizar toast existente (especialmente útil para progreso)
const updateToast = (id, updates) => {
setToasts(prev => prev.map(toast =>
toast.id === id ? { ...toast, ...updates } : toast
));
};
// Función para remover toast
@@ -102,12 +115,43 @@ export default function Procesos() {
return;
}
showToast('info', 'Iniciando procesamiento masivo', `Procesando ${procesosEjecutables.length} procesos de la página actual...`);
// Calcular tiempo estimado (aproximadamente 2-3 segundos por proceso)
const tiempoEstimadoSegundos = procesosEjecutables.length * 2.5;
const minutos = Math.floor(tiempoEstimadoSegundos / 60);
const segundos = Math.round(tiempoEstimadoSegundos % 60);
const tiempoTexto = minutos > 0 ? `${minutos}m ${segundos}s` : `${segundos}s`;
// Crear toast persistente con progreso
const progressToastId = showToast('info', 'Procesamiento Masivo en Progreso',
`Procesando ${procesosEjecutables.length} procesos...\n⏱ Tiempo estimado: ${tiempoTexto}`,
'',
true, // persistente
{ current: 0, total: procesosEjecutables.length, percentage: 0 }
);
const inicioTiempo = Date.now();
let exitosos = 0;
let errores = 0;
for (const proc of procesosEjecutables) {
for (let i = 0; i < procesosEjecutables.length; i++) {
const proc = procesosEjecutables[i];
// Actualizar progreso en tiempo real
const tiempoTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const progresoPercentage = Math.round(((i + 1) / procesosEjecutables.length) * 100);
const tiempoRestanteEstimado = i > 0 ? Math.round((tiempoTranscurrido / i) * (procesosEjecutables.length - i)) : tiempoEstimadoSegundos;
updateToast(progressToastId, {
message: `Procesando ${procesosEjecutables.length} procesos...\n⏱ Transcurrido: ${tiempoTranscurrido}s | Restante: ~${tiempoRestanteEstimado}s\n📊 Proceso actual: ${proc.pedimento?.numero || proc.pedimento} (${i + 1}/${procesosEjecutables.length})`,
progress: {
current: i + 1,
total: procesosEjecutables.length,
percentage: progresoPercentage,
exitosos,
errores
}
});
try {
// Cambiar estado visual a "Procesando"
updateProcesoEstado(proc.id, 2);
@@ -151,8 +195,25 @@ export default function Procesos() {
}
}
showToast('success', 'Procesamiento masivo completado',
`Resultados: ${exitosos} exitosos, ${errores} errores de ${procesosEjecutables.length} procesos.`);
const tiempoTotalTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const minutosTotales = Math.floor(tiempoTotalTranscurrido / 60);
const segundosTotales = tiempoTotalTranscurrido % 60;
const tiempoTotalTexto = minutosTotales > 0 ? `${minutosTotales}m ${segundosTotales}s` : `${segundosTotales}s`;
// Actualizar toast final
updateToast(progressToastId, {
type: 'success',
title: '✅ Procesamiento Completado',
message: `Resultados finales:\n✓ ${exitosos} exitosos | ✗ ${errores} errores\n⏱ Tiempo total: ${tiempoTotalTexto}`,
progress: {
current: procesosEjecutables.length,
total: procesosEjecutables.length,
percentage: 100,
exitosos,
errores,
completed: true
}
});
};
// Función para procesar seleccionados
@@ -171,12 +232,43 @@ export default function Procesos() {
return;
}
showToast('info', 'Iniciando procesamiento de seleccionados', `Procesando ${procesosAEjecutar.length} procesos seleccionados...`);
// Calcular tiempo estimado (aproximadamente 2-3 segundos por proceso)
const tiempoEstimadoSegundos = procesosAEjecutar.length * 2.5;
const minutos = Math.floor(tiempoEstimadoSegundos / 60);
const segundos = Math.round(tiempoEstimadoSegundos % 60);
const tiempoTexto = minutos > 0 ? `${minutos}m ${segundos}s` : `${segundos}s`;
// Crear toast persistente con progreso
const progressToastId = showToast('info', 'Procesamiento de Seleccionados en Progreso',
`Procesando ${procesosAEjecutar.length} procesos seleccionados...\n⏱ Tiempo estimado: ${tiempoTexto}`,
'',
true, // persistente
{ current: 0, total: procesosAEjecutar.length, percentage: 0 }
);
const inicioTiempo = Date.now();
let exitosos = 0;
let errores = 0;
for (const proc of procesosAEjecutar) {
for (let i = 0; i < procesosAEjecutar.length; i++) {
const proc = procesosAEjecutar[i];
// Actualizar progreso en tiempo real
const tiempoTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const progresoPercentage = Math.round(((i + 1) / procesosAEjecutar.length) * 100);
const tiempoRestanteEstimado = i > 0 ? Math.round((tiempoTranscurrido / i) * (procesosAEjecutar.length - i)) : tiempoEstimadoSegundos;
updateToast(progressToastId, {
message: `Procesando ${procesosAEjecutar.length} procesos seleccionados...\n⏱ Transcurrido: ${tiempoTranscurrido}s | Restante: ~${tiempoRestanteEstimado}s\n📊 Proceso actual: ${proc.pedimento?.numero || proc.pedimento} (${i + 1}/${procesosAEjecutar.length})`,
progress: {
current: i + 1,
total: procesosAEjecutar.length,
percentage: progresoPercentage,
exitosos,
errores
}
});
try {
// Cambiar estado visual a "Procesando"
updateProcesoEstado(proc.id, 2);
@@ -224,8 +316,25 @@ export default function Procesos() {
setSelectedProcesos([]);
setIsSelectAll(false);
showToast('success', 'Procesamiento de seleccionados completado',
`Resultados: ${exitosos} exitosos, ${errores} errores de ${procesosAEjecutar.length} procesos.`);
const tiempoTotalTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const minutosTotales = Math.floor(tiempoTotalTranscurrido / 60);
const segundosTotales = tiempoTotalTranscurrido % 60;
const tiempoTotalTexto = minutosTotales > 0 ? `${minutosTotales}m ${segundosTotales}s` : `${segundosTotales}s`;
// Actualizar toast final
updateToast(progressToastId, {
type: 'success',
title: '✅ Procesamiento de Seleccionados Completado',
message: `Resultados finales:\n✓ ${exitosos} exitosos | ✗ ${errores} errores\n⏱ Tiempo total: ${tiempoTotalTexto}`,
progress: {
current: procesosAEjecutar.length,
total: procesosAEjecutar.length,
percentage: 100,
exitosos,
errores,
completed: true
}
});
};
// Función para pasar página entera a En Espera
@@ -237,12 +346,43 @@ export default function Procesos() {
return;
}
showToast('info', 'Iniciando cambio masivo de estado', `Pasando ${procesosEnError.length} procesos a "En Espera"...`);
// Calcular tiempo estimado (aproximadamente 1 segundo por proceso)
const tiempoEstimadoSegundos = procesosEnError.length * 1;
const minutos = Math.floor(tiempoEstimadoSegundos / 60);
const segundos = Math.round(tiempoEstimadoSegundos % 60);
const tiempoTexto = minutos > 0 ? `${minutos}m ${segundos}s` : `${segundos}s`;
// Crear toast persistente con progreso
const progressToastId = showToast('info', 'Cambio Masivo de Estado en Progreso',
`Pasando ${procesosEnError.length} procesos a "En Espera"...\n⏱ Tiempo estimado: ${tiempoTexto}`,
'',
true, // persistente
{ current: 0, total: procesosEnError.length, percentage: 0 }
);
const inicioTiempo = Date.now();
let exitosos = 0;
let errores = 0;
for (const proc of procesosEnError) {
for (let i = 0; i < procesosEnError.length; i++) {
const proc = procesosEnError[i];
// Actualizar progreso en tiempo real
const tiempoTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const progresoPercentage = Math.round(((i + 1) / procesosEnError.length) * 100);
const tiempoRestanteEstimado = i > 0 ? Math.round((tiempoTranscurrido / i) * (procesosEnError.length - i)) : tiempoEstimadoSegundos;
updateToast(progressToastId, {
message: `Pasando ${procesosEnError.length} procesos a "En Espera"...\n⏱ Transcurrido: ${tiempoTranscurrido}s | Restante: ~${tiempoRestanteEstimado}s\n📊 Proceso actual: ${proc.pedimento?.numero || proc.pedimento} (${i + 1}/${procesosEnError.length})`,
progress: {
current: i + 1,
total: procesosEnError.length,
percentage: progresoPercentage,
exitosos,
errores
}
});
try {
// Cambiar estado visual a "Procesando" temporalmente
updateProcesoEstado(proc.id, 2);
@@ -275,8 +415,25 @@ export default function Procesos() {
}
}
showToast('success', 'Cambio masivo de estado completado',
`Resultados: ${exitosos} exitosos, ${errores} errores de ${procesosEnError.length} procesos.`);
const tiempoTotalTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const minutosTotales = Math.floor(tiempoTotalTranscurrido / 60);
const segundosTotales = tiempoTotalTranscurrido % 60;
const tiempoTotalTexto = minutosTotales > 0 ? `${minutosTotales}m ${segundosTotales}s` : `${segundosTotales}s`;
// Actualizar toast final
updateToast(progressToastId, {
type: 'success',
title: '✅ Cambio Masivo de Estado Completado',
message: `Resultados finales:\n✓ ${exitosos} exitosos | ✗ ${errores} errores\n⏱ Tiempo total: ${tiempoTotalTexto}`,
progress: {
current: procesosEnError.length,
total: procesosEnError.length,
percentage: 100,
exitosos,
errores,
completed: true
}
});
};
// Función para pasar seleccionados a En Espera
@@ -295,12 +452,43 @@ export default function Procesos() {
return;
}
showToast('info', 'Iniciando cambio de estado de seleccionados', `Pasando ${procesosACambiar.length} procesos seleccionados a "En Espera"...`);
// Calcular tiempo estimado (aproximadamente 1 segundo por proceso)
const tiempoEstimadoSegundos = procesosACambiar.length * 1;
const minutos = Math.floor(tiempoEstimadoSegundos / 60);
const segundos = Math.round(tiempoEstimadoSegundos % 60);
const tiempoTexto = minutos > 0 ? `${minutos}m ${segundos}s` : `${segundos}s`;
// Crear toast persistente con progreso
const progressToastId = showToast('info', 'Cambio de Estado de Seleccionados en Progreso',
`Pasando ${procesosACambiar.length} procesos seleccionados a "En Espera"...\n⏱ Tiempo estimado: ${tiempoTexto}`,
'',
true, // persistente
{ current: 0, total: procesosACambiar.length, percentage: 0 }
);
const inicioTiempo = Date.now();
let exitosos = 0;
let errores = 0;
for (const proc of procesosACambiar) {
for (let i = 0; i < procesosACambiar.length; i++) {
const proc = procesosACambiar[i];
// Actualizar progreso en tiempo real
const tiempoTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const progresoPercentage = Math.round(((i + 1) / procesosACambiar.length) * 100);
const tiempoRestanteEstimado = i > 0 ? Math.round((tiempoTranscurrido / i) * (procesosACambiar.length - i)) : tiempoEstimadoSegundos;
updateToast(progressToastId, {
message: `Pasando ${procesosACambiar.length} procesos seleccionados a "En Espera"...\n⏱ Transcurrido: ${tiempoTranscurrido}s | Restante: ~${tiempoRestanteEstimado}s\n📊 Proceso actual: ${proc.pedimento?.numero || proc.pedimento} (${i + 1}/${procesosACambiar.length})`,
progress: {
current: i + 1,
total: procesosACambiar.length,
percentage: progresoPercentage,
exitosos,
errores
}
});
try {
// Cambiar estado visual a "Procesando" temporalmente
updateProcesoEstado(proc.id, 2);
@@ -337,8 +525,25 @@ export default function Procesos() {
setSelectedProcesos([]);
setIsSelectAll(false);
showToast('success', 'Cambio de estado de seleccionados completado',
`Resultados: ${exitosos} exitosos, ${errores} errores de ${procesosACambiar.length} procesos.`);
const tiempoTotalTranscurrido = Math.round((Date.now() - inicioTiempo) / 1000);
const minutosTotales = Math.floor(tiempoTotalTranscurrido / 60);
const segundosTotales = tiempoTotalTranscurrido % 60;
const tiempoTotalTexto = minutosTotales > 0 ? `${minutosTotales}m ${segundosTotales}s` : `${segundosTotales}s`;
// Actualizar toast final
updateToast(progressToastId, {
type: 'success',
title: '✅ Cambio de Estado de Seleccionados Completado',
message: `Resultados finales:\n✓ ${exitosos} exitosos | ✗ ${errores} errores\n⏱ Tiempo total: ${tiempoTotalTexto}`,
progress: {
current: procesosACambiar.length,
total: procesosACambiar.length,
percentage: 100,
exitosos,
errores,
completed: true
}
});
};
// Función para cambiar estado de Error a En Espera
@@ -1302,7 +1507,7 @@ export default function Procesos() {
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold mb-1">{toast.title}</h4>
<p className="text-sm opacity-90">{toast.message}</p>
<p className="text-sm opacity-90 whitespace-pre-line">{toast.message}</p>
{toast.details && (
<details className="mt-2 cursor-pointer">
<summary className="text-xs opacity-70 hover:opacity-100 transition-opacity">
@@ -1323,49 +1528,108 @@ export default function Procesos() {
</div>
</details>
)}
{/* Barra de progreso tipo tqdm */}
{toast.progress && (
<div className="mt-3">
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-medium">
{toast.progress.current}/{toast.progress.total}
({toast.progress.percentage}%)
</span>
{toast.progress.exitosos !== undefined && toast.progress.errores !== undefined && (
<span className="text-xs">
<span className="text-green-600">{toast.progress.exitosos}</span>
{toast.progress.errores > 0 && (
<span className="text-red-600 ml-1">{toast.progress.errores}</span>
)}
</span>
)}
</div>
<div className={`w-full bg-gray-200 rounded-full h-2 overflow-hidden ${
toast.type === 'success' ? 'bg-green-100' :
toast.type === 'error' ? 'bg-red-100' :
toast.type === 'warning' ? 'bg-orange-100' :
'bg-blue-100'
}`}>
<div
className={`h-2 rounded-full transition-all duration-300 ease-out ${
toast.type === 'success' ? 'bg-green-500' :
toast.type === 'error' ? 'bg-red-500' :
toast.type === 'warning' ? 'bg-orange-500' :
'bg-blue-500'
}`}
style={{ width: `${toast.progress.percentage}%` }}
>
{/* Barra de progreso animada */}
{!toast.progress.completed && (
<div className="h-full w-full bg-gradient-to-r from-transparent via-white to-transparent opacity-30 animate-pulse"></div>
)}
</div>
</div>
{/* Indicador de velocidad/rate (similar a tqdm) */}
{toast.progress.current > 0 && !toast.progress.completed && (
<div className="text-xs opacity-70 mt-1">
📈 {((toast.progress.current / (Date.now() - (toast.id || Date.now()))) * 1000 * 60).toFixed(1)} items/min
</div>
)}
</div>
)}
</div>
<button
onClick={() => removeToast(toast.id)}
className="flex-shrink-0 ml-2 opacity-60 hover:opacity-100 transition-opacity"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{/* Botón de cerrar - solo visible si es persistente o completado */}
{(toast.persistent || (toast.progress && toast.progress.completed)) && (
<button
onClick={() => removeToast(toast.id)}
className="flex-shrink-0 ml-2 opacity-60 hover:opacity-100 transition-opacity p-1 rounded-full hover:bg-black/10"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
)}
{/* Indicador de toast automático */}
{!toast.persistent && (!toast.progress || !toast.progress.completed) && (
<div className="flex-shrink-0 ml-2 opacity-40">
<svg className="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
)}
</div>
</div>
{/* Barra de progreso */}
<div className={`h-1 ${
toast.type === 'success'
? 'bg-green-200'
: toast.type === 'error'
? 'bg-red-200'
: toast.type === 'warning'
? 'bg-orange-200'
: toast.type === 'info'
? 'bg-blue-200'
: 'bg-blue-200'
}`}>
<div
className={`h-full transition-all ease-linear ${
toast.type === 'success'
? 'bg-green-500'
: toast.type === 'error'
? 'bg-red-500'
: toast.type === 'warning'
? 'bg-orange-500'
: toast.type === 'info'
? 'bg-blue-500'
: 'bg-blue-500'
}`}
style={{
width: '100%',
animationDuration: toast.type === 'info' ? '8000ms' : '5000ms',
animation: `toast-progress ${toast.type === 'info' ? '8s' : '5s'} linear forwards`
}}
/>
</div>
{/* Barra de progreso de tiempo (solo para toasts automáticos sin progreso personalizado) */}
{!toast.persistent && !toast.progress && (
<div className={`h-1 ${
toast.type === 'success'
? 'bg-green-200'
: toast.type === 'error'
? 'bg-red-200'
: toast.type === 'warning'
? 'bg-orange-200'
: toast.type === 'info'
? 'bg-blue-200'
: 'bg-blue-200'
}`}>
<div
className={`h-full transition-all ease-linear ${
toast.type === 'success'
? 'bg-green-500'
: toast.type === 'error'
? 'bg-red-500'
: toast.type === 'warning'
? 'bg-orange-500'
: toast.type === 'info'
? 'bg-blue-500'
: 'bg-blue-500'
}`}
style={{
width: '100%',
animationDuration: toast.type === 'info' ? '8000ms' : '5000ms',
animation: `toast-progress ${toast.type === 'info' ? '8s' : '5s'} linear forwards`
}}
/>
</div>
)}
</div>
</div>
))}