se modifico auditor y pagina de procesos

This commit is contained in:
2025-10-12 07:52:06 -06:00
parent c924aab9c1
commit 03a9f793b1
5 changed files with 1049 additions and 1384 deletions

View File

@@ -17,9 +17,10 @@ import 'highlight.js/styles/github.css';
hljs.registerLanguage('xml', xml);
import { fetchPedimentoDocuments } from '../api/pedimentoDocuments';
import { fetchWithAuth, postWithAuth, putWithAuth } from '../fetchWithAuth';
import { fetchProcesamientoPedimentos } from '../api/procesos.ts';
import { fetchTasks } from '../api/procesos.ts';
import { fetchPedimentoCoves, downloadCove, downloadAcuseCove } from '../api/coves';
import { fetchPedimentoEdocuments, downloadEdocument, downloadAcuseEdocument } from '../api/edocuments';
import { getTaskStatusLabel, getTaskStatusColor, isTaskActionable, isTaskFinal } from '../api/taskStatus';
import { useParams, Link } from 'react-router-dom';
import { useNotification } from '../context/NotificationContext';
@@ -370,11 +371,21 @@ export default function PedimentoDetail() {
const [procesosPage, setProcesosPage] = useState(1);
const [procesosPageSize, setProcesosPageSize] = useState(10);
const [procesosFilters, setProcesosFilters] = useState({});
const [sortField, setSortField] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
const [selectedProcesos, setSelectedProcesos] = useState([]);
const [isSelectAll, setIsSelectAll] = useState(false);
// Estados para las acciones de procesos
const [executingId, setExecutingId] = useState(null);
const [changingStateId, setChangingStateId] = useState(null);
const [creatingService, setCreatingService] = useState(null);
// Ref para rastrear valores previos de filtros
const prevFiltersRef = useRef({
sortField: '',
sortOrder: 'asc'
});
// Estados para modal de preview
const [previewOpen, setPreviewOpen] = useState(false);
@@ -448,7 +459,7 @@ export default function PedimentoDetail() {
setSelected(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
};
const handleSelectAll = () => {
const handleSelectAllDocs = () => {
if (allSelected) setSelected([]);
else setSelected(allDocIds);
};
@@ -824,10 +835,22 @@ export default function PedimentoDetail() {
}, [edocsFilters]);
// Funciones para acciones de Procesos
// Map legacy estado values to new status values
const mapEstadoToStatus = (estado) => {
const mapping = {
1: 'pending', // Pendiente
2: 'processing', // En Proceso
3: 'completed', // Completado
4: 'failed', // Error
5: 'cancelled' // Cancelado
};
return mapping[estado] || 'pending';
};
const updateProcesoEstado = (procId, nuevoEstado) => {
setProcesos(procesos =>
procesos.map(proc =>
proc.id === procId ? { ...proc, estado: nuevoEstado } : proc
proc.id === procId ? { ...proc, status: mapEstadoToStatus(nuevoEstado) } : proc
)
);
};
@@ -850,7 +873,7 @@ export default function PedimentoDetail() {
organizacion: pedimentoData?.organizacion || 1 // Usar la organización del pedimento o default
};
const res = await postWithAuth(`${API_URL}/customs/procesamientopedimentos/`, body);
const res = await postWithAuth(`${API_URL}/tasks/tasks/`, body);
if (!res.ok) {
throw new Error(`Error al crear el servicio: ${nombreServicio}`);
@@ -871,7 +894,7 @@ export default function PedimentoDetail() {
...procesosFilters,
pedimento: id
};
fetchProcesamientoPedimentos(procesosPage, procesosPageSize, filters)
fetchTasks(procesosPage, procesosPageSize, filters)
.then((data) => {
setProcesos(data.results);
setProcesosCount(data.count);
@@ -893,33 +916,33 @@ export default function PedimentoDetail() {
setChangingStateId(proc.id);
// Cambiar estado visual a "Procesando" inmediatamente
updateProcesoEstado(proc.id, 2); // 2 = Procesando
updateProcesoEstado(proc.id, 'processing');
try {
const body = {
estado: 1, // Cambiar a En Espera
status: 'pending', // Cambiar a Pendiente
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);
const res = await putWithAuth(`${API_URL}/tasks/tasks/${proc.task_id}/`, body);
if (!res.ok) {
// Si falla, revertir a estado Error
updateProcesoEstado(proc.id, 4); // 4 = Error
// Si falla, cambiar a estado Error
updateProcesoEstado(proc.id, 'failed'); // Failed
throw new Error('Error al cambiar el estado del proceso');
}
// Cambiar estado visual a "En Espera" si fue exitoso
updateProcesoEstado(proc.id, 1); // 1 = En Espera
updateProcesoEstado(proc.id, 'pending');
showMessage('Estado cambiado a "En Espera" correctamente', 'success');
} catch (err) {
console.error('Error cambiando estado:', err);
updateProcesoEstado(proc.id, 4); // 4 = Error
updateProcesoEstado(proc.id, 'failed');
showMessage('Error al cambiar el estado del proceso: ' + err.message, 'error');
} finally {
setChangingStateId(null);
@@ -930,7 +953,7 @@ export default function PedimentoDetail() {
setExecutingId(proc.id);
// Cambiar estado visual a "Procesando" inmediatamente
updateProcesoEstado(proc.id, 2); // 2 = Procesando
updateProcesoEstado(proc.id, 'processing');
let endpoint = '';
// Determinar endpoint según el tipo de servicio
@@ -958,7 +981,7 @@ export default function PedimentoDetail() {
break;
default:
// Revertir estado si el servicio no es soportado
updateProcesoEstado(proc.id, proc.estado); // Revertir al estado original
updateProcesoEstado(proc.id, proc.status); // Revertir al estado original
showMessage('Este servicio no es compatible para ejecución directa.', 'error');
setExecutingId(null);
return;
@@ -974,17 +997,17 @@ export default function PedimentoDetail() {
if (!res.ok) {
// Si falla, cambiar estado a Error
updateProcesoEstado(proc.id, 4); // 4 = Error
updateProcesoEstado(proc.id, 'failed');
throw new Error('Error al ejecutar el servicio');
}
// Si es exitoso, cambiar estado a Finalizado
updateProcesoEstado(proc.id, 3); // 3 = Finalizado
updateProcesoEstado(proc.id, 'completed');
showMessage('El servicio se ha ejecutado correctamente', 'success');
} catch (err) {
// Cambiar estado a Error en caso de excepción
updateProcesoEstado(proc.id, 4); // 4 = Error
updateProcesoEstado(proc.id, 'failed');
if (err.message === 'SESSION_EXPIRED') {
showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error');
@@ -1257,6 +1280,57 @@ export default function PedimentoDetail() {
}
}
};
// Funciones de manejo de procesos
// Removed duplicate definitions
// Handle select all process checkboxes
const handleSelectAllProcesos = (e) => {
if (e.target.checked) {
const availableProcesos = procesos
.filter(proc => proc.status !== 'running' && proc.status !== 'completed')
.map(proc => proc.task_id);
setSelectedProcesos(availableProcesos);
setIsSelectAll(true);
} else {
setSelectedProcesos([]);
setIsSelectAll(false);
}
};
// Handle select individual process checkbox
const handleSelectProceso = (id, checked) => {
if (checked) {
setSelectedProcesos(prev => [...prev, id]);
} else {
setSelectedProcesos(prev => prev.filter(i => i !== id));
}
// Update isSelectAll
const availableProcesos = procesos
.filter(proc => proc.status !== 'running' && proc.status !== 'completed')
.map(proc => proc.task_id);
setIsSelectAll(availableProcesos.length === selectedProcesos.length + (checked ? 1 : -1));
};
const handlePasarPaginaAEspera = async () => {
const failedProcesses = procesos.filter(proc => proc.status === 'failed');
if (failedProcesses.length === 0) return;
for (const proceso of failedProcesses) {
await handlePasarAEspera(proceso);
}
};
const handlePasarSeleccionadosAEspera = async () => {
const failedSelectedProcesses = procesos.filter(
proc => selectedProcesos.includes(proc.task_id) && proc.status === 'failed'
);
if (failedSelectedProcesses.length === 0) return;
for (const proceso of failedSelectedProcesses) {
await handlePasarAEspera(proceso);
}
};
// Función para verificar servicios creados
const handleVerificarServiciosCreados = async () => {
@@ -1347,17 +1421,48 @@ export default function PedimentoDetail() {
// Fetch Procesos cuando sea necesario
useEffect(() => {
if (!id || activeTab !== 'procesos') return;
// Detectar si algún filtro cambió
const currentFilters = {
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 && procesosPage !== 1) {
setProcesosPage(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 };
setProcesosLoading(true);
setProcesosError('');
// Crear filtros incluyendo el pedimento
// Construir filtros
const filters = {
...procesosFilters,
pedimento: id // Filtrar por el pedimento actual
};
fetchProcesamientoPedimentos(procesosPage, procesosPageSize, filters)
if (sortField) {
const fieldMapping = {
'id': 'task_id',
'estado': 'status',
'pedimento': 'pedimento_app'
};
const mappedField = fieldMapping[sortField] || sortField;
filters['ordering'] = (sortOrder === 'desc' ? '-' : '') + mappedField;
}
fetchTasks(procesosPage, procesosPageSize, filters)
.then((data) => {
setProcesos(data.results);
setProcesosCount(data.count);
@@ -1372,7 +1477,7 @@ export default function PedimentoDetail() {
}
setProcesosLoading(false);
});
}, [id, activeTab, procesosPage, procesosPageSize, showMessage]);
}, [id, activeTab, procesosPage, procesosPageSize, sortField, sortOrder, procesosFilters, showMessage]);
// Resetear página de Procesos cuando cambie el pedimento
useEffect(() => {
@@ -3610,7 +3715,18 @@ export default function PedimentoDetail() {
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{procesos.map((proceso, index) => (
<tr key={`${proceso.id}-${index}`} className="hover:bg-gray-50">
<tr key={`${proceso.task_id}-${index}`} className={`hover:bg-gray-50 ${selectedProcesos.includes(proceso.task_id) ? 'bg-blue-50' : ''}`}>
<td className="px-3 py-4">
<div className="flex items-center justify-center">
<input
type="checkbox"
checked={selectedProcesos.includes(proceso.task_id)}
onChange={(e) => handleSelectProceso(proceso.task_id, e.target.checked)}
disabled={proceso.status === 'running' || proceso.status === 'completed'}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded disabled:opacity-50"
/>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
@@ -3622,7 +3738,10 @@ export default function PedimentoDetail() {
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
#{proceso.id}
#{proceso.task_id}
</div>
<div className="text-sm text-gray-500">
{proceso.pedimento_app}
</div>
</div>
</div>
@@ -3633,9 +3752,17 @@ export default function PedimentoDetail() {
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getEstadoColor(proceso.estado)}`}>
{getEstadoLabel(proceso.estado)}
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getTaskStatusColor(proceso.status)}`}>
{getTaskStatusLabel(proceso.status)}
</span>
{proceso.status === 'running' && (
<span className="ml-2 inline-flex items-center">
<svg className="animate-spin h-4 w-4 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{proceso.organizacion_name || 'N/A'}
@@ -3649,81 +3776,58 @@ export default function PedimentoDetail() {
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div className="flex items-center space-x-2">
{/* Botón Play (Ejecutar Servicio) */}
<button
onClick={() => handleEjecutarServicio(proceso)}
disabled={
executingId === proceso.id ||
proceso.estado === 2 || // En Proceso
proceso.estado === 3 || // Completado
proceso.estado === 5 // Cancelado
}
className={`inline-flex items-center p-2 border border-transparent rounded-md transition-colors duration-200 ${
executingId === proceso.id ||
proceso.estado === 2 ||
proceso.estado === 3 ||
proceso.estado === 5
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
: 'bg-green-100 text-green-700 hover:bg-green-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500'
}`}
title={
executingId === proceso.id ? 'Ejecutando...' :
proceso.estado === 2 ? 'No disponible - En proceso' :
proceso.estado === 3 ? 'No disponible - Completado' :
proceso.estado === 5 ? 'No disponible - Cancelado' :
'Ejecutar servicio'
}
>
{executingId === proceso.id ? (
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
)}
</button>
{isTaskActionable(proceso.status) && (
<button
onClick={() => handleEjecutarProceso(proceso)}
disabled={executingId === proceso.task_id || proceso.status === 'running'}
className={`group inline-flex items-center justify-center w-8 h-8 rounded-lg transition-all duration-200 ${
executingId === proceso.task_id || proceso.status === 'running'
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'text-green-700 bg-green-100 hover:bg-green-200 hover:text-green-800'
}`}
title={
executingId === proceso.task_id ? 'Ejecutando...' :
proceso.status === 'running' ? 'En proceso...' :
'Ejecutar servicio'
}
>
{executingId === proceso.task_id ? (
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<svg className="h-4 w-4 transition-transform group-hover:scale-110" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
)}
</button>
)}
{/* Botón Refresh (Pasar a Espera) */}
<button
onClick={() => handlePasarAEspera(proceso)}
disabled={
changingStateId === proceso.id ||
proceso.estado === 1 || // Pendiente
proceso.estado === 2 || // En Proceso
proceso.estado === 3 || // Completado
proceso.estado === 5 // Cancelado
}
className={`inline-flex items-center p-2 border border-transparent rounded-md transition-colors duration-200 ${
changingStateId === proceso.id ||
proceso.estado === 1 ||
proceso.estado === 2 ||
proceso.estado === 3 ||
proceso.estado === 5
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500'
}`}
title={
changingStateId === proceso.id ? 'Cambiando estado...' :
proceso.estado === 1 ? 'No disponible - Use Play para ejecutar' :
proceso.estado === 2 ? 'No disponible - En proceso' :
proceso.estado === 3 ? 'No disponible - Completado' :
proceso.estado === 5 ? 'No disponible - Cancelado' :
'Pasar a espera'
}
>
{changingStateId === proceso.id ? (
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
)}
</button>
{/* Botón Retry (Reintentar) */}
{proceso.status === 'failed' && (
<button
onClick={() => handlePasarAEspera(proceso)}
disabled={changingStateId === proceso.task_id}
className={`group inline-flex items-center justify-center w-8 h-8 rounded-lg transition-all duration-200 ${
changingStateId === proceso.task_id
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'text-yellow-700 bg-yellow-100 hover:bg-yellow-200 hover:text-yellow-800'
}`}
title={changingStateId === proceso.task_id ? 'Reiniciando...' : 'Reintentar proceso'}
>
{changingStateId === proceso.task_id ? (
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<svg className="h-4 w-4 transition-transform group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
)}
</button>
)}
</div>
</td>
</tr>