diff --git a/src/api/coves.js b/src/api/coves.js index 595bda4..a0c8c0e 100644 --- a/src/api/coves.js +++ b/src/api/coves.js @@ -11,10 +11,14 @@ export const fetchPedimentoCoves = async (pedimentoId, page = 1, pageSize = 10, }); if (filters.numero_cove) params.append('numero_cove__icontains', filters.numero_cove); + // Estado de 3 valores (pendiente | descargado | error) — T2026-05-027 + if (filters.cove_estado) params.append('cove_estado', filters.cove_estado); + if (filters.acuse_cove_estado) params.append('acuse_cove_estado', filters.acuse_cove_estado); + // Filtros booleanos legados (compatibilidad) 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); + if (filters.date_from || filters.created_at__gte) params.append('created_at__gte', filters.date_from || filters.created_at__gte); + if (filters.date_to || filters.created_at__lte) params.append('created_at__lte', filters.date_to || filters.created_at__lte); const response = await fetchWithAuth(`${API_BASE_URL}/customs/coves/?${params}`); if (!response.ok) throw new Error(await extractApiError(response)); diff --git a/src/api/edocuments.js b/src/api/edocuments.js index d19f1a6..b954157 100644 --- a/src/api/edocuments.js +++ b/src/api/edocuments.js @@ -13,10 +13,14 @@ export const fetchPedimentoEdocuments = async (pedimentoId, page = 1, pageSize = 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); + // Estado de 3 valores (pendiente | descargado | error) — T2026-05-027 + if (filters.edocument_estado) params.append('edocument_estado', filters.edocument_estado); + if (filters.acuse_estado) params.append('acuse_estado', filters.acuse_estado); + // Filtros booleanos legados (compatibilidad) 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); + if (filters.date_from || filters.created_at__gte) params.append('created_at__gte', filters.date_from || filters.created_at__gte); + if (filters.date_to || filters.created_at__lte) params.append('created_at__lte', filters.date_to || filters.created_at__lte); const response = await fetchWithAuth(`${API_BASE_URL}/customs/edocuments/?${params}`); if (!response.ok) throw new Error(await extractApiError(response)); diff --git a/src/pages/Auditor.jsx b/src/pages/Auditor.jsx index 6a28a43..88884ec 100644 --- a/src/pages/Auditor.jsx +++ b/src/pages/Auditor.jsx @@ -48,7 +48,7 @@ function AuditResultPanel({ modal, closing, iniciandoProcesamiento, onClose, onI const sinNadaQueHacer = (tipo === 'rm' && data?.tiene_remesas === false); const esAuditoriaIntegridad = ['int_pt', 'int_edoc', 'int_cove', 'int_rm'].includes(tipo); const mostrarBotonProcesar = !esCompletado && !sinNadaQueHacer && !esAuditoriaIntegridad; - const mostrarBotonCorregir = esAuditoriaIntegridad && !esCompletado && data?.estado !== 'sin_xml' && data?.estado !== 'sin_datos_xml' && data?.estado !== 'sin_datos'; + const mostrarBotonCorregir = esAuditoriaIntegridad && !esCompletado && !['sin_xml', 'sin_datos_xml', 'sin_datos', 'sin_xml_pc', 'descarga_solicitada', 'error'].includes(data?.estado); const tituloTipo = TIPO_LABELS[tipo] || tipo; const hayErroresPC = tipo === 'pc' && data?.hay_errores; @@ -2047,19 +2047,18 @@ const handleAuditarEDocumentPedimento = async (pedimentoId) => { const raw = await response.json(); const completado = raw.estado === 'completado'; const sinRemesas = raw.estado === 'sin_remesas'; + const soloMensaje = ['sin_remesas', 'sin_xml', 'sin_xml_pc', 'descarga_solicitada', 'error'].includes(raw.estado); const data = { estado: raw.estado, auditoria_completa: completado || sinRemesas, mensaje: completado ? `Remesa íntegra: ${raw.coves_db} COVEs de remesa en DB` - : sinRemesas - ? raw.mensaje - : raw.estado === 'sin_xml' + : soloMensaje ? raw.mensaje ?? 'No hay XML de remesa descargado' : `Faltan ${raw.faltantes?.length ?? 0} COVE(s) de remesa en DB`, - resumen: completado + resumen: (completado || raw.estado === 'incompleto') ? { 'COVEs en remesa': raw.total_en_remesa, 'COVEs en DB': raw.coves_db } - : (!completado && !sinRemesas && raw.estado !== 'sin_xml') ? { 'COVEs en remesa': raw.total_en_remesa, 'COVEs en DB': raw.coves_db } : null, + : null, faltantes: raw.faltantes ?? [], total_en_remesa: raw.total_en_remesa, coves_db: raw.coves_db, diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 4e8aecd..49e5389 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -2,8 +2,6 @@ import React, { useState } from 'react'; import { login, getMicrosoftLoginUrl } from '../api/auth'; import { Link } from 'react-router-dom'; -const HUB_URL = import.meta.env.VITE_HUB_URL || 'http://localhost:3001'; - export default function Login() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); @@ -33,15 +31,11 @@ export default function Login() { window.dispatchEvent(new CustomEvent('authStateChanged')); - if (data.first_login) { - // Primera vez: acaba de ser provisionado en Hub. - // Redirigir al Hub para que establezca su sesión KC y conozca el workspace. - const returnTo = encodeURIComponent('/app-launcher'); - window.location.href = `${HUB_URL}/login?return_to=${returnTo}`; - } else { - // Ya estaba migrado: ir directo al dashboard de EFC. - window.location.href = '/admin'; - } + // Login único: la sesión local de EFC ya quedó establecida con los tokens + // recibidos. En el primer ingreso el backend dispara la provisión/migración + // en Hub en segundo plano — no forzamos al usuario a pasar por el Hub. + // (El logout sí lo redirige al Hub para cerrar la sesión KC.) + window.location.href = '/admin'; } catch (err) { setError(err.message || 'Usuario o contraseña incorrectos'); diff --git a/src/pages/PedimentoDetail.jsx b/src/pages/PedimentoDetail.jsx index 26c15ea..c15d34d 100644 --- a/src/pages/PedimentoDetail.jsx +++ b/src/pages/PedimentoDetail.jsx @@ -65,6 +65,32 @@ const getEstadoColor = (estado) => { return colores[estado] || 'bg-gray-100 text-gray-800'; }; +// Badge de estado de descarga VU de 3 valores (T2026-05-027). Acepta el estado +// nuevo ('pendiente' | 'descargado' | 'error') con fallback al booleano legado +// mientras la migración no esté aplicada en todos los entornos. +const renderEstadoDescargaBadge = (estado, descargadoLegacy, intentos, ultimoError) => { + const value = estado || (descargadoLegacy ? 'descargado' : 'pendiente'); + const estilos = { + descargado: 'bg-green-100 text-green-800', + pendiente: 'bg-yellow-100 text-yellow-800', + error: 'bg-red-100 text-red-800', + }; + const etiquetas = { descargado: 'Descargado', pendiente: 'Pendiente', error: 'Error' }; + const label = etiquetas[value] || 'Pendiente'; + const showIntentos = value !== 'descargado' && Number(intentos) > 0; + const title = value === 'error' + ? (ultimoError || 'Error en la descarga; requiere reproceso manual') + : (showIntentos ? `Intentos automáticos de descarga: ${intentos}` : undefined); + return ( + + {label}{showIntentos ? ` (${intentos})` : ''} + + ); +}; + const getServicioLabel = (servicio) => { const servicios = { 1: 'Estado de Pedimento', @@ -141,6 +167,7 @@ export default function PedimentoDetail() { const [docsPrev, setDocsPrev] = useState(null); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); + const [docsRefreshKey, setDocsRefreshKey] = useState(0); const [selected, setSelected] = useState([]); const [downloading, setDownloading] = useState(false); const [downloadingAll, setDownloadingAll] = useState(false); @@ -200,10 +227,11 @@ export default function PedimentoDetail() { const [covesError, setCovesError] = useState(''); const [covesPage, setCovesPage] = useState(1); const [covesPageSize, setCovesPageSize] = useState(10); + const [covesRefreshKey, setCovesRefreshKey] = useState(0); const [covesFilters, setCovesFilters] = useState({ numero_cove: '', - cove_descargado: '', - acuse_cove_descargado: '', + cove_estado: '', + acuse_cove_estado: '', created_at__gte: '', created_at__lte: '' }); @@ -215,12 +243,13 @@ export default function PedimentoDetail() { const [edocsError, setEdocsError] = useState(''); const [edocsPage, setEdocsPage] = useState(1); const [edocsPageSize, setEdocsPageSize] = useState(10); + const [edocsRefreshKey, setEdocsRefreshKey] = useState(0); const [edocsFilters, setEdocsFilters] = useState({ numero_edocument: '', clave: '', descripcion: '', - edocument_descargado: '', - acuse_descargado: '', + edocument_estado: '', + acuse_estado: '', created_at__gte: '', created_at__lte: '' }); @@ -234,6 +263,7 @@ export default function PedimentoDetail() { const [pedimentoError, setPedimentoError] = useState(''); const [pedimentoPage, setPedimentoPage] = useState(1); const [pedimentoPageSize, setPedimentoPageSize] = useState(10); + const [pedimentoRefreshKey, setPedimentoRefreshKey] = useState(0); const [downloadingAllPedimento, setDownloadingAllPedimento] = useState(false); // Agrega estos estados para selección de documentos de pedimento const [selectedPedimentoDocuments, setSelectedPedimentoDocuments] = useState([]); @@ -331,6 +361,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => { const [partidasError, setPartidasError] = useState(''); const [partidasPage, setPartidasPage] = useState(1); const [partidasPageSize, setPartidasPageSize] = useState(10); + const [partidasRefreshKey, setPartidasRefreshKey] = useState(0); const [partidasFilters, setPartidasFilters] = useState({ numero_partida: '', descargado: '', @@ -1053,6 +1084,11 @@ const handleDeleteSelectedPedimentoDocuments = async () => { formData.append('document_type_id', headerUploadTypeId); } + // En el apartado Pedimento el backend clasifica el XML (PC/Remesa) y le asigna la nomenclatura + if (activeTab === 'pedimento') { + formData.append('tab_seccion', 'pedimento'); + } + // Agregar archivos al FormData selectedFiles.forEach((file) => { formData.append('files', file); @@ -1068,17 +1104,30 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } const result = await response.json(); - showMessage(result.message || `${result.uploaded_count || selectedFiles.length} archivo(s) subido(s) exitosamente`, 'success'); + + // Solo reportar éxito por lo realmente subido; los fallos se detallan (status 207) + const okCount = (result.created_count || 0) + (result.replaced_count || 0); + if (result.failed_files?.length > 0) { + const detalle = (result.errors || []).join(' | '); + if (okCount > 0) { + showMessage(`${okCount} archivo(s) subido(s); ${result.failed_files.length} fallaron: ${detalle}`, 'warning'); + } else { + showMessage(`No fue posible subir los documentos: ${detalle}`, 'error'); + } + } else { + showMessage(result.message || `${okCount || selectedFiles.length} archivo(s) subido(s) exitosamente`, 'success'); + } // Limpiar archivos seleccionados y cerrar modal setSelectedFiles([]); setShowUploadModal(false); setHeaderUploadTypeId(null); - // Forzar recarga de documentos - const currentPage = page; - setPage(0); - setTimeout(() => setPage(currentPage), 100); + // Recargar el listado del tab activo y el de Documentos generales + if (activeTab === 'pedimento') { + setPedimentoRefreshKey(prev => prev + 1); + } + setDocsRefreshKey(prev => prev + 1); } catch (error) { console.error('Error durante la subida:', error); showMessage(`Error durante la subida: ${error.message}`, 'error'); @@ -1182,7 +1231,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } setDocsLoading(false); }); - }, [id, page, pageSize, filters, documentTypeFilter, fileNameFilter, extensionFilter, dateFilter, orderBy, orderDir, showMessage]); + }, [id, page, pageSize, docsRefreshKey, filters, documentTypeFilter, fileNameFilter, extensionFilter, dateFilter, orderBy, orderDir, showMessage]); // Resetear página cuando cambien los filtros useEffect(() => { @@ -1244,7 +1293,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => { setPedimentoLoading(false); }); - },[id, activeTab, pedimentoPage, pedimentoPageSize, showMessage, pedimentoFilters, documentTypeFilter, fileNameFilter, extensionFilter, dateFilter, orderBy, orderDir]); + },[id, activeTab, pedimentoPage, pedimentoPageSize, pedimentoRefreshKey, showMessage, pedimentoFilters, documentTypeFilter, fileNameFilter, extensionFilter, dateFilter, orderBy, orderDir]); @@ -1270,7 +1319,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } setCovesLoading(false); }); - }, [id, activeTab, covesPage, covesPageSize, covesFilters, showMessage]); + }, [id, activeTab, covesPage, covesPageSize, covesFilters, covesRefreshKey, showMessage]); // Resetear página de COVEs cuando cambien los filtros useEffect(() => { @@ -1299,7 +1348,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } setEdocsLoading(false); }); - }, [id, activeTab, edocsPage, edocsPageSize, edocsFilters, showMessage]); + }, [id, activeTab, edocsPage, edocsPageSize, edocsFilters, edocsRefreshKey, showMessage]); // Resetear página de EDocs cuando cambien los filtros useEffect(() => { @@ -1883,7 +1932,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } setPartidasLoading(false); }); - }, [id, activeTab, partidasPage, partidasPageSize, partidasFilters, showMessage, pedimento]); + }, [id, activeTab, partidasPage, partidasPageSize, partidasFilters, partidasRefreshKey, showMessage, pedimento]); // Resetear página de Partidas cuando cambien los filtros useEffect(() => { @@ -2362,17 +2411,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } }; - // Restablece acuse_descargado=False (crea doc error tipo 26 para Errores VU) - // y lanza inmediatamente el reprocesamiento del acuse. + // Restablece el acuse a 'pendiente' con contador en 0 (crea doc error tipo 26 + // para Errores VU) y lanza inmediatamente el reprocesamiento del acuse. const handleResetAcuse = async (edoc) => { try { await resetAcuseEdocument(edoc.id); showMessage(`Restableciendo acuse de ${edoc.numero_edocument}. Iniciando reprocesamiento...`, 'info'); await handleAcuseEdocProcess(edoc); - fetchEdocs(id, edocsPage, edocsPageSize, edocsFilters); + setEdocsRefreshKey(k => k + 1); } catch (error) { showMessage(`Error al restablecer el acuse: ${error.message}`, 'error'); - fetchEdocs(id, edocsPage, edocsPageSize, edocsFilters); + setEdocsRefreshKey(k => k + 1); } }; @@ -2462,12 +2511,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => { 'success' ); + // Recargar en una página válida: tras el borrado la página actual puede ya no existir + const remainingPartidas = Math.max(0, partidasCount - selectedPartidas.length); + const lastPartidasPage = Math.max(1, Math.ceil(remainingPartidas / partidasPageSize)); + setSelectedPartidas([]); - // Forzar recarga de documentos - const currentPage = partidasPage; - setPartidasPage(0); - setTimeout(() => setPartidasPage(currentPage), 100); + if (partidasPage > lastPartidasPage) { + setPartidasPage(lastPartidasPage); + } else { + setPartidasRefreshKey(prev => prev + 1); + } } catch (error) { showMessage(`Error durante la eliminación: ${error.message}`, 'error'); @@ -2558,12 +2612,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => { 'success' ); + // Recargar en una página válida: tras el borrado la página actual puede ya no existir + const remainingCoves = Math.max(0, covesCount - selectedCoves.length); + const lastCovesPage = Math.max(1, Math.ceil(remainingCoves / covesPageSize)); + setSelectedCoves([]); - // Forzar recarga de documentos - // const currentPage = covesPage; - setCovesPage(0); - setTimeout(() => setCovesPage(1), 100); + if (covesPage > lastCovesPage) { + setCovesPage(lastCovesPage); + } else { + setCovesRefreshKey(prev => prev + 1); + } } catch (error) { showMessage(`Error durante la eliminación: ${error.message}`, 'error'); @@ -2723,12 +2782,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => { 'success' ); + // Recargar en una página válida: tras el borrado la página actual puede ya no existir + const remainingEdocs = Math.max(0, edocsCount - selectedEdocs.length); + const lastEdocsPage = Math.max(1, Math.ceil(remainingEdocs / edocsPageSize)); + setSelectedEdocs([]); - // Forzar recarga de documentos - const currentPage = edocsPage; - setEdocsPage(0); - setTimeout(() => setEdocsPage(currentPage), 100); + if (edocsPage > lastEdocsPage) { + setEdocsPage(lastEdocsPage); + } else { + setEdocsRefreshKey(prev => prev + 1); + } } catch (error) { showMessage(`Error durante la eliminación: ${error.message}`, 'error'); @@ -3074,19 +3138,14 @@ const isEdoc = selectedDocumentForUpload?.tab === 'edoc'; const data = await result.json(); showMessage(data.message || 'Registro creado exitosamente', 'success'); - // Recargar la tabla correspondiente + // Recargar la tabla correspondiente vía refresh keys (los useEffect de cada + // pestaña hacen el fetch con la firma y filtros correctos) if (tab === 'partida') { - fetchPedimentoPartidas(partidasPage, partidasPageSize, partidasFilters) - .then((d) => { setPartidas(d.results || []); setPartidasCount(d.count || 0); }) - .catch(err => console.error('Error recargando partidas:', err)); + setPartidasRefreshKey(k => k + 1); } else if (tab === 'cove') { - fetchPedimentoCoves && fetchPedimentoCoves(covesPage, covesPageSize, covesFilters) - .then((d) => { setCoves(d.results || []); setCovesCount(d.count || 0); }) - .catch(err => console.error('Error recargando coves:', err)); + setCovesRefreshKey(k => k + 1); } else if (tab === 'edoc') { - fetchPedimentoEdocuments && fetchPedimentoEdocuments(edocsPage, edocsPageSize, edocsFilters) - .then((d) => { setEdocs(d.results || []); setEdocsCount(d.count || 0); }) - .catch(err => console.error('Error recargando edocs:', err)); + setEdocsRefreshKey(k => k + 1); } } catch (error) { showMessage(`Error: ${error.message}`, 'error'); @@ -5156,31 +5215,33 @@ useEffect(() => {
@@ -5344,22 +5405,10 @@ useEffect(() => { {formatDate(cove.created_at)} - - {cove.cove_descargado ? 'Descargado' : 'Pendiente'} - + {renderEstadoDescargaBadge(cove.cove_estado, cove.cove_descargado, cove.cove_intentos, cove.ultimo_error)} - - {cove.acuse_cove_descargado ? 'Descargado' : 'Pendiente'} - + {renderEstadoDescargaBadge(cove.acuse_cove_estado, cove.acuse_cove_descargado, cove.acuse_cove_intentos, cove.ultimo_error)}
@@ -5663,31 +5712,33 @@ useEffect(() => {
@@ -5875,22 +5926,10 @@ useEffect(() => { {formatDate(edoc.created_at)} - - {edoc.edocument_descargado ? 'Descargado' : 'Pendiente'} - + {renderEstadoDescargaBadge(edoc.edocument_estado, edoc.edocument_descargado, edoc.edocument_intentos, edoc.ultimo_error)} - - {edoc.acuse_descargado ? 'Descargado' : 'Pendiente'} - + {renderEstadoDescargaBadge(edoc.acuse_estado, edoc.acuse_descargado, edoc.acuse_intentos, edoc.ultimo_error)}
@@ -7335,7 +7374,7 @@ useEffect(() => {

Información

{activeTab === 'pedimento' - ? 'Para que los archivos de pedimento completo y/o remesa se vean en este apartado, deben tener la siguiente nomenclatura en el nombre Pedimento Completo ej: vu_PC_26-01-1234-1234567.xml y para Remesa ej: vu_RM_26-01-1234-1234567.xml, El 26-01-1234-1234567 es el pedimento al que se esta subiendo el documento.' + ? `El sistema detectará si el XML es un Pedimento Completo o una Remesa.` : 'Los archivos se subirán al pedimento actual. Se aceptan múltiples formatos de archivo.' }

diff --git a/src/pages/Reports.jsx b/src/pages/Reports.jsx index bb217c9..cbf01b9 100644 --- a/src/pages/Reports.jsx +++ b/src/pages/Reports.jsx @@ -935,21 +935,11 @@ export default function Reports() { } setIsExporting(true); - - try { - const progressDiv = document.createElement('div'); - progressDiv.className = 'fixed bottom-4 right-4 bg-white p-4 rounded-lg shadow-xl border border-blue-100'; - progressDiv.innerHTML = ` -
-
-

Preparando exportación DataStage...

-
- `; - document.body.appendChild(progressDiv); + try { // DETECCIÓN AUTOMÁTICA DEL MODO const modo = modelosConCampos.length > 1 ? 'multiple' : 'simple'; - + const exportData = { modo: modo, format: exportFormat, @@ -959,11 +949,10 @@ export default function Reports() { if (modo === 'simple') { // MODO SIMPLE: solo un modelo con campos const modeloUnico = modelosConCampos[0]; - const modelData = datastageModels.find(m => m.model === modeloUnico); - + exportData.model = modeloUnico; exportData.fields = modelFieldsMap[modeloUnico]; - + } else { // MODO MÚLTIPLE: varios modelos con campos exportData.models = modelosConCampos.map(modelo => { @@ -976,14 +965,8 @@ export default function Reports() { }); } - // Resto del código de exportación... - progressDiv.innerHTML = ` -
-
-

Generando archivo DataStage...

-
- `; - + // El backend encola la generación en Celery y responde 202 con task_id; + // el progreso llega por SSE al TaskProgressCard y ahí se descarga el archivo. const response = await fetchWithAuth(`${API_URL}/reports/exportmodel/datastage/`, { method: 'POST', headers: { @@ -997,68 +980,26 @@ export default function Reports() { throw new Error(errorData.error || errorData.message || 'Error al exportar DataStage'); } - const blob = await response.blob(); - - const contentDisposition = response.headers.get('Content-Disposition'); - - let fileName = ''; - - if (contentDisposition) { - const filenameMatch = contentDisposition.match(/filename="(.+?)"/) || - contentDisposition.match(/filename=([^;]+)/); - - if (filenameMatch) { - fileName = filenameMatch[1].replace(/"/g, ''); - } - } - - if (!fileName) { - const isZip = blob.type === 'application/zip'; - const isExcel = blob.type.includes('spreadsheetml'); - - if (isZip) { - fileName = modo === 'multiple' - ? `datastage_reports_${new Date().toISOString().split('T')[0]}.zip` - : `datastage_${modelosConCampos[0]}_particionado_${new Date().toISOString().split('T')[0]}.zip`; - } else if (isExcel) { - fileName = `datastage_${modelosConCampos[0]}_${new Date().toISOString().split('T')[0]}.xlsx`; - } else { - fileName = `datastage_${modelosConCampos[0]}_${new Date().toISOString().split('T')[0]}.csv`; - } + const data = await response.json(); + if (data.task_id) { + addTask({ + task_id: data.task_id, + label: 'Reporte DataStage', + organizacion_id: globalFilters.organizacion, + taskType: 'report', + report_id: data.report_id, + status: 'submitted', + }); + pendingReportTasksRef.current.add(data.task_id); } - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', fileName); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - window.URL.revokeObjectURL(url); + showMessage('Reporte solicitado. Puedes ver el progreso en la barra inferior.', 'success'); - progressDiv.innerHTML = ` -
- - - -

¡Exportación completada! (Modo ${modo})

-
- `; - setTimeout(() => { - progressDiv.style.opacity = '0'; - progressDiv.style.transform = 'translateY(100%)'; - setTimeout(() => progressDiv.remove(), 300); - }, 2000); - - showMessage(`¡Archivo ${fileName} descargado exitosamente! (${modo === 'multiple' ? 'Múltiple' : 'Simple'})`, 'success'); - } catch (error) { console.error('❌ ERROR AL EXPORTAR DATASTAGE:', error); showMessage(error.message || 'Error al exportar DataStage. Por favor intente nuevamente.', 'error'); } finally { setIsExporting(false); - const progressDiv = document.querySelector('.fixed.bottom-4.right-4'); - if (progressDiv) progressDiv.remove(); } }; @@ -1795,10 +1736,61 @@ export default function Reports() { )} - {isExporting ? 'Generando archivo...' : `Generar y descargar ${exportFormat.toUpperCase()}`} + {isExporting ? 'Generando archivo...' : `Generar reporte ${exportFormat.toUpperCase()}`} +
+

Historial de Reportes

+
+ + + + + + + + + + + + + {reports.filter(r => r.report_type === 'datastage').length > 0 ? ( + reports + .filter(r => r.report_type === 'datastage') + .map((r) => ( + + + + + + + + + )) + ) : ( + + + + )} + +
IDEstadoCreadoFinalizadoErrorDescargar
{r.report_id}{r.status}{r.created_at}{r.finished_at}{r.error_message ? r.error_message : '-'} + {r.status === 'ready' ? ( + + ) : ( + - + )} +
+ No hay reportes de datastage +
+
+
+