import React, { useEffect, useState, useRef } from 'react'; // Animación fade-in/slide-up para bloques const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`; if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-pedimento')) { const style = document.createElement('style'); style.id = 'fadein-slideup-pedimento'; style.innerHTML = fadeInSlideUp; document.head.appendChild(style); } import hljs from 'highlight.js/lib/core'; import xml from 'highlight.js/lib/languages/xml'; 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 { fetchPedimentoCoves, downloadCove, downloadAcuseCove } from '../api/coves'; import { fetchPedimentoEdocuments, downloadEdocument, downloadAcuseEdocument } from '../api/edocuments'; import { useParams, Link } from 'react-router-dom'; import { useNotification } from '../context/NotificationContext'; const API_URL = import.meta.env.VITE_EFC_API_URL; const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL; // Funciones auxiliares para estados y servicios (copiadas de procesos.js) const getEstadoLabel = (estado) => { const estados = { 1: 'Pendiente', 2: 'En Proceso', 3: 'Completado', 4: 'Error', 5: 'Cancelado' }; return estados[estado] || `Estado ${estado}`; }; const getEstadoColor = (estado) => { const colores = { 1: 'bg-yellow-100 text-yellow-800', 2: 'bg-blue-100 text-blue-800', 3: 'bg-green-100 text-green-800', 4: 'bg-red-100 text-red-800', 5: 'bg-gray-100 text-gray-800' }; return colores[estado] || 'bg-gray-100 text-gray-800'; }; const getServicioLabel = (servicio) => { const servicios = { 1: 'Estado de Pedimento', 3: 'Pedimento Completo', 4: 'Partidas', 5: 'Remesa', 6: 'Acuse ', 7: 'EDocuments', 8: 'Acuse de Cove', 9: 'Cove' }; return servicios[servicio] || `Servicio ${servicio}`; }; const getServicioColor = (servicio) => { const colores = { 1: 'bg-purple-100 text-purple-800', 2: 'bg-indigo-100 text-indigo-800', 3: 'bg-blue-100 text-blue-800', 4: 'bg-cyan-100 text-cyan-800', 5: 'bg-teal-100 text-teal-800', 6: 'bg-green-100 text-green-800', 7: 'bg-yellow-100 text-yellow-800', 8: 'bg-orange-100 text-orange-800', 9: 'bg-pink-100 text-pink-800' }; return colores[servicio] || 'bg-gray-100 text-gray-800'; }; // Función para formatear XML (pretty print) function formatXml(xml) { const PADDING = ' '; const reg = /(>)(<)(\/*)/g; let formatted = ''; let pad = 0; xml = xml.replace(reg, '$1\r\n$2$3'); xml.split(/\r?\n/).forEach((node) => { let indent = 0; if (node.match(/.+<\/\w[^>]*>$/)) { indent = 0; } else if (node.match(/^<\/\w/)) { if (pad !== 0) pad -= 1; } else if (node.match(/^<\w[^>]*[^\/]>/)) { indent = 1; } formatted += PADDING.repeat(pad) + node + '\r\n'; pad += indent; }); return formatted.trim(); } // Funci\u00f3n auxiliar para descargas individuales\nconst downloadFile = async (id, filename = 'archivo', showMessage) => {\n try {\n const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);\n \n if (!res.ok) {\n showMessage('Error en la descarga del archivo', 'error');\n return;\n }\n \n const blob = await res.blob();\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n a.remove();\n window.URL.revokeObjectURL(url);\n } catch (error) {\n console.error('Error downloading file:', error);\n if (error.message === 'SESSION_EXPIRED') {\n showMessage('Tu sesi\u00f3n ha expirado, por favor inicia sesi\u00f3n de nuevo.', 'error');\n } else {\n showMessage('Error al descargar el archivo', 'error');\n }\n }\n};\n\n// Funci\u00f3n auxiliar para descargas\nconst downloadBulkZip = async (ids, showMessage, pedimentoName) => {\n try {\n const response = await fetchWithAuth(`${API_URL}/record/documents/bulk-download/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ document_ids: ids })\n });\n \n if (!response.ok) throw new Error('Error en la descarga');\n \n const blob = await response.blob();\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `documentos_${pedimentoName || 'pedimento'}.zip`;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(url);\n document.body.removeChild(a);\n \n showMessage('Descarga iniciada exitosamente', 'success');\n } catch (error) {\n showMessage('Error en la descarga: ' + error.message, 'error');\n }\n}; export default function PedimentoDetail() { // Estados principales const [activeTab, setActiveTab] = useState('documentos'); const [pedimento, setPedimento] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const { id } = useParams(); const { showMessage } = useNotification(); // Estados para documentos const [documents, setDocuments] = useState([]); const [docsCount, setDocsCount] = useState(0); const [docsLoading, setDocsLoading] = useState(true); const [docsError, setDocsError] = useState(''); const [docsNext, setDocsNext] = useState(null); const [docsPrev, setDocsPrev] = useState(null); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [selected, setSelected] = useState([]); const [downloading, setDownloading] = useState(false); const [downloadingAll, setDownloadingAll] = useState(false); const [showFilters, setShowFilters] = useState(false); // Filtros simplificados de documentos (nuevo diseño) const [filters, setFilters] = useState({ name: '', document_type: '', extension: '', date: '', created_at__gte: '', created_at__lte: '', fuente: '', pedimento_numero: '' }); // Estados para filtros (legacy - para compatibilidad) const [documentTypeFilter, setDocumentTypeFilter] = useState(''); const [fileNameFilter, setFileNameFilter] = useState(''); const [extensionFilter, setExtensionFilter] = useState(''); const [dateFilter, setDateFilter] = useState(''); const [orderBy, setOrderBy] = useState(''); const [orderDir, setOrderDir] = useState('asc'); // Estados para COVEs const [coves, setCoves] = useState([]); const [covesCount, setCovesCount] = useState(0); const [covesLoading, setCovesLoading] = useState(false); const [covesError, setCovesError] = useState(''); const [covesPage, setCovesPage] = useState(1); const [covesPageSize, setCovesPageSize] = useState(10); const [covesFilters, setCovesFilters] = useState({ numero_cove: '', cove_descargado: '', acuse_cove_descargado: '', created_at__gte: '', created_at__lte: '' }); // Estados para EDocs const [edocs, setEdocs] = useState([]); const [edocsCount, setEdocsCount] = useState(0); const [edocsLoading, setEdocsLoading] = useState(false); const [edocsError, setEdocsError] = useState(''); const [edocsPage, setEdocsPage] = useState(1); const [edocsPageSize, setEdocsPageSize] = useState(10); const [edocsFilters, setEdocsFilters] = useState({ numero_edocument: '', clave: '', descripcion: '', edocument_descargado: '', acuse_descargado: '', created_at__gte: '', created_at__lte: '' }); // Estados para Partidas const [partidas, setPartidas] = useState([]); const [partidasCount, setPartidasCount] = useState(0); const [partidasLoading, setPartidasLoading] = useState(false); const [partidasError, setPartidasError] = useState(''); const [partidasPage, setPartidasPage] = useState(1); const [partidasPageSize, setPartidasPageSize] = useState(10); const [partidasFilters, setPartidasFilters] = useState({ numero_partida: '', descargado: '', numero_partida__gte: '', numero_partida__lte: '', created_at__gte: '', created_at__lte: '' }); const [selectedPartidas, setSelectedPartidas] = useState([]); const [downloadingPartidas, setDownloadingPartidas] = useState(false); const [downloadingAllPartidas, setDownloadingAllPartidas] = useState(false); // Función para obtener partidas const fetchPedimentoPartidas = async (page = 1, pageSize = 10, filters = {}) => { console.log('fetchPedimentoPartidas called with:', { page, pageSize, filters }); console.log('pedimento object:', pedimento); console.log('pedimento.id:', pedimento?.id); if (!pedimento?.id) { throw new Error('No hay ID de pedimento disponible'); } const params = new URLSearchParams({ pedimento: pedimento.id, page: page.toString(), page_size: pageSize.toString() }); // Solo agregar filtros que tengan valores Object.entries(filters).forEach(([key, value]) => { if (value && value.toString().trim() !== '') { params.append(key, value); } }); const finalUrl = `${API_URL}/customs/partidas/?${params}`; console.log('Final URL:', finalUrl); const response = await fetchWithAuth(finalUrl); if (!response.ok) { throw new Error('Error al obtener las partidas'); } const result = await response.json(); console.log('API Response:', result); return result; }; // Estados para Procesos const [procesos, setProcesos] = useState([]); const [procesosCount, setProcesosCount] = useState(0); const [procesosLoading, setProcesosLoading] = useState(false); const [procesosError, setProcesosError] = useState(''); const [procesosPage, setProcesosPage] = useState(1); const [procesosPageSize, setProcesosPageSize] = useState(10); const [procesosFilters, setProcesosFilters] = useState({}); // Estados para las acciones de procesos const [executingId, setExecutingId] = useState(null); const [changingStateId, setChangingStateId] = useState(null); const [creatingService, setCreatingService] = useState(null); // Estados para modal de preview const [previewOpen, setPreviewOpen] = useState(false); const [previewUrl, setPreviewUrl] = useState(''); const [previewType, setPreviewType] = useState(''); const [previewLoading, setPreviewLoading] = useState(false); const [previewError, setPreviewError] = useState(''); const [previewXml, setPreviewXml] = useState(''); const [previewXmlHtml, setPreviewXmlHtml] = useState(''); const [previewDoc, setPreviewDoc] = useState(null); const [previewContent, setPreviewContent] = useState(''); const [imageZoom, setImageZoom] = useState(1); // Refs const focusKeeperRef = useRef(null); // Opciones para tipos de documento const documentTypeOptions = [ { value: '', label: 'Todos' }, { value: 1, label: 'Pedimento Partida' }, { value: 2, label: 'Pedimento Completo' }, { value: 3, label: 'Remesa' }, { value: 4, label: 'Acuse' }, { value: 5, label: 'EDocument' }, { value: 7, label: 'Acuse Cove' }, { value: 8, label: 'Cove' }, { value: 9, label: 'Documento de digitalización' }, ]; // Helper para obtener el nombre legible del tipo de documento const getDocumentTypeName = (type) => { const found = documentTypeOptions.find(opt => String(opt.value) === String(type)); return found ? found.label : 'Documento'; }; // Función para cambiar de pestaña const handleTabChange = (tab) => { setActiveTab(tab); }; // Handler SPA para paginación const handlePageChange = (newPage, e) => { if (e && typeof e.preventDefault === 'function') e.preventDefault(); if (e && typeof e.stopPropagation === 'function') e.stopPropagation(); if (newPage < 1 || newPage > Math.max(1, Math.ceil(docsCount / pageSize)) || newPage === page) return; setPage(newPage); // Quitar el foco del botón activo para evitar salto de scroll if (typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } }; // Handler para cambio de ordenamiento const handleSort = (field) => { if (orderBy === field) { setOrderDir(orderDir === 'asc' ? 'desc' : 'asc'); } else { setOrderBy(field); setOrderDir('asc'); } }; // Funciones de selección const allDocIds = documents.map(doc => doc.id); const allSelected = selected.length === allDocIds.length && allDocIds.length > 0; const handleSelect = (id) => { setSelected(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); }; const handleSelectAll = () => { if (allSelected) setSelected([]); else setSelected(allDocIds); }; const handleBulkDownload = async (ids) => { setDownloading(true); await downloadBulkZip(ids, showMessage, pedimento?.pedimento); setDownloading(false); }; // Vista previa de documento const handlePreview = async (doc) => { setPreviewLoading(true); setPreviewError(''); setPreviewUrl(''); setPreviewType(''); setPreviewXml(''); setPreviewDoc(doc); setImageZoom(1); setPreviewContent(''); setPreviewOpen(true); try { const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${doc.id}/`); if (!res.ok) { setPreviewError('Error al obtener el archivo'); setPreviewLoading(false); return; } // Detectar tipo de archivo let type = ''; if (doc.extension) { if (doc.extension.toLowerCase() === 'pdf') type = 'pdf'; else if (["jpg","jpeg","png","gif","bmp","webp"].includes(doc.extension.toLowerCase())) type = 'img'; else if (doc.extension.toLowerCase() === 'xml') type = 'xml'; else if (["txt","log","csv"].includes(doc.extension.toLowerCase())) type = 'txt'; else type = 'other'; } setPreviewType(type); if (type === 'xml') { const text = await res.text(); const prettyText = formatXml(text); setPreviewXml(prettyText); // Formatear y resaltar XML try { const highlighted = hljs.highlight(prettyText, { language: 'xml' }).value; setPreviewXmlHtml(highlighted); } catch (e) { setPreviewXmlHtml(prettyText); } setPreviewLoading(false); } else if (type === 'txt') { const text = await res.text(); setPreviewContent(text); setPreviewLoading(false); } else { const blob = await res.blob(); const url = window.URL.createObjectURL(blob); setPreviewUrl(url); setPreviewLoading(false); } } catch (err) { console.error('Error in preview:', err); if (err.message === 'SESSION_EXPIRED') { setPreviewError('Tu sesión ha expirado, por favor inicia sesión de nuevo.'); } else { setPreviewError('Error al obtener el archivo'); } setPreviewLoading(false); } }; // Cerrar modal y limpiar blob const handleClosePreview = () => { setPreviewOpen(false); if (previewUrl) window.URL.revokeObjectURL(previewUrl); setPreviewUrl(''); setPreviewType(''); setPreviewError(''); setPreviewXml(''); setPreviewXmlHtml(''); setPreviewDoc(null); setPreviewContent(''); setImageZoom(1); }; // Funciones para el nuevo diseño de documentos const downloadAll = async () => { setDownloadingAll(true); try { const allDocIds = documents.map(doc => doc.id); await handleBulkDownload(allDocIds); } catch (error) { console.error('Error downloading all documents:', error); showMessage('Error al descargar todos los documentos', 'error'); } finally { setDownloadingAll(false); } }; const previewDocument = async (doc) => { await handlePreview(doc); }; const downloadDocument = async (doc) => { const fileName = doc.archivo ? doc.archivo.split('/').pop() : `documento_${doc.id}`; await downloadFile(doc.id, fileName, showMessage); }; const formatFileSize = (bytes) => { if (!bytes) return 'N/A'; const kb = bytes / 1024; if (kb < 1024) { return `${kb.toFixed(1)} KB`; } const mb = kb / 1024; return `${mb.toFixed(1)} MB`; }; const getFileExtension = (filename) => { if (!filename) return ''; const parts = filename.split('.'); return parts.length > 1 ? parts[parts.length - 1] : ''; }; const getFuenteName = (fuente) => { const fuentes = { 1: 'Manual', 2: 'VU', 3: 'Importación', 4: 'Sistema' }; return fuentes[fuente] || 'Desconocida'; }; // Efecto para cargar datos del pedimento useEffect(() => { const fetchPedimento = async () => { try { const response = await fetchWithAuth(`${API_URL}/customs/pedimentos/${id}/`); if (response.ok) { const data = await response.json(); setPedimento(data); } } catch (err) { setError('Error al cargar el pedimento'); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); } } finally { setLoading(false); } }; if (id) { fetchPedimento(); } }, [id, showMessage]); // Fetch paginated documents useEffect(() => { if (!id) return; console.log('Starting to fetch documents with:', { id, page, pageSize, filters, orderBy, orderDir }); setDocsLoading(true); setDocsError(''); // Construir parámetros de filtros usando el nuevo sistema const apiFilters = {}; if (filters.document_type) apiFilters.document_type = filters.document_type; if (filters.name) apiFilters.archivo__icontains = filters.name; if (filters.extension) apiFilters.extension = filters.extension; if (filters.date) apiFilters.created_at__date = filters.date; if (filters.created_at__gte) apiFilters.created_at__gte = filters.created_at__gte; if (filters.created_at__lte) apiFilters.created_at__lte = filters.created_at__lte; if (filters.fuente) apiFilters.fuente = filters.fuente; if (filters.pedimento_numero) apiFilters.pedimento_numero__icontains = filters.pedimento_numero; // Mantener compatibilidad con filtros legacy si están en uso if (documentTypeFilter) apiFilters.document_type = documentTypeFilter; if (fileNameFilter) apiFilters.archivo__icontains = fileNameFilter; if (extensionFilter) apiFilters.extension = extensionFilter; if (dateFilter) apiFilters.created_at__date = dateFilter; if (orderBy) { const orderField = orderBy === 'archivo' ? 'archivo' : orderBy === 'document_type' ? 'document_type' : orderBy === 'created_at' ? 'created_at' : orderBy === 'size' ? 'size' : orderBy; apiFilters.ordering = orderDir === 'desc' ? `-${orderField}` : orderField; } console.log('Calling fetchPedimentoDocuments with filters:', apiFilters); fetchPedimentoDocuments(id, page, pageSize, apiFilters) .then((data) => { console.log('Received documents data:', data); setDocuments(data.results); setDocsCount(data.count); setDocsNext(data.next); setDocsPrev(data.previous); setDocsLoading(false); }) .catch(err => { console.error('Error fetching documents:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); } else { setDocsError(err.message); } setDocsLoading(false); }); }, [id, page, pageSize, filters, documentTypeFilter, fileNameFilter, extensionFilter, dateFilter, orderBy, orderDir, showMessage]); // Resetear página cuando cambien los filtros useEffect(() => { setPage(1); }, [filters, documentTypeFilter, fileNameFilter, extensionFilter, dateFilter]); // Debug logs useEffect(() => { console.log('Documents data:', { documents, docsCount, page, pageSize, docsLoading, docsError }); }, [documents, docsCount, page, pageSize, docsLoading, docsError]); // Fetch COVEs cuando sea necesario useEffect(() => { if (!id || activeTab !== 'coves') return; setCovesLoading(true); setCovesError(''); fetchPedimentoCoves(id, covesPage, covesPageSize, covesFilters) .then((data) => { setCoves(data.results); setCovesCount(data.count); setCovesLoading(false); }) .catch(err => { console.error('Error fetching COVEs:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); } else { setCovesError(err.message); } setCovesLoading(false); }); }, [id, activeTab, covesPage, covesPageSize, covesFilters, showMessage]); // Resetear página de COVEs cuando cambien los filtros useEffect(() => { setCovesPage(1); }, [covesFilters]); // Fetch EDocs cuando sea necesario useEffect(() => { if (!id || activeTab !== 'edocs') return; setEdocsLoading(true); setEdocsError(''); fetchPedimentoEdocuments(id, edocsPage, edocsPageSize, edocsFilters) .then((data) => { setEdocs(data.results); setEdocsCount(data.count); setEdocsLoading(false); }) .catch(err => { console.error('Error fetching EDocs:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); } else { setEdocsError(err.message); } setEdocsLoading(false); }); }, [id, activeTab, edocsPage, edocsPageSize, edocsFilters, showMessage]); // Resetear página de EDocs cuando cambien los filtros useEffect(() => { setEdocsPage(1); }, [edocsFilters]); // Funciones para acciones de Procesos const updateProcesoEstado = (procId, nuevoEstado) => { setProcesos(procesos => procesos.map(proc => proc.id === procId ? { ...proc, estado: nuevoEstado } : proc ) ); }; // Verificar si existe un servicio específico const existeServicio = (tipoServicio) => { return procesos.some(proceso => proceso.servicio === tipoServicio); }; // Función para crear un nuevo servicio const handleCrearServicio = async (tipoServicio, nombreServicio) => { setCreatingService(tipoServicio); try { const body = { pedimento: id, servicio: tipoServicio, estado: 1, // Pendiente tipo_procesamiento: 1, organizacion: pedimentoData?.organizacion || 1 // Usar la organización del pedimento o default }; const res = await postWithAuth(`${API_URL}/customs/procesamientopedimentos/`, body); if (!res.ok) { throw new Error(`Error al crear el servicio: ${nombreServicio}`); } const nuevoServicio = await res.json(); // Agregar el nuevo servicio a la lista local setProcesos(prev => [...prev, nuevoServicio]); setProcesosCount(prev => prev + 1); showMessage(`Servicio "${nombreServicio}" creado correctamente`, 'success'); // Refrescar la lista después de un breve delay setTimeout(() => { // Re-fetch los procesos para asegurar consistencia const filters = { ...procesosFilters, pedimento: id }; fetchProcesamientoPedimentos(procesosPage, procesosPageSize, filters) .then((data) => { setProcesos(data.results); setProcesosCount(data.count); }) .catch(err => { console.error('Error refreshing procesos:', err); }); }, 1000); } catch (err) { console.error('Error creando servicio:', err); showMessage(`Error al crear el servicio: ${err.message}`, 'error'); } finally { setCreatingService(null); } }; 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 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 showMessage('Estado cambiado a "En Espera" correctamente', 'success'); } catch (err) { console.error('Error cambiando estado:', err); updateProcesoEstado(proc.id, 4); // 4 = Error showMessage('Error al cambiar el estado del proceso: ' + err.message, 'error'); } finally { setChangingStateId(null); } }; 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 showMessage('Este servicio no es compatible para ejecución directa.', 'error'); 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 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 if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al ejecutar servicio: ' + err.message, 'error'); } } finally { setExecutingId(null); } }; // Función para comparar Remesas vs COVEs en DB const handleCompararRemesasCoves = async () => { try { showMessage('Iniciando comparación de Remesas vs COVEs...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; // Llamada al endpoint de comparación (ajustar según la API real) const res = await postWithAuth(`${MICROSERVICE_URL}/services/comparar_remesas_coves`, body); if (!res.ok) { throw new Error('Error al realizar la comparación'); } const resultado = await res.json(); // Mostrar resultado de la comparación if (resultado.coincidencias) { showMessage(`Comparación completada: ${resultado.mensaje || 'Remesas y COVEs coinciden correctamente'}`, 'success'); } else { showMessage(`Discrepancias encontradas: ${resultado.mensaje || 'Se encontraron diferencias entre Remesas y COVEs'}`, 'warning'); } // Opcional: mostrar detalles en consola para debugging console.log('Resultado comparación Remesas vs COVEs:', resultado); } catch (err) { console.error('Error comparando Remesas vs COVEs:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al comparar Remesas vs COVEs: ' + err.message, 'error'); } } }; // Función para auditar Pedimento Completo const handleAuditarPedimentoCompleto = async () => { try { showMessage('Iniciando auditoría del Pedimento Completo...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/auditar_pedimento_completo`, body); if (!res.ok) { throw new Error('Error al auditar el pedimento completo'); } const resultado = await res.json(); if (resultado.auditoria_exitosa) { showMessage(`Auditoría completada: ${resultado.mensaje || 'Pedimento completo auditado correctamente'}`, 'success'); } else { showMessage(`Problemas encontrados: ${resultado.mensaje || 'Se encontraron inconsistencias en el pedimento'}`, 'warning'); } console.log('Resultado auditoría Pedimento Completo:', resultado); } catch (err) { console.error('Error auditando Pedimento Completo:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al auditar Pedimento Completo: ' + err.message, 'error'); } } }; // Función para auditar EDocs const handleAuditarEDocs = async () => { try { showMessage('Iniciando auditoría de EDocs...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/auditar_edocs`, body); if (!res.ok) { throw new Error('Error al auditar EDocs'); } const resultado = await res.json(); if (resultado.auditoria_exitosa) { showMessage(`Auditoría EDocs completada: ${resultado.mensaje || 'EDocs auditados correctamente'}`, 'success'); } else { showMessage(`Problemas en EDocs: ${resultado.mensaje || 'Se encontraron inconsistencias en EDocs'}`, 'warning'); } console.log('Resultado auditoría EDocs:', resultado); } catch (err) { console.error('Error auditando EDocs:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al auditar EDocs: ' + err.message, 'error'); } } }; // Función para auditar Partidas const handleAuditarPartidas = async () => { try { showMessage('Iniciando auditoría de Partidas...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/auditar_partidas`, body); if (!res.ok) { throw new Error('Error al auditar Partidas'); } const resultado = await res.json(); if (resultado.auditoria_exitosa) { showMessage(`Auditoría Partidas completada: ${resultado.mensaje || 'Partidas auditadas correctamente'}`, 'success'); } else { showMessage(`Problemas en Partidas: ${resultado.mensaje || 'Se encontraron inconsistencias en Partidas'}`, 'warning'); } console.log('Resultado auditoría Partidas:', resultado); } catch (err) { console.error('Error auditando Partidas:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al auditar Partidas: ' + err.message, 'error'); } } }; // Función para auditar Acuses const handleAuditarAcuses = async () => { try { showMessage('Iniciando auditoría de Acuses...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/auditar_acuses`, body); if (!res.ok) { throw new Error('Error al auditar Acuses'); } const resultado = await res.json(); if (resultado.auditoria_exitosa) { showMessage(`Auditoría Acuses completada: ${resultado.mensaje || 'Acuses auditados correctamente'}`, 'success'); } else { showMessage(`Problemas en Acuses: ${resultado.mensaje || 'Se encontraron inconsistencias en Acuses'}`, 'warning'); } console.log('Resultado auditoría Acuses:', resultado); } catch (err) { console.error('Error auditando Acuses:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al auditar Acuses: ' + err.message, 'error'); } } }; // Función para auditar COVEs const handleAuditarCoves = async () => { try { showMessage('Iniciando auditoría de COVEs...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/auditar_coves`, body); if (!res.ok) { throw new Error('Error al auditar COVEs'); } const resultado = await res.json(); if (resultado.auditoria_exitosa) { showMessage(`Auditoría COVEs completada: ${resultado.mensaje || 'COVEs auditados correctamente'}`, 'success'); } else { showMessage(`Problemas en COVEs: ${resultado.mensaje || 'Se encontraron inconsistencias en COVEs'}`, 'warning'); } console.log('Resultado auditoría COVEs:', resultado); } catch (err) { console.error('Error auditando COVEs:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al auditar COVEs: ' + err.message, 'error'); } } }; // Función para auditar Acuse de COVEs const handleAuditarAcuseCoves = async () => { try { showMessage('Iniciando auditoría de Acuse de COVEs...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/auditar_acuse_coves`, body); if (!res.ok) { throw new Error('Error al auditar Acuse de COVEs'); } const resultado = await res.json(); if (resultado.auditoria_exitosa) { showMessage(`Auditoría Acuse COVEs completada: ${resultado.mensaje || 'Acuse de COVEs auditado correctamente'}`, 'success'); } else { showMessage(`Problemas en Acuse COVEs: ${resultado.mensaje || 'Se encontraron inconsistencias en Acuse de COVEs'}`, 'warning'); } console.log('Resultado auditoría Acuse COVEs:', resultado); } catch (err) { console.error('Error auditando Acuse COVEs:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al auditar Acuse COVEs: ' + err.message, 'error'); } } }; // Función para verificar servicios creados const handleVerificarServiciosCreados = async () => { try { showMessage('Iniciando verificación de servicios creados...', 'info'); const body = { pedimento: id, organizacion: pedimento?.organizacion || 1 }; const res = await postWithAuth(`${MICROSERVICE_URL}/services/verificar_servicios_creados`, body); if (!res.ok) { throw new Error('Error al verificar servicios creados'); } const resultado = await res.json(); if (resultado.verificacion_exitosa) { showMessage(`Verificación completada: ${resultado.mensaje || 'Todos los servicios han sido verificados correctamente'}`, 'success'); } else { showMessage(`Problemas encontrados: ${resultado.mensaje || 'Se encontraron servicios faltantes o con problemas'}`, 'warning'); } // Mostrar resumen de servicios si está disponible if (resultado.resumen) { console.log('Resumen de servicios:', resultado.resumen); // Opcional: mostrar el resumen en una notificación adicional if (resultado.resumen.total_servicios) { showMessage(`Servicios encontrados: ${resultado.resumen.servicios_activos}/${resultado.resumen.total_servicios}`, 'info'); } } console.log('Resultado verificación servicios creados:', resultado); } catch (err) { console.error('Error verificando servicios creados:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', 'error'); } else { showMessage('Error al verificar servicios creados: ' + err.message, 'error'); } } }; // Fetch Partidas cuando sea necesario useEffect(() => { console.log('Partidas useEffect triggered:', { id, activeTab, pedimento: pedimento?.id, partidasPage, partidasPageSize, partidasFilters }); if (!id || activeTab !== 'partidas' || !pedimento) { console.log('Skipping partidas fetch due to missing conditions:', { hasId: !!id, isPartidasTab: activeTab === 'partidas', hasPedimento: !!pedimento }); return; } console.log('Starting partidas fetch...'); setPartidasLoading(true); setPartidasError(''); fetchPedimentoPartidas(partidasPage, partidasPageSize, partidasFilters) .then((data) => { console.log('Partidas fetch success:', data); setPartidas(data.results || []); setPartidasCount(data.count || 0); setPartidasLoading(false); }) .catch(err => { console.error('Error fetching Partidas:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); } else { setPartidasError(err.message); } setPartidasLoading(false); }); }, [id, activeTab, partidasPage, partidasPageSize, partidasFilters, showMessage, pedimento]); // Resetear página de Partidas cuando cambien los filtros useEffect(() => { setPartidasPage(1); }, [partidasFilters]); // Fetch Procesos cuando sea necesario useEffect(() => { if (!id || activeTab !== 'procesos') return; setProcesosLoading(true); setProcesosError(''); // Crear filtros incluyendo el pedimento const filters = { ...procesosFilters, pedimento: id // Filtrar por el pedimento actual }; fetchProcesamientoPedimentos(procesosPage, procesosPageSize, filters) .then((data) => { setProcesos(data.results); setProcesosCount(data.count); setProcesosLoading(false); }) .catch(err => { console.error('Error fetching Procesos:', err); if (err.message === 'SESSION_EXPIRED') { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); } else { setProcesosError(err.message); } setProcesosLoading(false); }); }, [id, activeTab, procesosPage, procesosPageSize, showMessage]); // Resetear página de Procesos cuando cambie el pedimento useEffect(() => { setProcesosPage(1); }, [id]); // Funciones para COVEs const handleCoveDownload = async (cove) => { try { await downloadCove(cove.id); showMessage(`COVE ${cove.numero_cove} descargado exitosamente`, 'success'); } catch (error) { showMessage(`Error al descargar COVE: ${error.message}`, 'error'); } }; const handleAcuseCoveDownload = async (cove) => { try { await downloadAcuseCove(cove.id); showMessage(`Acuse de COVE ${cove.numero_cove} descargado exitosamente`, 'success'); } catch (error) { showMessage(`Error al descargar acuse de COVE: ${error.message}`, 'error'); } }; // Funciones para EDocs const handleEdocDownload = async (edoc) => { try { await downloadEdocument(edoc.id); showMessage(`EDocs ${edoc.numero_edocument} descargado exitosamente`, 'success'); } catch (error) { showMessage(`Error al descargar EDocs: ${error.message}`, 'error'); } }; const handleAcuseEdocDownload = async (edoc) => { try { await downloadAcuseEdocument(edoc.id); showMessage(`Acuse de EDocs ${edoc.numero_edocument} descargado exitosamente`, 'success'); } catch (error) { showMessage(`Error al descargar acuse de EDocs: ${error.message}`, 'error'); } }; // Funciones para procesar peticiones const handleCoveRequest = async (cove) => { console.log('Request cove:', cove); showMessage(`Procesando petición para COVE #${cove.numero_cove}...`, 'info'); // Aquí implementarías la lógica de petición específica para COVEs }; const handleEdocRequest = async (edoc) => { console.log('Request edoc:', edoc); showMessage(`Procesando petición para EDocs #${edoc.numero_edocument}...`, 'info'); // Aquí implementarías la lógica de petición específica para EDocs }; const formatDate = (dateString) => { if (!dateString) return 'N/A'; return new Date(dateString).toLocaleDateString('es-ES', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }; // Funciones para Partidas const handlePartidaPreview = async (partida) => { // Pasar un objeto que simule un documento con el ID del pedimento const partidaDoc = { ...partida, id: pedimento.id }; await handlePreview(partidaDoc); }; const handlePartidaDownload = async (partida) => { const fileName = partida.archivo ? partida.archivo.split('/').pop() : `partida_${partida.numero_partida}_${partida.id}`; await downloadFile(pedimento.id, fileName, showMessage); }; const handlePartidaRequest = async (partida) => { console.log('Request partida:', partida); showMessage(`Procesando petición para partida #${partida.numero_partida}...`, 'info'); // Aquí implementarías la lógica de petición específica para partidas }; // Funciones de selección para partidas const allPartidaIds = partidas.map(partida => partida.id); const allPartidasSelected = selectedPartidas.length === allPartidaIds.length && allPartidaIds.length > 0; const handleSelectPartida = (id) => { setSelectedPartidas(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); }; const handleSelectAllPartidas = () => { if (allPartidasSelected) setSelectedPartidas([]); else setSelectedPartidas(allPartidaIds); }; const handleBulkDownloadPartidas = async (ids) => { setDownloadingPartidas(true); await downloadBulkZip(ids, showMessage, `${pedimento?.pedimento}_partidas`); setDownloadingPartidas(false); }; const downloadAllPartidas = async () => { setDownloadingAllPartidas(true); try { const allPartidaIds = partidas.map(partida => partida.id); await handleBulkDownloadPartidas(allPartidaIds); } catch (error) { console.error('Error downloading all partidas:', error); showMessage('Error al descargar todas las partidas', 'error'); } finally { setDownloadingAllPartidas(false); } }; // Estados de carga if (loading) return (
Cargando detalle del pedimento...
{error}
← Volver a expedientesInformación completa del pedimento y documentos asociados
{docsCount > 0 && ({error}
No se encontraron documentos para este pedimento.
| Fuente | Acciones | |||||
|---|---|---|---|---|---|---|
|
{doc.archivo ? doc.archivo.split('/').pop() : 'Sin nombre'}
Pedimento: {doc.pedimento_numero || 'N/A'}
|
{getDocumentTypeName(doc.document_type)} | {doc.extension ? doc.extension.toUpperCase() : 'N/A'} | {formatFileSize(doc.size)} | {getFuenteName(doc.fuente)} | {formatDate(doc.created_at)} |
|
{partidasError}
No se encontraron partidas para este pedimento.
| ID | Número de Partida | Estado de Descarga | Fecha de Creación | Última Actualización | Acciones | |
|---|---|---|---|---|---|---|
| handleSelectPartida(partida.id)} /> |
#{partida.id}
|
{partida.numero_partida}
|
{partida.descargado ? 'Descargado' : 'Pendiente'} | {partida.created_at ? formatDate(partida.created_at) : 'N/A'} | {partida.updated_at ? formatDate(partida.updated_at) : 'N/A'} |
{/* Botón Petición (solo activo si está pendiente) */}
|
{covesError}
No se encontraron COVEs para este pedimento.
| Número COVE | Fecha de Creación | Estado COVE | Estado Acuse | Acciones |
|---|---|---|---|---|
|
{cove.numero_cove}
|
{formatDate(cove.created_at)} | {cove.cove_descargado ? 'Descargado' : 'Pendiente'} | {cove.acuse_cove_descargado ? 'Descargado' : 'Pendiente'} |
{/* Botón Procesar petición (solo activo si está pendiente) */}
|
{edocsError}
No se encontraron documentos electrónicos para este pedimento.
| Número EDocs | Clave | Descripción | Fecha de Creación | Estado EDocs | Estado Acuse | Acciones |
|---|---|---|---|---|---|---|
|
{edoc.numero_edocument}
|
{edoc.clave || 'N/A'} | {edoc.descripcion || 'Sin descripción'} | {formatDate(edoc.created_at)} | {edoc.edocument_descargado ? 'Descargado' : 'Pendiente'} | {edoc.acuse_descargado ? 'Descargado' : 'Pendiente'} |
{/* Botón Procesar petición (solo activo si está pendiente) */}
|
{procesosError}
No se encontraron procesos para este pedimento.
| ID Proceso | Servicio | Estado | Organización | Fecha Creación | Última Actualización | Acciones |
|---|---|---|---|---|---|---|
|
#{proceso.id}
|
{getServicioLabel(proceso.servicio)} | {getEstadoLabel(proceso.estado)} | {proceso.organizacion_name || 'N/A'} | {formatDate(proceso.created_at)} | {formatDate(proceso.updated_at)} |
{/* Botón Ejecutar Servicio - solo para estados Pendiente o Error */}
{(proceso.estado === 1 || proceso.estado === 4) && (
)}
{/* Botón Pasar a Espera - solo para estados En Proceso, Completado o Error */}
{(proceso.estado === 2 || proceso.estado === 3 || proceso.estado === 4) && (
)}
{/* Mostrar texto si no hay acciones disponibles */}
{proceso.estado !== 1 && proceso.estado !== 2 && proceso.estado !== 3 && proceso.estado !== 4 && (
Sin acciones
)}
|
Documentos
{docsCount || 0}
COVEs
{covesCount || 0}
EDocs
{edocsCount || 0}
Procesos
{procesosCount || 0}
Pedimento procesado correctamente
Todos los documentos han sido validados y procesados
{new Date().toLocaleDateString('es-ES')}
Documentos cargados
Se han cargado {docsCount || 0} documentos al expediente
{pedimento?.created_at ? new Date(pedimento.created_at).toLocaleDateString('es-ES') : 'Fecha no disponible'}
Expediente creado
Expediente #{pedimento?.pedimento_app || 'N/A'} iniciado
{pedimento?.created_at ? new Date(pedimento.created_at).toLocaleDateString('es-ES') : 'Fecha no disponible'}
Preparando vista previa
{previewError}
{previewContent || 'Contenido no disponible'}
Este tipo de archivo no se puede mostrar en el navegador
Descargar archivoNo se pudo cargar el documento