From 347d59b6c1daae8c29e0d7e2de06d37fca64d7fa Mon Sep 17 00:00:00 2001 From: Kevin Rosales Date: Wed, 1 Oct 2025 21:12:38 -0600 Subject: [PATCH] Se modifico Pedimento detail se empezaron a agregar elementos del auditor y detalle completo del pedimento --- src/api/coves.js | 97 + src/api/edocuments.js | 103 + src/api/pedimentoDocuments.ts | 31 +- src/api/procesos.js | 109 + src/pages/Datastage.jsx | 18 +- src/pages/Documents.jsx | 42 +- src/pages/Expedientes.jsx | 82 +- src/pages/PedimentoDetail.jsx | 3847 +++++++++++++++++++++++++-------- src/pages/Procesos.jsx | 6 +- 9 files changed, 3329 insertions(+), 1006 deletions(-) create mode 100644 src/api/coves.js create mode 100644 src/api/edocuments.js create mode 100644 src/api/procesos.js diff --git a/src/api/coves.js b/src/api/coves.js new file mode 100644 index 0000000..c41b808 --- /dev/null +++ b/src/api/coves.js @@ -0,0 +1,97 @@ +import { fetchWithAuth } from '../fetchWithAuth'; + +const API_BASE_URL = process.env.NODE_ENV === 'production' + ? 'https://your-production-api.com/api/v1' + : 'http://192.168.1.79:8000/api/v1'; + +export const fetchPedimentoCoves = async (pedimentoId, page = 1, pageSize = 10, filters = {}) => { + try { + const params = new URLSearchParams({ + pedimento: pedimentoId, + page: page.toString(), + page_size: pageSize.toString(), + }); + + // Agregar filtros si existen + if (filters.numero_cove) { + params.append('numero_cove__icontains', filters.numero_cove); + } + if (filters.cove_descargado !== undefined && filters.cove_descargado !== '') { + params.append('cove_descargado', filters.cove_descargado); + } + if (filters.acuse_cove_descargado !== undefined && filters.acuse_cove_descargado !== '') { + params.append('acuse_cove_descargado', filters.acuse_cove_descargado); + } + if (filters.date_from) { + params.append('created_at__gte', filters.date_from); + } + if (filters.date_to) { + params.append('created_at__lte', filters.date_to); + } + + const response = await fetchWithAuth(`${API_BASE_URL}/customs/coves/?${params}`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + return { + results: data.results, + count: data.count, + next: data.next, + previous: data.previous + }; + } catch (error) { + console.error('Error fetching COVEs:', error); + throw error; + } +}; + +export const downloadCove = async (coveId) => { + try { + const response = await fetchWithAuth(`${API_BASE_URL}/customs/coves/${coveId}/download/`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = `COVE_${coveId}.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error) { + console.error('Error downloading COVE:', error); + throw error; + } +}; + +export const downloadAcuseCove = async (coveId) => { + try { + const response = await fetchWithAuth(`${API_BASE_URL}/customs/coves/${coveId}/download-acuse/`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = `ACUSE_COVE_${coveId}.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error) { + console.error('Error downloading COVE acuse:', error); + throw error; + } +}; \ No newline at end of file diff --git a/src/api/edocuments.js b/src/api/edocuments.js new file mode 100644 index 0000000..1568368 --- /dev/null +++ b/src/api/edocuments.js @@ -0,0 +1,103 @@ +import { fetchWithAuth } from '../fetchWithAuth'; + +const API_BASE_URL = process.env.NODE_ENV === 'production' + ? 'https://your-production-api.com/api/v1' + : 'http://192.168.1.79:8000/api/v1'; + +export const fetchPedimentoEdocuments = async (pedimentoId, page = 1, pageSize = 10, filters = {}) => { + try { + const params = new URLSearchParams({ + pedimento: pedimentoId, + page: page.toString(), + page_size: pageSize.toString(), + }); + + // Agregar filtros si existen + if (filters.numero_edocument) { + params.append('numero_edocument__icontains', filters.numero_edocument); + } + if (filters.clave) { + params.append('clave__icontains', filters.clave); + } + if (filters.descripcion) { + params.append('descripcion__icontains', filters.descripcion); + } + if (filters.edocument_descargado !== undefined && filters.edocument_descargado !== '') { + params.append('edocument_descargado', filters.edocument_descargado); + } + if (filters.acuse_descargado !== undefined && filters.acuse_descargado !== '') { + params.append('acuse_descargado', filters.acuse_descargado); + } + if (filters.date_from) { + params.append('created_at__gte', filters.date_from); + } + if (filters.date_to) { + params.append('created_at__lte', filters.date_to); + } + + const response = await fetchWithAuth(`${API_BASE_URL}/customs/edocuments/?${params}`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + return { + results: data.results, + count: data.count, + next: data.next, + previous: data.previous + }; + } catch (error) { + console.error('Error fetching EDocs:', error); + throw error; + } +}; + +export const downloadEdocument = async (edocId) => { + try { + const response = await fetchWithAuth(`${API_BASE_URL}/customs/edocuments/${edocId}/download/`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = `EDOC_${edocId}.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error) { + console.error('Error downloading EDocs:', error); + throw error; + } +}; + +export const downloadAcuseEdocument = async (edocId) => { + try { + const response = await fetchWithAuth(`${API_BASE_URL}/customs/edocuments/${edocId}/download-acuse/`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = `ACUSE_EDOC_${edocId}.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error) { + console.error('Error downloading EDocs acuse:', error); + throw error; + } +}; \ No newline at end of file diff --git a/src/api/pedimentoDocuments.ts b/src/api/pedimentoDocuments.ts index 16992c2..da5564e 100644 --- a/src/api/pedimentoDocuments.ts +++ b/src/api/pedimentoDocuments.ts @@ -5,10 +5,12 @@ export interface PedimentoDocument { id: string; organizacion: string; pedimento: string; + pedimento_numero: string; archivo: string; document_type: number; size: number; extension: string; + fuente: number; created_at: string; updated_at: string; } @@ -20,17 +22,38 @@ export interface PedimentoDocumentsResponse { results: PedimentoDocument[]; } +export interface DocumentFilters { + document_type?: string; + archivo__icontains?: string; + extension?: string; + created_at__date?: string; + ordering?: string; +} + const API_URL = (import.meta as any).env.VITE_EFC_API_URL; export async function fetchPedimentoDocuments( pedimentoId: string, page: number = 1, - pageSize: number = 10 + pageSize: number = 10, + filters: DocumentFilters = {} ): Promise { try { - const res = await fetchWithAuth( - `${API_URL}/record/documents/?page=${page}&page_size=${pageSize}&pedimento=${pedimentoId}` - ); + // Construir URL con filtros + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + pedimento: pedimentoId + }); + + // Agregar filtros si existen + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== '') { + params.append(key, value.toString()); + } + }); + + const res = await fetchWithAuth(`${API_URL}/record/documents/?${params.toString()}`); if (!res.ok) { throw new Error('No autorizado o error en la petición'); diff --git a/src/api/procesos.js b/src/api/procesos.js new file mode 100644 index 0000000..d1eec55 --- /dev/null +++ b/src/api/procesos.js @@ -0,0 +1,109 @@ +import { fetchWithAuth } from '../fetchWithAuth'; + +const API_BASE_URL = process.env.NODE_ENV === 'production' + ? 'https://your-production-api.com/api/v1' + : 'http://192.168.1.79:8000/api/v1'; + +export const fetchPedimentoProcesos = async (pedimentoId, page = 1, pageSize = 10, filters = {}) => { + try { + const params = new URLSearchParams({ + pedimento: pedimentoId, + page: page.toString(), + page_size: pageSize.toString(), + }); + + // Agregar filtros si existen + if (filters.estado !== undefined && filters.estado !== '') { + params.append('estado', filters.estado); + } + if (filters.servicio !== undefined && filters.servicio !== '') { + params.append('servicio', filters.servicio); + } + if (filters.organizacion_name) { + params.append('organizacion_name__icontains', filters.organizacion_name); + } + if (filters.date_from) { + params.append('created_at__gte', filters.date_from); + } + if (filters.date_to) { + params.append('created_at__lte', filters.date_to); + } + if (filters.updated_from) { + params.append('updated_at__gte', filters.updated_from); + } + if (filters.updated_to) { + params.append('updated_at__lte', filters.updated_to); + } + + const response = await fetchWithAuth(`${API_BASE_URL}/customs/procesamientopedimentos/?${params}`); + + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + return { + results: data.results, + count: data.count, + next: data.next, + previous: data.previous + }; + } catch (error) { + console.error('Error fetching Procesos:', error); + throw error; + } +}; + +// Mapeo de estados +export const getEstadoLabel = (estado) => { + const estados = { + 1: 'Pendiente', + 2: 'En Proceso', + 3: 'Completado', + 4: 'Error', + 5: 'Cancelado' + }; + return estados[estado] || `Estado ${estado}`; +}; + +export 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'; +}; + +// Mapeo de servicios +export const getServicioLabel = (servicio) => { + const servicios = { + 1: 'Digitalización', + 2: 'Validación', + 3: 'Procesamiento SAT', + 4: 'Generación COVEs', + 5: 'Generación EDocs', + 6: 'Envío VUCEM', + 7: 'Clasificación', + 8: 'Archivo Digital', + 9: 'Notificaciones' + }; + return servicios[servicio] || `Servicio ${servicio}`; +}; + +export 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'; +}; \ No newline at end of file diff --git a/src/pages/Datastage.jsx b/src/pages/Datastage.jsx index 91550c2..363a1d9 100644 --- a/src/pages/Datastage.jsx +++ b/src/pages/Datastage.jsx @@ -351,16 +351,16 @@ export default function Datastage() { {/* Tabla para pantallas grandes */}
- - +
+ - - - - - - - + + + + + + + diff --git a/src/pages/Documents.jsx b/src/pages/Documents.jsx index 4a8389f..30238a3 100644 --- a/src/pages/Documents.jsx +++ b/src/pages/Documents.jsx @@ -415,28 +415,29 @@ export default function Documents() { {/* Tabla para pantallas grandes */}
6 ? 'auto' : 'hidden', position: 'relative' }}> -
IDArchivoContribuyenteProcesadoCreadoActualizadoAccionesIDArchivoContribuyenteProcesadoCreadoActualizadoAcciones
- - - - - - - - - +
+
- { if (el) el.indeterminate = someSelected; }} - onChange={handleSelectAll} - className="h-3.5 w-3.5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded align-middle" - style={{ minWidth: '14px', minHeight: '14px' }} - /> - PedimentoArchivoTipoTamañoExtensiónAcciones
+ + + + + + + + + - + {/* Loader/Error/Empty state dentro del área de la tabla, sin cambiar el layout */} {loading ? ( @@ -536,6 +537,7 @@ export default function Documents() { )}
+ { if (el) el.indeterminate = someSelected; }} + onChange={handleSelectAll} + className="h-3.5 w-3.5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded align-middle" + style={{ minWidth: '14px', minHeight: '14px' }} + /> + PedimentoArchivoTipoTamañoExtensiónAcciones
+
diff --git a/src/pages/Expedientes.jsx b/src/pages/Expedientes.jsx index 6f3ebd1..a1303ca 100644 --- a/src/pages/Expedientes.jsx +++ b/src/pages/Expedientes.jsx @@ -410,27 +410,27 @@ export default function Documents() {
{/* Vista de tabla para pantallas grandes */}
-
- - +
+
+ - - - - - - - - - - - + + + + + + + + + + + - + {loading ? ( - ) : error ? ( - ) : currentDocuments.length > 0 ? ( currentDocuments.map(ped => ( - - + - - - - - - + + + + + - - - + + -
PedimentoFecha de pagoContribuyenteCURP ApoderadoPartidasFecha de CargaTipo OperacionClaveNo. ArchivosPeso TotalExpedientePedimentoFecha PagoContribuyenteCURP Apod.PartidasF. CargaTipo Op.ClaveArchivosPeso TotalExpediente
+
Cargando expedientes... @@ -439,7 +439,7 @@ export default function Documents() {
+
@@ -452,35 +452,39 @@ export default function Documents() {
+
{ped.pedimento_app} {ped.fecha_pago}{ped.contribuyente}{ped.curp_apoderado}{ped.numero_partidas}{ped.created_at ? ped.created_at.slice(0, 10) : ''} - {ped.tipo_operacion === 1 - ? 'Importación' - : ped.tipo_operacion === 2 - ? 'Exportación' - : ped.tipo_operacion} + {ped.fecha_pago}{ped.contribuyente}{ped.curp_apoderado}{ped.numero_partidas}{ped.created_at ? ped.created_at.slice(0, 10) : ''} + + {ped.tipo_operacion === 1 + ? 'Import.' + : ped.tipo_operacion === 2 + ? 'Export.' + : ped.tipo_operacion} + {ped.clave_pedimento}{ped.documentos_count} - {typeof ped.documentos_peso_total === 'number' - ? (ped.documentos_peso_total / 1024).toFixed(2) + ' MB' - : ped.documentos_peso_total} + {ped.clave_pedimento}{ped.documentos_count} +
+ {typeof ped.documentos_peso_total === 'number' + ? (ped.documentos_peso_total / 1024).toFixed(2) + ' MB' + : ped.documentos_peso_total} +
+ - +
diff --git a/src/pages/PedimentoDetail.jsx b/src/pages/PedimentoDetail.jsx index 5425a7d..d0d4cb8 100644 --- a/src/pages/PedimentoDetail.jsx +++ b/src/pages/PedimentoDetail.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +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); } @@ -14,107 +14,181 @@ 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 type removed for JSX compatibility import { fetchPedimentoDocuments } from '../api/pedimentoDocuments'; -import { fetchWithAuth, postWithAuth } from '../fetchWithAuth'; +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; -const downloadFile = async (id, filename = 'archivo', showMessage) => { - try { - const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`); - - if (!res.ok) { - showMessage('Error en la descarga del archivo', 'error'); - return; - } - - const blob = await res.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - } catch (error) { - console.error('Error downloading file:', error); - if (error.message === 'SESSION_EXPIRED') { - showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); - } else { - showMessage('Error al descargar el archivo', 'error'); - } - } -}; - -const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => { - if (!ids.length) { - showMessage('Selecciona al menos un documento.', 'error'); - return; - } - - try { - const res = await postWithAuth(`${API_URL}/record/documents/bulk-download/`, { - document_ids: ids, - pedimento_nombre: pedimentoNombre - }); - - if (!res.ok) { - showMessage('Error en la descarga masiva', 'error'); - return; - } - - const blob = await res.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${pedimentoNombre || 'documentos'}.zip`; - document.body.appendChild(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - } catch (error) { - console.error('Error in bulk download:', error); - if (error.message === 'SESSION_EXPIRED') { - showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); - } else { - showMessage('Error en la descarga masiva', 'error'); - } - } -}; - -import { useRef, useLayoutEffect } from 'react'; -export default function PedimentoDetail() { - // 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(); - } - // 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'; +// 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' }; - // Estado para modal de preview + 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: '', + 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: '', + date_from: '', + date_to: '' + }); + + // 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: '', + date_from: '', + date_to: '' + }); + + // 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(''); @@ -122,24 +196,34 @@ export default function PedimentoDetail() { const [previewError, setPreviewError] = useState(''); const [previewXml, setPreviewXml] = useState(''); const [previewXmlHtml, setPreviewXmlHtml] = useState(''); - // Filtros y ordenamiento - const [fileNameFilter, setFileNameFilter] = useState(''); - const [extensionFilter, setExtensionFilter] = useState(''); - const [dateFilter, setDateFilter] = useState(''); - const [orderBy, setOrderBy] = useState(''); - const [orderDir, setOrderDir] = useState('asc'); - const { id } = useParams(); - const [pedimento, setPedimento] = useState(null); - const [docsLoading, setDocsLoading] = useState(true); - const [docsError, setDocsError] = useState(''); -const [documents, setDocuments] = useState([]); - const [docsCount, setDocsCount] = useState(0); -const [docsNext, setDocsNext] = useState(null); -const [docsPrev, setDocsPrev] = useState(null); - // Refuerza la paginación SPA: nunca recarga la página, solo cambia el estado local - const [page, setPage] = useState(1); - // Ref para foco oculto (accesibilidad, opcional) + 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: 'Pedimento Remesas' }, + { value: 4, label: 'Pedimento Acuse' }, + { value: 5, label: 'Pedimento EDocument' }, + { value: 6, label: 'Estado Pedimento' }, + ]; + + // 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) => { @@ -153,95 +237,17 @@ const [docsPrev, setDocsPrev] = useState(null); } }; - // Eliminado manejo manual de scroll para evitar saltos - const [pageSize, setPageSize] = useState(10); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [selected, setSelected] = useState([]); - const [downloading, setDownloading] = useState(false); - const [documentTypeFilter, setDocumentTypeFilter] = useState(''); - const documentTypeOptions = [ - { value: '', label: 'Todos' }, - { value: 1, label: 'Pedimento Partida' }, - { value: 2, label: 'Pedimento Completo' }, - { value: 3, label: 'Pedimento Remesas' }, - { value: 4, label: 'Pedimento Acuse' }, - { value: 5, label: 'Pedimento EDocument' }, - { value: 6, label: 'Estado Pedimento' }, - ]; - const { showMessage } = useNotification(); - - useEffect(() => { - fetchWithAuth(`${API_URL}/customs/pedimentos/${id}/`) - .then(res => { - if (!res.ok) throw new Error('No autorizado o error en la petición'); - return res.json(); - }) - .then(data => { - setPedimento(data); - setLoading(false); - }) - .catch(err => { - console.error('Error fetching pedimento:', err); - if (err.message === 'SESSION_EXPIRED') { - showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); - } else { - setError(err.message); - } - setLoading(false); - }); - }, [id, showMessage]); - - // Fetch paginated documents - useEffect(() => { - if (!id) return; - setDocsLoading(true); - setDocsError(''); - fetchPedimentoDocuments(id, page, pageSize) - .then((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, showMessage]); - - if (loading) return ( -
-
- - - - -

Cargando detalle de pedimento...

-
-
- ); - - if (error) return ( -
-
-
- - - -

{error}

-
-
-
- ); - if (!pedimento) return null; + // 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; @@ -267,6 +273,9 @@ const [docsPrev, setDocsPrev] = useState(null); setPreviewUrl(''); setPreviewType(''); setPreviewXml(''); + setPreviewDoc(doc); + setImageZoom(1); + setPreviewContent(''); setPreviewOpen(true); try { const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${doc.id}/`); @@ -283,9 +292,11 @@ const [docsPrev, setDocsPrev] = useState(null); 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); @@ -298,6 +309,10 @@ const [docsPrev, setDocsPrev] = useState(null); 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); @@ -324,104 +339,2727 @@ const [docsPrev, setDocsPrev] = useState(null); setPreviewError(''); setPreviewXml(''); setPreviewXmlHtml(''); + setPreviewDoc(null); + setPreviewContent(''); + setImageZoom(1); }; - // Lógica para botones numerados de paginación - const totalPages = Math.max(1, Math.ceil(docsCount / pageSize)); - let pageNumbers = []; - if (totalPages <= 7) { - pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1); - } else { - if (page <= 4) { - pageNumbers = [1, 2, 3, 4, 5, '...', totalPages]; - } else if (page >= totalPages - 3) { - pageNumbers = [1, '...', totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages]; - } else { - pageNumbers = [1, '...', page - 1, page, page + 1, '...', totalPages]; + // 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.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 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'); + } + }; + + 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' + }); + }; + + // Estados de carga + if (loading) return ( +
+
+
+

Cargando detalle del pedimento...

+
+
+ ); + + if (error) return ( +
+
+
+ + + +
+

Error al cargar

+

{error}

+ + ← Volver a expedientes + +
+
+ ); return (
- {/* Modal de vista previa mejorado */} - {previewOpen && ( -
-
- {/* Header del modal con gradiente */} -
-
-
- - - + + {/* Header */} +
+
+ + + + + Volver a la lista + +

+ Detalle de Pedimento +

+

+ Información completa del pedimento y documentos asociados +

+ {docsCount > 0 && ( +
+ + 📄 {docsCount} documentos + +
+ )} +
+
+ + {/* Información del Pedimento */} + {pedimento && ( +
+
+
+
+ + + +
+

Información General

+
+
+
+
+
+
+ + + + Pedimento +
+
{pedimento.pedimento_app}
+
+ +
+
+ + + + Contribuyente +
+
{pedimento.contribuyente}
+
+ +
+
+ + + + Fecha de Pago +
+
{pedimento.fecha_pago}
+
+
+
+
+ )} + + {/* Sistema de pestañas */} +
+ + {/* Navegación de pestañas */} +
+ +
+ + {/* Contenido de las pestañas */} + {activeTab === 'documentos' && ( +
+ {/* Header de la sección */} +
+
+
+

+ Documentos del Pedimento +

+ + {docsCount} documentos + +
+ + {documents.length > 0 && ( +
+ + + +
+ )} +
+ + {/* Filtros expandibles */} + {showFilters && ( +
+
+ + setFilters(prev => ({ ...prev, name: e.target.value }))} + placeholder="Nombre del archivo..." + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + setFilters(prev => ({ ...prev, pedimento_numero: e.target.value }))} + placeholder="Número de pedimento..." + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + setFilters(prev => ({ ...prev, date: e.target.value }))} + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ +
+
+ )} +
+ {loading ? ( +
+
+ Cargando documentos... +
+ ) : error ? ( +
+
+ +
-

Vista previa de documento

+

Error al cargar documentos

+

{error}

+ ) : documents.length === 0 ? ( +
+ + + +

No hay documentos

+

+ No se encontraron documentos para este pedimento. +

+
+ ) : ( +
+ {/* Tabla de documentos */} +
+ + + + + + + + + + + + + + {documents.map((doc, index) => ( + + + + + + + + + + ))} + +
+ + + + + + + + + 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)} + +
+ + +
+
+
+
+ )} + + {/* Paginación para documentos */} + {docsCount > 0 && ( +
+
+ + Mostrando {((page - 1) * pageSize) + 1} a{' '} + {Math.min(page * pageSize, docsCount)} de{' '} + {docsCount} documentos + + +
+ +
+ + + + Página {page} de {Math.ceil(docsCount / pageSize)} + + + +
+
+ )} +
+ )} + + {activeTab === 'coves' && ( +
+ {/* Header de la sección */} +
+
+
+

+ COVEs del Pedimento +

+ + {covesCount} COVEs + +
+
+ + {/* Filtros */} +
+
+ + setCovesFilters(prev => ({ ...prev, numero_cove: e.target.value }))} + placeholder="Número COVE..." + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + setCovesFilters(prev => ({ ...prev, date_from: e.target.value }))} + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+
+
+ + {covesLoading ? ( +
+
+ Cargando COVEs... +
+ ) : covesError ? ( +
+
+ + + +
+

Error al cargar COVEs

+

{covesError}

+
+ ) : coves.length === 0 ? ( +
+ + + +

No hay COVEs

+

+ No se encontraron COVEs para este pedimento. +

+
+ ) : ( +
+ {/* Tabla de COVEs */} +
+ + + + + + + + + + + + {coves.map((cove, index) => ( + + + + + + + + ))} + +
+ 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'} + + +
+ + +
+
+
+
+ )} + + {/* Paginación para COVEs */} + {covesCount > 0 && ( +
+
+ + Mostrando {((covesPage - 1) * covesPageSize) + 1} a{' '} + {Math.min(covesPage * covesPageSize, covesCount)} de{' '} + {covesCount} COVEs + + +
+ +
+ + + + Página {covesPage} de {Math.ceil(covesCount / covesPageSize)} + + + +
+
+ )} +
+ )} + + {activeTab === 'edocs' && ( +
+ {/* Header de la sección */} +
+
+
+

+ EDocs del Pedimento +

+ + {edocsCount} EDocs + +
+
+ + {/* Filtros */} +
+
+ + setEdocsFilters(prev => ({ ...prev, numero_edocument: e.target.value }))} + placeholder="Número EDocs..." + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + setEdocsFilters(prev => ({ ...prev, clave: e.target.value }))} + placeholder="Clave..." + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + setEdocsFilters(prev => ({ ...prev, descripcion: e.target.value }))} + placeholder="Descripción..." + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + setEdocsFilters(prev => ({ ...prev, date_from: e.target.value }))} + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+ +
+ + setEdocsFilters(prev => ({ ...prev, date_to: e.target.value }))} + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+
+
+ + {edocsLoading ? ( +
+
+ Cargando EDocs... +
+ ) : edocsError ? ( +
+
+ + + +
+

Error al cargar EDocs

+

{edocsError}

+
+ ) : edocs.length === 0 ? ( +
+ + + +

No hay EDocs

+

+ No se encontraron documentos electrónicos para este pedimento. +

+
+ ) : ( +
+ {/* Tabla de EDocs */} +
+ + + + + + + + + + + + + + {edocs.map((edoc, index) => ( + + + + + + + + + + ))} + +
+ 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'} + + +
+ + +
+
+
+
+ )} + + {/* Paginación para EDocs */} + {edocsCount > 0 && ( +
+
+ + Mostrando {((edocsPage - 1) * edocsPageSize) + 1} a{' '} + {Math.min(edocsPage * edocsPageSize, edocsCount)} de{' '} + {edocsCount} EDocs + + +
+ +
+ + + + Página {edocsPage} de {Math.ceil(edocsCount / edocsPageSize)} + + + +
+
+ )} +
+ )} + + {activeTab === 'procesos' && ( +
+ {/* Header de la sección */} +
+
+
+

+ Procesos del Pedimento +

+ + {procesosCount} Procesos + +
+ + {/* Botones de creación de servicios */} +
+ {/* Botón prioritario: Obtener pedimento completo */} + {!existeServicio(3) && ( + + )} + + {/* Otros servicios disponibles */} + {existeServicio(3) && ( +
+ {/* Generación COVEs */} + {!existeServicio(4) && ( + + )} + + {/* Generación EDocs */} + {!existeServicio(5) && ( + + )} +
+ )} +
+
+
+ + {procesosLoading ? ( +
+
+ Cargando procesos... +
+ ) : procesosError ? ( +
+
+ + + +
+

Error al cargar procesos

+

{procesosError}

+
+ ) : procesos.length === 0 ? ( +
+ + + +

No hay procesos

+

+ No se encontraron procesos para este pedimento. +

+
+ ) : ( +
+ {/* Tabla de Procesos */} +
+ + + + + + + + + + + + + + {procesos.map((proceso, index) => ( + + + + + + + + + + ))} + +
+ 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 + )} +
+
+
+
+ )} + + {/* Paginación para Procesos */} + {procesosCount > 0 && ( +
+
+ + Mostrando {((procesosPage - 1) * procesosPageSize) + 1} a{' '} + {Math.min(procesosPage * procesosPageSize, procesosCount)} de{' '} + {procesosCount} procesos + + +
+ +
+ + + + Página {procesosPage} de {Math.ceil(procesosCount / procesosPageSize)} + + + +
+
+ )} +
+ )} + + {activeTab === 'auditor' && ( +
+ {/* Header de la sección */} +
+
+
+

+ Auditoría del Pedimento +

+ + Análisis Completo + +
+ + {/* Botones de Auditoría */} +
+ + + + + + + + + + + + + + + +
+
+
+ + {/* Contenido del Auditor */} +
+ {/* Resumen de Estado */} +
+

+ + + + Resumen de Estado del Pedimento +

+ +
+
+
+
+

Documentos

+

{docsCount || 0}

+
+ + + +
+
+ +
+
+
+

COVEs

+

{covesCount || 0}

+
+ + + +
+
+ +
+
+
+

EDocs

+

{edocsCount || 0}

+
+ + + +
+
+ +
+
+
+

Procesos

+

{procesosCount || 0}

+
+ + + +
+
+
+
+ + {/* Análisis de Integridad */} +
+

+ + + + Análisis de Integridad +

+ +
+
+
+ + + + Documentos Requeridos +
+ Completo +
+ +
+
+ + + + Validación de Estructura +
+ Verificado +
+ +
+
+ + + + Seguridad y Firmas +
+ Auditado +
+
+
+ + {/* Timeline de Actividades */} +
+

+ + + + Timeline de Actividades Recientes +

+ +
+
+
+
+ + + +
+
+
+

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'}

+
+
+
+
+
+
+ )} +
+
+ + {/* Modal de vista previa mejorado */} + {previewOpen && ( +
+
+ {/* Header del modal con gradiente e información del archivo */} +
+
+
+ + + + +
+
+

Vista previa de documento

+ {previewDoc && ( +
+ {previewDoc.archivo_original || 'Sin nombre'} + {previewDoc.size && ( + + {formatFileSize(previewDoc.size)} + + )} + {previewDoc.extension && ( + + {previewDoc.extension} + + )} +
+ )} +
+
+ + {/* Controles del header */} +
+ {/* Botón de descarga siempre visible */} + {previewDoc && ( + + )} + + {/* Controles de zoom para imágenes */} + {previewType === 'img' && ( +
+ + {Math.round(imageZoom * 100)}% + + +
+ )} + + {/* Botón cerrar */}
+
- {/* Contenido del modal */} -
- {previewLoading ? ( -
-
-
- - - - - Cargando documento... -
+ {/* Contenido del modal */} +
+ {previewLoading ? ( +
+
+
+ + + + + Cargando documento...
+

Preparando vista previa

- ) : previewError ? ( -
-
- +
+ ) : previewError ? ( +
+
+
+ -

{previewError}

+

Error al cargar documento

+

{previewError}

+
- ) : previewType === 'pdf' ? ( -