Compare commits
3 Commits
feature/hu
...
d54a4754a8
| Author | SHA1 | Date | |
|---|---|---|---|
| d54a4754a8 | |||
| 14db21c671 | |||
| 45548f9bc8 |
@@ -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);
|
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.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.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_from || filters.created_at__gte) params.append('created_at__gte', filters.date_from || filters.created_at__gte);
|
||||||
if (filters.date_to) params.append('created_at__lte', filters.date_to);
|
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}`);
|
const response = await fetchWithAuth(`${API_BASE_URL}/customs/coves/?${params}`);
|
||||||
if (!response.ok) throw new Error(await extractApiError(response));
|
if (!response.ok) throw new Error(await extractApiError(response));
|
||||||
|
|||||||
@@ -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.numero_edocument) params.append('numero_edocument__icontains', filters.numero_edocument);
|
||||||
if (filters.clave) params.append('clave__icontains', filters.clave);
|
if (filters.clave) params.append('clave__icontains', filters.clave);
|
||||||
if (filters.descripcion) params.append('descripcion__icontains', filters.descripcion);
|
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.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.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_from || filters.created_at__gte) params.append('created_at__gte', filters.date_from || filters.created_at__gte);
|
||||||
if (filters.date_to) params.append('created_at__lte', filters.date_to);
|
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}`);
|
const response = await fetchWithAuth(`${API_BASE_URL}/customs/edocuments/?${params}`);
|
||||||
if (!response.ok) throw new Error(await extractApiError(response));
|
if (!response.ok) throw new Error(await extractApiError(response));
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function AuditResultPanel({ modal, closing, iniciandoProcesamiento, onClose, onI
|
|||||||
const sinNadaQueHacer = (tipo === 'rm' && data?.tiene_remesas === false);
|
const sinNadaQueHacer = (tipo === 'rm' && data?.tiene_remesas === false);
|
||||||
const esAuditoriaIntegridad = ['int_pt', 'int_edoc', 'int_cove', 'int_rm'].includes(tipo);
|
const esAuditoriaIntegridad = ['int_pt', 'int_edoc', 'int_cove', 'int_rm'].includes(tipo);
|
||||||
const mostrarBotonProcesar = !esCompletado && !sinNadaQueHacer && !esAuditoriaIntegridad;
|
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 tituloTipo = TIPO_LABELS[tipo] || tipo;
|
||||||
const hayErroresPC = tipo === 'pc' && data?.hay_errores;
|
const hayErroresPC = tipo === 'pc' && data?.hay_errores;
|
||||||
|
|
||||||
@@ -2047,19 +2047,18 @@ const handleAuditarEDocumentPedimento = async (pedimentoId) => {
|
|||||||
const raw = await response.json();
|
const raw = await response.json();
|
||||||
const completado = raw.estado === 'completado';
|
const completado = raw.estado === 'completado';
|
||||||
const sinRemesas = raw.estado === 'sin_remesas';
|
const sinRemesas = raw.estado === 'sin_remesas';
|
||||||
|
const soloMensaje = ['sin_remesas', 'sin_xml', 'sin_xml_pc', 'descarga_solicitada', 'error'].includes(raw.estado);
|
||||||
const data = {
|
const data = {
|
||||||
estado: raw.estado,
|
estado: raw.estado,
|
||||||
auditoria_completa: completado || sinRemesas,
|
auditoria_completa: completado || sinRemesas,
|
||||||
mensaje: completado
|
mensaje: completado
|
||||||
? `Remesa íntegra: ${raw.coves_db} COVEs de remesa en DB`
|
? `Remesa íntegra: ${raw.coves_db} COVEs de remesa en DB`
|
||||||
: sinRemesas
|
: soloMensaje
|
||||||
? raw.mensaje
|
|
||||||
: raw.estado === 'sin_xml'
|
|
||||||
? raw.mensaje ?? 'No hay XML de remesa descargado'
|
? raw.mensaje ?? 'No hay XML de remesa descargado'
|
||||||
: `Faltan ${raw.faltantes?.length ?? 0} COVE(s) de remesa en DB`,
|
: `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 }
|
? { '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 ?? [],
|
faltantes: raw.faltantes ?? [],
|
||||||
total_en_remesa: raw.total_en_remesa,
|
total_en_remesa: raw.total_en_remesa,
|
||||||
coves_db: raw.coves_db,
|
coves_db: raw.coves_db,
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import React, { useState } from 'react';
|
|||||||
import { login, getMicrosoftLoginUrl } from '../api/auth';
|
import { login, getMicrosoftLoginUrl } from '../api/auth';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const HUB_URL = import.meta.env.VITE_HUB_URL || 'http://localhost:3001';
|
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@@ -33,15 +31,11 @@ export default function Login() {
|
|||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('authStateChanged'));
|
window.dispatchEvent(new CustomEvent('authStateChanged'));
|
||||||
|
|
||||||
if (data.first_login) {
|
// Login único: la sesión local de EFC ya quedó establecida con los tokens
|
||||||
// Primera vez: acaba de ser provisionado en Hub.
|
// recibidos. En el primer ingreso el backend dispara la provisión/migración
|
||||||
// Redirigir al Hub para que establezca su sesión KC y conozca el workspace.
|
// en Hub en segundo plano — no forzamos al usuario a pasar por el Hub.
|
||||||
const returnTo = encodeURIComponent('/app-launcher');
|
// (El logout sí lo redirige al Hub para cerrar la sesión KC.)
|
||||||
window.location.href = `${HUB_URL}/login?return_to=${returnTo}`;
|
window.location.href = '/admin';
|
||||||
} else {
|
|
||||||
// Ya estaba migrado: ir directo al dashboard de EFC.
|
|
||||||
window.location.href = '/admin';
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || 'Usuario o contraseña incorrectos');
|
setError(err.message || 'Usuario o contraseña incorrectos');
|
||||||
|
|||||||
@@ -65,6 +65,32 @@ const getEstadoColor = (estado) => {
|
|||||||
return colores[estado] || 'bg-gray-100 text-gray-800';
|
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 (
|
||||||
|
<span
|
||||||
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${estilos[value] || estilos.pendiente}`}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
{label}{showIntentos ? ` (${intentos})` : ''}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getServicioLabel = (servicio) => {
|
const getServicioLabel = (servicio) => {
|
||||||
const servicios = {
|
const servicios = {
|
||||||
1: 'Estado de Pedimento',
|
1: 'Estado de Pedimento',
|
||||||
@@ -141,6 +167,7 @@ export default function PedimentoDetail() {
|
|||||||
const [docsPrev, setDocsPrev] = useState(null);
|
const [docsPrev, setDocsPrev] = useState(null);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [docsRefreshKey, setDocsRefreshKey] = useState(0);
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
const [downloading, setDownloading] = useState(false);
|
const [downloading, setDownloading] = useState(false);
|
||||||
const [downloadingAll, setDownloadingAll] = useState(false);
|
const [downloadingAll, setDownloadingAll] = useState(false);
|
||||||
@@ -200,10 +227,11 @@ export default function PedimentoDetail() {
|
|||||||
const [covesError, setCovesError] = useState('');
|
const [covesError, setCovesError] = useState('');
|
||||||
const [covesPage, setCovesPage] = useState(1);
|
const [covesPage, setCovesPage] = useState(1);
|
||||||
const [covesPageSize, setCovesPageSize] = useState(10);
|
const [covesPageSize, setCovesPageSize] = useState(10);
|
||||||
|
const [covesRefreshKey, setCovesRefreshKey] = useState(0);
|
||||||
const [covesFilters, setCovesFilters] = useState({
|
const [covesFilters, setCovesFilters] = useState({
|
||||||
numero_cove: '',
|
numero_cove: '',
|
||||||
cove_descargado: '',
|
cove_estado: '',
|
||||||
acuse_cove_descargado: '',
|
acuse_cove_estado: '',
|
||||||
created_at__gte: '',
|
created_at__gte: '',
|
||||||
created_at__lte: ''
|
created_at__lte: ''
|
||||||
});
|
});
|
||||||
@@ -215,12 +243,13 @@ export default function PedimentoDetail() {
|
|||||||
const [edocsError, setEdocsError] = useState('');
|
const [edocsError, setEdocsError] = useState('');
|
||||||
const [edocsPage, setEdocsPage] = useState(1);
|
const [edocsPage, setEdocsPage] = useState(1);
|
||||||
const [edocsPageSize, setEdocsPageSize] = useState(10);
|
const [edocsPageSize, setEdocsPageSize] = useState(10);
|
||||||
|
const [edocsRefreshKey, setEdocsRefreshKey] = useState(0);
|
||||||
const [edocsFilters, setEdocsFilters] = useState({
|
const [edocsFilters, setEdocsFilters] = useState({
|
||||||
numero_edocument: '',
|
numero_edocument: '',
|
||||||
clave: '',
|
clave: '',
|
||||||
descripcion: '',
|
descripcion: '',
|
||||||
edocument_descargado: '',
|
edocument_estado: '',
|
||||||
acuse_descargado: '',
|
acuse_estado: '',
|
||||||
created_at__gte: '',
|
created_at__gte: '',
|
||||||
created_at__lte: ''
|
created_at__lte: ''
|
||||||
});
|
});
|
||||||
@@ -234,6 +263,7 @@ export default function PedimentoDetail() {
|
|||||||
const [pedimentoError, setPedimentoError] = useState('');
|
const [pedimentoError, setPedimentoError] = useState('');
|
||||||
const [pedimentoPage, setPedimentoPage] = useState(1);
|
const [pedimentoPage, setPedimentoPage] = useState(1);
|
||||||
const [pedimentoPageSize, setPedimentoPageSize] = useState(10);
|
const [pedimentoPageSize, setPedimentoPageSize] = useState(10);
|
||||||
|
const [pedimentoRefreshKey, setPedimentoRefreshKey] = useState(0);
|
||||||
const [downloadingAllPedimento, setDownloadingAllPedimento] = useState(false);
|
const [downloadingAllPedimento, setDownloadingAllPedimento] = useState(false);
|
||||||
// Agrega estos estados para selección de documentos de pedimento
|
// Agrega estos estados para selección de documentos de pedimento
|
||||||
const [selectedPedimentoDocuments, setSelectedPedimentoDocuments] = useState([]);
|
const [selectedPedimentoDocuments, setSelectedPedimentoDocuments] = useState([]);
|
||||||
@@ -331,6 +361,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
const [partidasError, setPartidasError] = useState('');
|
const [partidasError, setPartidasError] = useState('');
|
||||||
const [partidasPage, setPartidasPage] = useState(1);
|
const [partidasPage, setPartidasPage] = useState(1);
|
||||||
const [partidasPageSize, setPartidasPageSize] = useState(10);
|
const [partidasPageSize, setPartidasPageSize] = useState(10);
|
||||||
|
const [partidasRefreshKey, setPartidasRefreshKey] = useState(0);
|
||||||
const [partidasFilters, setPartidasFilters] = useState({
|
const [partidasFilters, setPartidasFilters] = useState({
|
||||||
numero_partida: '',
|
numero_partida: '',
|
||||||
descargado: '',
|
descargado: '',
|
||||||
@@ -1053,6 +1084,11 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
formData.append('document_type_id', headerUploadTypeId);
|
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
|
// Agregar archivos al FormData
|
||||||
selectedFiles.forEach((file) => {
|
selectedFiles.forEach((file) => {
|
||||||
formData.append('files', file);
|
formData.append('files', file);
|
||||||
@@ -1068,17 +1104,30 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
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
|
// Limpiar archivos seleccionados y cerrar modal
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
setShowUploadModal(false);
|
setShowUploadModal(false);
|
||||||
setHeaderUploadTypeId(null);
|
setHeaderUploadTypeId(null);
|
||||||
|
|
||||||
// Forzar recarga de documentos
|
// Recargar el listado del tab activo y el de Documentos generales
|
||||||
const currentPage = page;
|
if (activeTab === 'pedimento') {
|
||||||
setPage(0);
|
setPedimentoRefreshKey(prev => prev + 1);
|
||||||
setTimeout(() => setPage(currentPage), 100);
|
}
|
||||||
|
setDocsRefreshKey(prev => prev + 1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error durante la subida:', error);
|
console.error('Error durante la subida:', error);
|
||||||
showMessage(`Error durante la subida: ${error.message}`, 'error');
|
showMessage(`Error durante la subida: ${error.message}`, 'error');
|
||||||
@@ -1182,7 +1231,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
}
|
}
|
||||||
setDocsLoading(false);
|
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
|
// Resetear página cuando cambien los filtros
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1244,7 +1293,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
setPedimentoLoading(false);
|
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);
|
setCovesLoading(false);
|
||||||
});
|
});
|
||||||
}, [id, activeTab, covesPage, covesPageSize, covesFilters, showMessage]);
|
}, [id, activeTab, covesPage, covesPageSize, covesFilters, covesRefreshKey, showMessage]);
|
||||||
|
|
||||||
// Resetear página de COVEs cuando cambien los filtros
|
// Resetear página de COVEs cuando cambien los filtros
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1299,7 +1348,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
}
|
}
|
||||||
setEdocsLoading(false);
|
setEdocsLoading(false);
|
||||||
});
|
});
|
||||||
}, [id, activeTab, edocsPage, edocsPageSize, edocsFilters, showMessage]);
|
}, [id, activeTab, edocsPage, edocsPageSize, edocsFilters, edocsRefreshKey, showMessage]);
|
||||||
|
|
||||||
// Resetear página de EDocs cuando cambien los filtros
|
// Resetear página de EDocs cuando cambien los filtros
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1883,7 +1932,7 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
}
|
}
|
||||||
setPartidasLoading(false);
|
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
|
// Resetear página de Partidas cuando cambien los filtros
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -2362,17 +2411,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restablece acuse_descargado=False (crea doc error tipo 26 para Errores VU)
|
// Restablece el acuse a 'pendiente' con contador en 0 (crea doc error tipo 26
|
||||||
// y lanza inmediatamente el reprocesamiento del acuse.
|
// para Errores VU) y lanza inmediatamente el reprocesamiento del acuse.
|
||||||
const handleResetAcuse = async (edoc) => {
|
const handleResetAcuse = async (edoc) => {
|
||||||
try {
|
try {
|
||||||
await resetAcuseEdocument(edoc.id);
|
await resetAcuseEdocument(edoc.id);
|
||||||
showMessage(`Restableciendo acuse de ${edoc.numero_edocument}. Iniciando reprocesamiento...`, 'info');
|
showMessage(`Restableciendo acuse de ${edoc.numero_edocument}. Iniciando reprocesamiento...`, 'info');
|
||||||
await handleAcuseEdocProcess(edoc);
|
await handleAcuseEdocProcess(edoc);
|
||||||
fetchEdocs(id, edocsPage, edocsPageSize, edocsFilters);
|
setEdocsRefreshKey(k => k + 1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage(`Error al restablecer el acuse: ${error.message}`, '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'
|
'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([]);
|
setSelectedPartidas([]);
|
||||||
|
|
||||||
// Forzar recarga de documentos
|
if (partidasPage > lastPartidasPage) {
|
||||||
const currentPage = partidasPage;
|
setPartidasPage(lastPartidasPage);
|
||||||
setPartidasPage(0);
|
} else {
|
||||||
setTimeout(() => setPartidasPage(currentPage), 100);
|
setPartidasRefreshKey(prev => prev + 1);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage(`Error durante la eliminación: ${error.message}`, 'error');
|
showMessage(`Error durante la eliminación: ${error.message}`, 'error');
|
||||||
@@ -2558,12 +2612,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
'success'
|
'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([]);
|
setSelectedCoves([]);
|
||||||
|
|
||||||
// Forzar recarga de documentos
|
if (covesPage > lastCovesPage) {
|
||||||
// const currentPage = covesPage;
|
setCovesPage(lastCovesPage);
|
||||||
setCovesPage(0);
|
} else {
|
||||||
setTimeout(() => setCovesPage(1), 100);
|
setCovesRefreshKey(prev => prev + 1);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage(`Error durante la eliminación: ${error.message}`, 'error');
|
showMessage(`Error durante la eliminación: ${error.message}`, 'error');
|
||||||
@@ -2723,12 +2782,17 @@ const handleDeleteSelectedPedimentoDocuments = async () => {
|
|||||||
'success'
|
'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([]);
|
setSelectedEdocs([]);
|
||||||
|
|
||||||
// Forzar recarga de documentos
|
if (edocsPage > lastEdocsPage) {
|
||||||
const currentPage = edocsPage;
|
setEdocsPage(lastEdocsPage);
|
||||||
setEdocsPage(0);
|
} else {
|
||||||
setTimeout(() => setEdocsPage(currentPage), 100);
|
setEdocsRefreshKey(prev => prev + 1);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage(`Error durante la eliminación: ${error.message}`, 'error');
|
showMessage(`Error durante la eliminación: ${error.message}`, 'error');
|
||||||
@@ -3074,19 +3138,14 @@ const isEdoc = selectedDocumentForUpload?.tab === 'edoc';
|
|||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
showMessage(data.message || 'Registro creado exitosamente', 'success');
|
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') {
|
if (tab === 'partida') {
|
||||||
fetchPedimentoPartidas(partidasPage, partidasPageSize, partidasFilters)
|
setPartidasRefreshKey(k => k + 1);
|
||||||
.then((d) => { setPartidas(d.results || []); setPartidasCount(d.count || 0); })
|
|
||||||
.catch(err => console.error('Error recargando partidas:', err));
|
|
||||||
} else if (tab === 'cove') {
|
} else if (tab === 'cove') {
|
||||||
fetchPedimentoCoves && fetchPedimentoCoves(covesPage, covesPageSize, covesFilters)
|
setCovesRefreshKey(k => k + 1);
|
||||||
.then((d) => { setCoves(d.results || []); setCovesCount(d.count || 0); })
|
|
||||||
.catch(err => console.error('Error recargando coves:', err));
|
|
||||||
} else if (tab === 'edoc') {
|
} else if (tab === 'edoc') {
|
||||||
fetchPedimentoEdocuments && fetchPedimentoEdocuments(edocsPage, edocsPageSize, edocsFilters)
|
setEdocsRefreshKey(k => k + 1);
|
||||||
.then((d) => { setEdocs(d.results || []); setEdocsCount(d.count || 0); })
|
|
||||||
.catch(err => console.error('Error recargando edocs:', err));
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage(`Error: ${error.message}`, 'error');
|
showMessage(`Error: ${error.message}`, 'error');
|
||||||
@@ -5156,31 +5215,33 @@ useEffect(() => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label className="block mb-1 text-sm font-medium text-gray-700">
|
||||||
COVE descargado
|
Estado COVE
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={covesFilters.cove_descargado}
|
value={covesFilters.cove_estado}
|
||||||
onChange={(e) => setCovesFilters(prev => ({ ...prev, cove_descargado: e.target.value }))}
|
onChange={(e) => setCovesFilters(prev => ({ ...prev, cove_estado: 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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="">Todos</option>
|
<option value="">Todos</option>
|
||||||
<option value="true">Descargado</option>
|
<option value="pendiente">Pendiente</option>
|
||||||
<option value="false">No descargado</option>
|
<option value="descargado">Descargado</option>
|
||||||
|
<option value="error">Error</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label className="block mb-1 text-sm font-medium text-gray-700">
|
||||||
Acuse descargado
|
Estado acuse
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={covesFilters.acuse_cove_descargado}
|
value={covesFilters.acuse_cove_estado}
|
||||||
onChange={(e) => setCovesFilters(prev => ({ ...prev, acuse_cove_descargado: e.target.value }))}
|
onChange={(e) => setCovesFilters(prev => ({ ...prev, acuse_cove_estado: 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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="">Todos</option>
|
<option value="">Todos</option>
|
||||||
<option value="true">Descargado</option>
|
<option value="pendiente">Pendiente</option>
|
||||||
<option value="false">No descargado</option>
|
<option value="descargado">Descargado</option>
|
||||||
|
<option value="error">Error</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -5344,22 +5405,10 @@ useEffect(() => {
|
|||||||
{formatDate(cove.created_at)}
|
{formatDate(cove.created_at)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
{renderEstadoDescargaBadge(cove.cove_estado, cove.cove_descargado, cove.cove_intentos, cove.ultimo_error)}
|
||||||
cove.cove_descargado
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: 'bg-yellow-100 text-yellow-800'
|
|
||||||
}`}>
|
|
||||||
{cove.cove_descargado ? 'Descargado' : 'Pendiente'}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
{renderEstadoDescargaBadge(cove.acuse_cove_estado, cove.acuse_cove_descargado, cove.acuse_cove_intentos, cove.ultimo_error)}
|
||||||
cove.acuse_cove_descargado
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: 'bg-yellow-100 text-yellow-800'
|
|
||||||
}`}>
|
|
||||||
{cove.acuse_cove_descargado ? 'Descargado' : 'Pendiente'}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm font-medium text-right whitespace-nowrap">
|
<td className="px-6 py-4 text-sm font-medium text-right whitespace-nowrap">
|
||||||
<div className="flex items-center justify-end space-x-2">
|
<div className="flex items-center justify-end space-x-2">
|
||||||
@@ -5663,31 +5712,33 @@ useEffect(() => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label className="block mb-1 text-sm font-medium text-gray-700">
|
||||||
EDocs descargado
|
Estado EDocs
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={edocsFilters.edocument_descargado}
|
value={edocsFilters.edocument_estado}
|
||||||
onChange={(e) => setEdocsFilters(prev => ({ ...prev, edocument_descargado: e.target.value }))}
|
onChange={(e) => setEdocsFilters(prev => ({ ...prev, edocument_estado: 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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="">Todos</option>
|
<option value="">Todos</option>
|
||||||
<option value="true">Descargado</option>
|
<option value="pendiente">Pendiente</option>
|
||||||
<option value="false">No descargado</option>
|
<option value="descargado">Descargado</option>
|
||||||
|
<option value="error">Error</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label className="block mb-1 text-sm font-medium text-gray-700">
|
||||||
Acuse descargado
|
Estado acuse
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={edocsFilters.acuse_descargado}
|
value={edocsFilters.acuse_estado}
|
||||||
onChange={(e) => setEdocsFilters(prev => ({ ...prev, acuse_descargado: e.target.value }))}
|
onChange={(e) => setEdocsFilters(prev => ({ ...prev, acuse_estado: 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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="">Todos</option>
|
<option value="">Todos</option>
|
||||||
<option value="true">Descargado</option>
|
<option value="pendiente">Pendiente</option>
|
||||||
<option value="false">No descargado</option>
|
<option value="descargado">Descargado</option>
|
||||||
|
<option value="error">Error</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -5875,22 +5926,10 @@ useEffect(() => {
|
|||||||
{formatDate(edoc.created_at)}
|
{formatDate(edoc.created_at)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
{renderEstadoDescargaBadge(edoc.edocument_estado, edoc.edocument_descargado, edoc.edocument_intentos, edoc.ultimo_error)}
|
||||||
edoc.edocument_descargado
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: 'bg-yellow-100 text-yellow-800'
|
|
||||||
}`}>
|
|
||||||
{edoc.edocument_descargado ? 'Descargado' : 'Pendiente'}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
{renderEstadoDescargaBadge(edoc.acuse_estado, edoc.acuse_descargado, edoc.acuse_intentos, edoc.ultimo_error)}
|
||||||
edoc.acuse_descargado
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: 'bg-yellow-100 text-yellow-800'
|
|
||||||
}`}>
|
|
||||||
{edoc.acuse_descargado ? 'Descargado' : 'Pendiente'}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm font-medium text-right whitespace-nowrap" style={{minWidth: '140px'}}>
|
<td className="px-6 py-4 text-sm font-medium text-right whitespace-nowrap" style={{minWidth: '140px'}}>
|
||||||
<div className="flex items-center justify-end space-x-2 flex-nowrap">
|
<div className="flex items-center justify-end space-x-2 flex-nowrap">
|
||||||
@@ -7335,7 +7374,7 @@ useEffect(() => {
|
|||||||
<p className="text-sm font-medium text-blue-800">Información</p>
|
<p className="text-sm font-medium text-blue-800">Información</p>
|
||||||
<p className="mt-1 text-sm text-blue-700">
|
<p className="mt-1 text-sm text-blue-700">
|
||||||
{activeTab === 'pedimento'
|
{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.' }
|
: 'Los archivos se subirán al pedimento actual. Se aceptan múltiples formatos de archivo.' }
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -937,16 +937,6 @@ export default function Reports() {
|
|||||||
setIsExporting(true);
|
setIsExporting(true);
|
||||||
|
|
||||||
try {
|
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 = `
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-500"></div>
|
|
||||||
<p class="text-sm text-gray-600">Preparando exportación DataStage...</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(progressDiv);
|
|
||||||
|
|
||||||
// DETECCIÓN AUTOMÁTICA DEL MODO
|
// DETECCIÓN AUTOMÁTICA DEL MODO
|
||||||
const modo = modelosConCampos.length > 1 ? 'multiple' : 'simple';
|
const modo = modelosConCampos.length > 1 ? 'multiple' : 'simple';
|
||||||
|
|
||||||
@@ -959,7 +949,6 @@ export default function Reports() {
|
|||||||
if (modo === 'simple') {
|
if (modo === 'simple') {
|
||||||
// MODO SIMPLE: solo un modelo con campos
|
// MODO SIMPLE: solo un modelo con campos
|
||||||
const modeloUnico = modelosConCampos[0];
|
const modeloUnico = modelosConCampos[0];
|
||||||
const modelData = datastageModels.find(m => m.model === modeloUnico);
|
|
||||||
|
|
||||||
exportData.model = modeloUnico;
|
exportData.model = modeloUnico;
|
||||||
exportData.fields = modelFieldsMap[modeloUnico];
|
exportData.fields = modelFieldsMap[modeloUnico];
|
||||||
@@ -976,14 +965,8 @@ export default function Reports() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resto del código de exportación...
|
// El backend encola la generación en Celery y responde 202 con task_id;
|
||||||
progressDiv.innerHTML = `
|
// el progreso llega por SSE al TaskProgressCard y ahí se descarga el archivo.
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-green-500"></div>
|
|
||||||
<p class="text-sm text-gray-600">Generando archivo DataStage...</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await fetchWithAuth(`${API_URL}/reports/exportmodel/datastage/`, {
|
const response = await fetchWithAuth(`${API_URL}/reports/exportmodel/datastage/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -997,68 +980,26 @@ export default function Reports() {
|
|||||||
throw new Error(errorData.error || errorData.message || 'Error al exportar DataStage');
|
throw new Error(errorData.error || errorData.message || 'Error al exportar DataStage');
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await response.blob();
|
const data = await response.json();
|
||||||
|
if (data.task_id) {
|
||||||
const contentDisposition = response.headers.get('Content-Disposition');
|
addTask({
|
||||||
|
task_id: data.task_id,
|
||||||
let fileName = '';
|
label: 'Reporte DataStage',
|
||||||
|
organizacion_id: globalFilters.organizacion,
|
||||||
if (contentDisposition) {
|
taskType: 'report',
|
||||||
const filenameMatch = contentDisposition.match(/filename="(.+?)"/) ||
|
report_id: data.report_id,
|
||||||
contentDisposition.match(/filename=([^;]+)/);
|
status: 'submitted',
|
||||||
|
});
|
||||||
if (filenameMatch) {
|
pendingReportTasksRef.current.add(data.task_id);
|
||||||
fileName = filenameMatch[1].replace(/"/g, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fileName) {
|
showMessage('Reporte solicitado. Puedes ver el progreso en la barra inferior.', 'success');
|
||||||
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 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);
|
|
||||||
|
|
||||||
progressDiv.innerHTML = `
|
|
||||||
<div class="flex items-center gap-3 text-green-600">
|
|
||||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
||||||
</svg>
|
|
||||||
<p class="text-sm">¡Exportación completada! (Modo ${modo})</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
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) {
|
} catch (error) {
|
||||||
console.error('❌ ERROR AL EXPORTAR DATASTAGE:', error);
|
console.error('❌ ERROR AL EXPORTAR DATASTAGE:', error);
|
||||||
showMessage(error.message || 'Error al exportar DataStage. Por favor intente nuevamente.', 'error');
|
showMessage(error.message || 'Error al exportar DataStage. Por favor intente nuevamente.', 'error');
|
||||||
} finally {
|
} finally {
|
||||||
setIsExporting(false);
|
setIsExporting(false);
|
||||||
const progressDiv = document.querySelector('.fixed.bottom-4.right-4');
|
|
||||||
if (progressDiv) progressDiv.remove();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1795,10 +1736,61 @@ export default function Reports() {
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<span>{isExporting ? 'Generando archivo...' : `Generar y descargar ${exportFormat.toUpperCase()}`}</span>
|
<span>{isExporting ? 'Generando archivo...' : `Generar reporte ${exportFormat.toUpperCase()}`}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-10">
|
||||||
|
<h2 className="text-lg font-bold text-slate-700 mb-4">Historial de Reportes</h2>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="min-w-full bg-white rounded-lg shadow border border-slate-200">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-slate-100">
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">ID</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Estado</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Creado</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Finalizado</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Error</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Descargar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{reports.filter(r => r.report_type === 'datastage').length > 0 ? (
|
||||||
|
reports
|
||||||
|
.filter(r => r.report_type === 'datastage')
|
||||||
|
.map((r) => (
|
||||||
|
<tr key={r.report_id}>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.report_id}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.status}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.created_at}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.finished_at}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-red-500">{r.error_message ? r.error_message : '-'}</td>
|
||||||
|
<td className="px-4 py-2 text-xs">
|
||||||
|
{r.status === 'ready' ? (
|
||||||
|
<button
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
onClick={() => handleDownloadReport(r.report_id)}
|
||||||
|
>
|
||||||
|
Descargar
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="text-slate-400">-</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={6} className="px-4 py-2 text-center text-slate-400">
|
||||||
|
No hay reportes de datastage
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>{`
|
<style>{`
|
||||||
.skeleton-animation {
|
.skeleton-animation {
|
||||||
animation: shimmer 2s linear infinite;
|
animation: shimmer 2s linear infinite;
|
||||||
|
|||||||
Reference in New Issue
Block a user