import React, { useEffect, useState, useRef } from 'react'; import { Link } from 'react-router-dom'; import { fetchProcesamientoPedimentos } from '../api/procesos.ts'; import { postWithAuth, putWithAuth } from '../fetchWithAuth'; const API_URL = import.meta.env.VITE_EFC_API_URL; const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL; // Estado para loading de ejecución de servicio // y función para ejecutar el servicio según el tipo de proceso export default function Procesos() { const [procesos, setProcesos] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [page, setPage] = useState(1); const [count, setCount] = useState(0); const [itemsPerPage, setItemsPerPage] = useState(12); // Filtros const [pedimentoPedimentoFilter, setPedimentoPedimentoFilter] = useState(''); const [estadoFilter, setEstadoFilter] = useState(''); const [servicioFilter, setServicioFilter] = useState(''); // Sorting const [sortField, setSortField] = useState(''); const [sortOrder, setSortOrder] = useState('asc'); // 'asc' | 'desc' // Estado para loading de ejecución de servicio const [executingId, setExecutingId] = useState(null); // Estado para loading de cambio de estado const [changingStateId, setChangingStateId] = useState(null); // Estado para el sistema de toast const [toasts, setToasts] = useState([]); // Estado para selección masiva const [selectedProcesos, setSelectedProcesos] = useState([]); const [isSelectAll, setIsSelectAll] = useState(false); // Ref para rastrear valores previos de filtros y detectar cambios const prevFiltersRef = useRef({ pedimentoPedimentoFilter: '', estadoFilter: '', servicioFilter: '', sortField: '', sortOrder: 'asc' }); // Función para mostrar toast const showToast = (type, title, message, details = '', persistent = false, progress = null) => { const id = Date.now(); const newToast = { id, type, title, message, details, isVisible: true, persistent, progress }; setToasts(prev => [...prev, newToast]); // 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 const removeToast = (id) => { setToasts(prev => prev.filter(toast => toast.id !== id)); }; // Función para actualizar el estado de un proceso específico const updateProcesoEstado = (procId, nuevoEstado) => { setProcesos(prev => prev.map(proc => proc.id === procId ? { ...proc, estado: nuevoEstado } : proc )); }; // Funciones para manejo de selección const handleSelectProceso = (procesoId, isSelected) => { if (isSelected) { setSelectedProcesos(prev => [...prev, procesoId]); } else { setSelectedProcesos(prev => prev.filter(id => id !== procesoId)); } }; const handleSelectAll = () => { if (isSelectAll) { setSelectedProcesos([]); setIsSelectAll(false); } else { // Solo seleccionar procesos que se pueden ejecutar (En Espera o Error) const procesosEjecutables = procesos.filter(proc => proc.estado === 1 || proc.estado === 4); setSelectedProcesos(procesosEjecutables.map(proc => proc.id)); setIsSelectAll(true); } }; // Función para procesar página entera const handleProcesarPaginaEntera = async () => { const procesosEjecutables = procesos.filter(proc => proc.estado === 1 || proc.estado === 4); if (procesosEjecutables.length === 0) { showToast('warning', 'Sin procesos ejecutables', 'No hay procesos en estado "En Espera" o "Error" para procesar.'); return; } // 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 (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); // Determinar endpoint según el tipo de servicio let endpoint = ''; switch (proc.servicio) { case 3: endpoint = '/services/pedimento_completo'; break; case 4: endpoint = '/services/partidas'; break; case 5: endpoint = '/services/remesas'; break; case 6: endpoint = '/services/acuse'; break; case 7: endpoint = '/services/edocument'; break; case 8: endpoint = '/services/coves'; break; case 9: endpoint = '/services/acuseCove'; break; default: updateProcesoEstado(proc.id, 4); errores++; continue; } const body = { pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId, }; const res = await postWithAuth(`${MICROSERVICE_URL}${endpoint}`, body); if (!res.ok) { updateProcesoEstado(proc.id, 4); errores++; } else { updateProcesoEstado(proc.id, 3); exitosos++; } // Pequeña pausa entre requests para no sobrecargar await new Promise(resolve => setTimeout(resolve, 500)); } catch (err) { updateProcesoEstado(proc.id, 4); errores++; } } 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 const handleProcesarSeleccionados = async () => { if (selectedProcesos.length === 0) { showToast('warning', 'Sin selección', 'No hay procesos seleccionados para procesar.'); return; } const procesosAEjecutar = procesos.filter(proc => selectedProcesos.includes(proc.id) && (proc.estado === 1 || proc.estado === 4) ); if (procesosAEjecutar.length === 0) { showToast('warning', 'Sin procesos ejecutables', 'Los procesos seleccionados no están en estado "En Espera" o "Error".'); return; } // 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 (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); // Determinar endpoint según el tipo de servicio let endpoint = ''; switch (proc.servicio) { case 3: endpoint = '/services/pedimento_completo'; break; case 4: endpoint = '/services/partidas'; break; case 5: endpoint = '/services/remesas'; break; case 6: endpoint = '/services/acuse'; break; case 7: endpoint = '/services/edocument'; break; case 8: endpoint = '/services/coves'; break; case 9: endpoint = '/services/acuseCove'; break; default: updateProcesoEstado(proc.id, 4); errores++; continue; } const body = { pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId, }; const res = await postWithAuth(`${MICROSERVICE_URL}${endpoint}`, body); if (!res.ok) { updateProcesoEstado(proc.id, 4); errores++; } else { updateProcesoEstado(proc.id, 3); exitosos++; } // Pequeña pausa entre requests para no sobrecargar await new Promise(resolve => setTimeout(resolve, 500)); } catch (err) { updateProcesoEstado(proc.id, 4); errores++; } } // Limpiar selección después del procesamiento setSelectedProcesos([]); setIsSelectAll(false); 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 const handlePasarPaginaAEspera = async () => { const procesosEnError = procesos.filter(proc => proc.estado === 4); if (procesosEnError.length === 0) { showToast('warning', 'Sin procesos en error', 'No hay procesos en estado "Error" para pasar a "En Espera".'); return; } // 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 (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); const body = { estado: 1, // Cambiar a En Espera tipo_procesamiento: 2, pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, servicio: proc.servicio, organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId }; const res = await putWithAuth(`${API_URL}/customs/procesamientopedimentos/${proc.id}/`, body); if (!res.ok) { // Si falla, revertir a estado Error updateProcesoEstado(proc.id, 4); errores++; } else { // Si es exitoso, cambiar estado a En Espera updateProcesoEstado(proc.id, 1); exitosos++; } // Pequeña pausa entre requests para no sobrecargar await new Promise(resolve => setTimeout(resolve, 300)); } catch (err) { updateProcesoEstado(proc.id, 4); errores++; } } 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 const handlePasarSeleccionadosAEspera = async () => { if (selectedProcesos.length === 0) { showToast('warning', 'Sin selección', 'No hay procesos seleccionados para cambiar.'); return; } const procesosACambiar = procesos.filter(proc => selectedProcesos.includes(proc.id) && proc.estado === 4 ); if (procesosACambiar.length === 0) { showToast('warning', 'Sin procesos en error', 'Los procesos seleccionados no están en estado "Error".'); return; } // 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 (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); const body = { estado: 1, // Cambiar a En Espera tipo_procesamiento: 2, pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, servicio: proc.servicio, organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId }; const res = await putWithAuth(`${API_URL}/customs/procesamientopedimentos/${proc.id}/`, body); if (!res.ok) { // Si falla, revertir a estado Error updateProcesoEstado(proc.id, 4); errores++; } else { // Si es exitoso, cambiar estado a En Espera updateProcesoEstado(proc.id, 1); exitosos++; } // Pequeña pausa entre requests para no sobrecargar await new Promise(resolve => setTimeout(resolve, 300)); } catch (err) { updateProcesoEstado(proc.id, 4); errores++; } } // Limpiar selección después del cambio setSelectedProcesos([]); setIsSelectAll(false); 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 const handlePasarAEspera = async (proc) => { setChangingStateId(proc.id); // Cambiar estado visual a "Procesando" inmediatamente updateProcesoEstado(proc.id, 2); // 2 = Procesando try { const body = { estado: 1, // Cambiar a En Espera tipo_procesamiento: 2, pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, servicio: proc.servicio, organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId }; const res = await putWithAuth(`${API_URL}/customs/procesamientopedimentos/${proc.id}/`, body); if (!res.ok) { // Si falla, revertir a estado Error updateProcesoEstado(proc.id, 4); // 4 = Error let errorText = 'Error desconocido'; try { errorText = await res.text(); } catch (textErr) { // Error al leer respuesta } throw new Error(`Error al cambiar el estado del proceso: ${errorText}`); } // Cambiar estado visual a "En Espera" si fue exitoso updateProcesoEstado(proc.id, 1); // 1 = En Espera showToast('success', '¡Éxito!', 'Estado cambiado a "En Espera" correctamente'); // Refrescar la lista de procesos después de un delay más largo setTimeout(() => { window.location.reload(); }, 3000); } catch (err) { // Crear un mensaje de error más detallado y persistente const errorDetails = { message: err.message, status: err.status || 'N/A', stack: err.stack, timestamp: new Date().toISOString() }; // Alert más detallado que permanece visible const detailedMessage = ` 🚨 ERROR DETALLADO: ⏰ Tiempo: ${new Date().toLocaleString()} 📝 Mensaje: ${err.message} 🔢 Status: ${err.status || 'N/A'} 🔍 Tipo: ${err.name || 'Error'} 📋 Copia este mensaje y compártelo para debugging. `.trim(); if (err.message === 'SESSION_EXPIRED') { showToast('error', '🚪 Sesión Expirada', 'Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', detailedMessage); } else { showToast('error', '🚨 Error al cambiar estado', 'Ocurrió un error al intentar cambiar el estado del proceso.', detailedMessage); } } finally { setChangingStateId(null); } }; // Función para ejecutar el servicio según el tipo de proceso const handleEjecutarServicio = async (proc) => { setExecutingId(proc.id); // Cambiar estado visual a "Procesando" inmediatamente updateProcesoEstado(proc.id, 2); // 2 = Procesando let endpoint = ''; // Determinar endpoint según el tipo de servicio switch (proc.servicio) { case 3: endpoint = '/services/pedimento_completo'; break; case 4: // Partidas endpoint = '/services/partidas'; break; case 5: // Remesas endpoint = '/services/remesas'; break; case 6: // Acuse endpoint = '/services/acuse'; break; case 7: endpoint = '/services/edocument'; break; case 8: // Coves endpoint = '/services/coves'; break; case 9: // Acuse Cove endpoint = '/services/acuseCove'; break; default: // Revertir estado si el servicio no es soportado updateProcesoEstado(proc.id, proc.estado); // Revertir al estado original showToast('error', 'Servicio no soportado', 'Este servicio no es compatible para ejecución directa.'); setExecutingId(null); return; } try { const body = { pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId, }; const res = await postWithAuth(`${MICROSERVICE_URL}${endpoint}`, body); if (!res.ok) { // Si falla, cambiar estado a Error updateProcesoEstado(proc.id, 4); // 4 = Error throw new Error('Error al ejecutar el servicio'); } // Si es exitoso, cambiar estado a Finalizado updateProcesoEstado(proc.id, 3); // 3 = Finalizado showToast('success', '¡Servicio ejecutado!', 'El servicio se ha ejecutado correctamente'); } catch (err) { // Cambiar estado a Error en caso de excepción updateProcesoEstado(proc.id, 4); // 4 = Error if (err.message === 'SESSION_EXPIRED') { showToast('error', '🚪 Sesión Expirada', 'Tu sesión ha expirado. Por favor, inicia sesión nuevamente.'); } else { showToast('error', '❌ Error al ejecutar servicio', 'Ocurrió un error al intentar ejecutar el servicio.', 'Error: ' + (err instanceof Error ? err.message : String(err))); } } finally { setExecutingId(null); } }; useEffect(() => { async function fetchProcesos() { // Detectar si algún filtro cambió const currentFilters = { pedimentoPedimentoFilter, estadoFilter, servicioFilter, sortField, sortOrder }; const filtersChanged = Object.keys(currentFilters).some( key => currentFilters[key] !== prevFiltersRef.current[key] ); // Si los filtros cambiaron y no estamos en la página 1, resetear página if (filtersChanged && page !== 1) { setPage(1); // Actualizar ref con valores actuales prevFiltersRef.current = { ...currentFilters }; return; // Salir temprano, el efecto se ejecutará de nuevo con page = 1 } // Actualizar ref con valores actuales prevFiltersRef.current = { ...currentFilters }; setLoading(true); setError(''); try { // Construir filtros const filters = {}; if (pedimentoPedimentoFilter) filters['pedimento__pedimento_app'] = pedimentoPedimentoFilter; if (estadoFilter) filters['estado'] = estadoFilter; if (servicioFilter) filters['servicio'] = servicioFilter; if (sortField) { filters['ordering'] = (sortOrder === 'desc' ? '-' : '') + sortField; } const data = await fetchProcesamientoPedimentos(page, itemsPerPage, filters); setProcesos(data.results || []); setCount(data.count || 0); } catch (err) { if (err.message === 'SESSION_EXPIRED') { setError('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.'); } else { setError(err instanceof Error ? err.message : String(err)); } } finally { setLoading(false); } } fetchProcesos(); }, [page, itemsPerPage, pedimentoPedimentoFilter, estadoFilter, servicioFilter, sortField, sortOrder]); return (
{/* Header mejorado y responsivo */}

Procesos del Sistema {count > 0 && ( {count} procesos )}

Estado actual de los procesos de la agencia aduanal

{/* Efectos decorativos de fondo modernos */}
{/* Partículas flotantes */}
{/* Animaciones CSS */}
{/* Contenido principal */}

Procesamiento de Pedimentos

{count > 0 && (
Total de registros: {count}
)}
{/* Botones de acción masiva */}

Acciones masivas

{/* Sección de Procesamiento */}

Ejecutar Servicios

{/* Divisor vertical */}
{/* Sección de Cambio de Estado */}

Pasar a En Espera

{/* Divisor vertical */}
{/* Sección de Control */}

Control

{selectedProcesos.length > 0 ? ( ) : (
Sin selección
)}
{/* Filtros responsivos mejorados */}

Filtros de búsqueda

setPedimentoPedimentoFilter(e.target.value)} placeholder="Buscar por pedimento..." className="w-full border border-gray-300 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm transition-all duration-200 hover:shadow-md" />
{/* Estados de carga y error mejorados */} {loading ? (

Cargando procesos...

) : error ? (

Error al cargar

{error}

) : ( <> {/* Vista de tabla para pantallas grandes */}
{procesos.length === 0 ? ( ) : ( procesos.map((proc) => ( )) )}
{ setSortField('id'); setSortOrder(sortField === 'id' && sortOrder === 'asc' ? 'desc' : 'asc'); }} >
ID {sortField === 'id' && (sortOrder === 'asc' ? '▲' : '▼')}
{ setSortField('organizacion_name'); setSortOrder(sortField === 'organizacion_name' && sortOrder === 'asc' ? 'desc' : 'asc'); }} >
Organización {sortField === 'organizacion_name' && (sortOrder === 'asc' ? '▲' : '▼')}
{ setSortField('estado'); setSortOrder(sortField === 'estado' && sortOrder === 'asc' ? 'desc' : 'asc'); }} >
Estado {sortField === 'estado' && (sortOrder === 'asc' ? '▲' : '▼')}
{ setSortField('pedimento'); setSortOrder(sortField === 'pedimento' && sortOrder === 'asc' ? 'desc' : 'asc'); }} >
Pedimento {sortField === 'pedimento' && (sortOrder === 'asc' ? '▲' : '▼')}
{ setSortField('servicio'); setSortOrder(sortField === 'servicio' && sortOrder === 'asc' ? 'desc' : 'asc'); }} >
Servicio {sortField === 'servicio' && (sortOrder === 'asc' ? '▲' : '▼')}
Acciones

No hay procesos disponibles

Intenta ajustar los filtros de búsqueda

handleSelectProceso(proc.id, e.target.checked)} disabled={proc.estado === 2 || proc.estado === 3} // Deshabilitar si está Procesando o Finalizado className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 disabled:opacity-50" /> {proc.id} {proc.organizacion_name || '-'} {(() => { const estado = proc.estado === 1 ? { text: 'En Espera', color: 'bg-yellow-100 text-yellow-800 border-yellow-200' } : proc.estado === 2 ? { text: 'Procesando', color: 'bg-blue-100 text-blue-800 border-blue-200' } : proc.estado === 3 ? { text: 'Finalizado', color: 'bg-green-100 text-green-800 border-green-200' } : proc.estado === 4 ? { text: 'Error', color: 'bg-red-100 text-red-800 border-red-200' } : { text: String(proc.estado), color: 'bg-gray-100 text-gray-800 border-gray-200' }; return ( {estado.text} ); })()} {typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.pedimento_app || proc.pedimento.pedimento_app || JSON.stringify(proc.pedimento) : proc.pedimento} {proc.servicio === 1 ? 'Estado de pedimento' : proc.servicio === 2 ? 'Listado de pedimentos' : proc.servicio === 3 ? 'Pedimento Completo' : proc.servicio === 4 ? 'Pedimento Partidas' : proc.servicio === 5 ? 'Pedimento Remesas' : proc.servicio === 6 ? 'Acuse' : proc.servicio === 7 ? 'EDocument' : proc.servicio === 8 ? 'Cove' : proc.servicio === 9 ? 'Acuse Cove' : String(proc.servicio)}
{/* Botón Play - Ejecutar Servicio */} {/* Botón Reload - Pasar a Espera */}
{/* Vista de tarjetas para pantallas pequeñas y medianas */}
{procesos.length === 0 ? (

No hay procesos disponibles

Intenta ajustar los filtros de búsqueda

) : ( procesos.map((proc) => (
handleSelectProceso(proc.id, e.target.checked)} disabled={proc.estado === 2 || proc.estado === 3} // Deshabilitar si está Procesando o Finalizado className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 disabled:opacity-50 mt-1" />

Proceso #{proc.id}

{proc.organizacion_name || 'Sin organización'}

{(() => { const estado = proc.estado === 1 ? { text: 'En Espera', color: 'bg-yellow-100 text-yellow-800 border-yellow-200' } : proc.estado === 2 ? { text: 'Procesando', color: 'bg-blue-100 text-blue-800 border-blue-200' } : proc.estado === 3 ? { text: 'Finalizado', color: 'bg-green-100 text-green-800 border-green-200' } : proc.estado === 4 ? { text: 'Error', color: 'bg-red-100 text-red-800 border-red-200' } : { text: String(proc.estado), color: 'bg-gray-100 text-gray-800 border-gray-200' }; return ( {estado.text} ); })()}
Pedimento: {typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.pedimento || JSON.stringify(proc.pedimento) : proc.pedimento}
Servicio: {proc.servicio === 1 ? 'Estado de pedimento' : proc.servicio === 2 ? 'Listado de pedimentos' : proc.servicio === 3 ? 'Pedimento Completo' : proc.servicio === 4 ? 'Pedimento Partidas' : proc.servicio === 5 ? 'Pedimento Remesas' : proc.servicio === 6 ? 'Acuse' : proc.servicio === 7 ? 'EDocument' : proc.servicio === 8 ? 'Cove' : proc.servicio === 9 ? 'Acuse Cove' : String(proc.servicio)}
{/* Botón Play - Ejecutar Servicio */} {/* Botón Reload - Pasar a Espera */}
)) )}
{/* Paginación compartida mejorada */} {count > 0 && (
{(() => { const totalPages = Math.max(1, Math.ceil(count / itemsPerPage)); const maxPagesToShow = 5; let startPage = Math.max(1, page - Math.floor(maxPagesToShow / 2)); let endPage = startPage + maxPagesToShow - 1; if (endPage > totalPages) { endPage = totalPages; startPage = Math.max(1, endPage - maxPagesToShow + 1); } const pageNumbers = []; for (let i = startPage; i <= endPage; i++) { pageNumbers.push(i); } return ( <>
{pageNumbers.map(num => ( ))}
Página {page} de {totalPages} ); })()}
)} )}
{/* Sistema de Toast Notifications */}
{toasts.map((toast) => (
{toast.type === 'success' ? ( ) : toast.type === 'error' ? ( ) : toast.type === 'warning' ? ( ) : ( )}

{toast.title}

{toast.message}

{toast.details && (
Ver detalles técnicos
{toast.details}
)} {/* Barra de progreso tipo tqdm */} {toast.progress && (
{toast.progress.current}/{toast.progress.total} ({toast.progress.percentage}%) {toast.progress.exitosos !== undefined && toast.progress.errores !== undefined && ( ✓{toast.progress.exitosos} {toast.progress.errores > 0 && ( ✗{toast.progress.errores} )} )}
{/* Barra de progreso animada */} {!toast.progress.completed && (
)}
{/* Indicador de velocidad/rate (similar a tqdm) */} {toast.progress.current > 0 && !toast.progress.completed && (
📈 {((toast.progress.current / (Date.now() - (toast.id || Date.now()))) * 1000 * 60).toFixed(1)} items/min
)}
)}
{/* Botón de cerrar - solo visible si es persistente o completado */} {(toast.persistent || (toast.progress && toast.progress.completed)) && ( )} {/* Indicador de toast automático */} {!toast.persistent && (!toast.progress || !toast.progress.completed) && (
)}
{/* Barra de progreso de tiempo (solo para toasts automáticos sin progreso personalizado) */} {!toast.persistent && !toast.progress && (
)}
))}
{/* Estilos CSS para las animaciones */}
); }