From f60c581a02acd9d9fef2673f39df9c5405b9c1e1 Mon Sep 17 00:00:00 2001 From: marcos Date: Mon, 25 May 2026 14:49:51 -0600 Subject: [PATCH] fix/T2026-05-054-procesar-acuses --- src/api/edocuments.js | 9 + src/pages/Auditor.jsx | 495 ++++++++++++++++++++--------- src/pages/PedimentoDetail.jsx | 566 +++++++++++++++++++++++++++------- 3 files changed, 820 insertions(+), 250 deletions(-) diff --git a/src/api/edocuments.js b/src/api/edocuments.js index 8cf56fb..d19f1a6 100644 --- a/src/api/edocuments.js +++ b/src/api/edocuments.js @@ -56,3 +56,12 @@ export const downloadAcuseEdocument = async (edocId) => { window.URL.revokeObjectURL(url); document.body.removeChild(a); }; + +export const resetAcuseEdocument = async (edocId) => { + const response = await fetchWithAuth( + `${API_BASE_URL}/customs/edocuments/${edocId}/reset-acuse/`, + { method: 'POST' } + ); + if (!response.ok) throw new Error(await extractApiError(response)); + return response.json(); +}; diff --git a/src/pages/Auditor.jsx b/src/pages/Auditor.jsx index 08d52d7..de30586 100644 --- a/src/pages/Auditor.jsx +++ b/src/pages/Auditor.jsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import { getWithAuth, postWithAuth, fetchWithAuth } from '../fetchWithAuth'; import { useNotification } from '../context/NotificationContext'; @@ -34,15 +34,43 @@ const PROCESAMIENTO_URL_MAP = { function AuditResultModal({ modal, iniciandoProcesamiento, onClose, onIniciarProcesamiento }) { const { tipo, pedimento_app, data } = modal; - // si el backend no retorna 'estado' (ej. pedimento completo), tratamos como no completado - const esCompletado = data?.estado === 'completado' || data?.auditoria_completa == true; - // remesa sin remesas: no tiene nada que procesar, ocultar botón - const sinNadaQueHacer = tipo === 'rm' && data?.tiene_remesas === false; + + const esCompletado = tipo === 'pc' + ? (!data?.hay_pendientes && !data?.hay_errores) + : (data?.estado === 'completado' || data?.auditoria_completa == true); + + const sinNadaQueHacer = (tipo === 'rm' && data?.tiene_remesas === false); const mostrarBotonProcesar = !esCompletado && !sinNadaQueHacer; - const tituloTipo = TIPO_LABELS[tipo] || tipo; + const hayErroresPC = tipo === 'pc' && data?.hay_errores; - const iconoEstado = esCompletado ? ( + const headerBg = hayErroresPC + ? 'bg-red-50 border-b border-red-100' + : esCompletado + ? 'bg-green-50 border-b border-green-100' + : 'bg-amber-50 border-b border-amber-100'; + + const badge = hayErroresPC ? ( + + Con errores + + ) : esCompletado ? ( + + Completado + + ) : ( + + Pendiente + + ); + + const iconoEstado = hayErroresPC ? ( +
+ + + +
+ ) : esCompletado ? (
@@ -56,144 +84,315 @@ function AuditResultModal({ modal, iniciandoProcesamiento, onClose, onIniciarPro
); + return (
-
+
{/* Header */} -
+

{tituloTipo}

{pedimento_app}

- - - {esCompletado ? 'Completado' : 'En proceso'} - + {badge}
- {/* Cuerpo */} -
+ {/* Cuerpo scrolleable */} +
{iconoEstado} - {/* Mensaje principal */} -

{data?.mensaje}

- - {/* Resumen numérico */} - {data?.resumen && ( -
- {Object.entries(data.resumen).map(([key, val]) => ( -
- {val} - - {key.replace(/_/g, ' ')} - -
- ))} -
- )} - - {/* Resumen de XMLs analizados — solo muestra lo relevante */} - {data?.xmls_analizados && (() => { - const total = data.xmls_analizados.length; - const conError = data.xmls_analizados.filter(x => x.informacion_extraida?.tiene_error === true); - const auditOk = data.auditoria_completa !== false; - - return ( -
- {/* Contadores compactos */} -
-
- {data.archivos_xml_encontrados ?? total} - Encontrados + {tipo === 'pc' ? ( + <> + {/* Información del XML */} + {data?.informacion_xml && ( +
+
+

Información del pedimento

-
- {total} - Analizados -
-
0 ? 'bg-red-50 border-red-100' : 'bg-green-50 border-green-100'}`}> - 0 ? 'text-red-700' : 'text-green-700'}`}>{conError.length} - 0 ? 'text-red-500' : 'text-green-500'}`}>Con error +
+ {[ + { label: 'Contribuyente', value: `${data.informacion_xml.contribuyente_rfc} — ${data.informacion_xml.contribuyente_nombre}` }, + { label: 'Tipo operación', value: `${data.informacion_xml.clave_pedimento} · ${data.informacion_xml.tipo_operacion}` }, + // { label: 'Aduana', value: data.informacion_xml.aduana_clave }, + // { label: 'Partidas', value: data.informacion_xml.numero_partidas }, + { label: 'Fecha pago', value: data.informacion_xml.fecha_pago?.split('-06:00')[0] ?? '—' }, + // { label: 'Fecha presentación', value: data.informacion_xml.fecha_presentacion?.split('-06:00')[0] ?? '—' }, + { label: 'Valor comercial', value: data.informacion_xml.valor_comercial_total != null ? `$${data.informacion_xml.valor_comercial_total.toLocaleString('es-MX')}` : '—' }, + { label: 'Tipo cambio', value: data.informacion_xml.tipo_cambio }, + ].map(({ label, value }) => ( +
+ {label} + {value ?? '—'} +
+ ))}
+ )} - {/* Advertencia si auditoría incompleta */} - {!auditOk && ( -
- - + {/* Diagnóstico del PC cuando no está descargado */} + {!data?.pc_descargado && ( +
+
+

Pedimento completo

+
+ {data?.fuente && ( + + {data.fuente === 'datastage' ? 'Datastage' : 'Manual'} + + )} + No descargado +
+
+
+ {[ + { label: 'Aduana', value: data?.datos?.aduana, ok: data?.validacion?.aduana_valida }, + { label: 'Patente', value: data?.datos?.patente, ok: data?.validacion?.patente_valida }, + { label: 'Núm. pedimento', value: data?.datos?.numero_pedimento, ok: data?.validacion?.pedimento_valido }, + { label: 'Núm. operación', value: data?.datos?.numero_operacion, ok: data?.validacion?.numero_operacion_presente }, + { label: 'Contribuyente', value: data?.datos?.contribuyente_rfc, ok: data?.validacion?.tiene_contribuyente }, + { label: 'Credenciales VUCEM', value: data?.validacion?.tiene_credenciales_vucem ? 'Configuradas' : 'No configuradas', ok: data?.validacion?.tiene_credenciales_vucem }, + ].map(({ label, value, ok }) => ( +
+ {label} + + {value ?? } + + {ok === false && ( + + + + )} + {ok === true && ( + + + + )} +
+ ))} +
+ {!data?.puede_procesar && data?.razones_no_puede_procesar?.length > 0 && ( +
+ {data.razones_no_puede_procesar.map((r, i) => ( +

• {r}

+ ))} +
+ )} +
+ )} + + {/* Errores detectados */} + {data?.hay_errores && data?.errores_detectados?.length > 0 && ( +
+ + Errores detectados ({data.errores_detectados.length}) + + - Auditoría incompleta — algunos documentos no pudieron verificarse + +
+ {data.errores_detectados.map((err, i) => ( +
{typeof err === 'string' ? err : JSON.stringify(err.nombre_archivo)}
+ ))}
- )} +
+ )} - {/* Detalle de XMLs con error */} - {conError.length > 0 && ( -
- - XMLs con error ({conError.length}) - - - - -
- {conError.map((xml) => ( -
-

{xml.nombre_archivo}

-

{xml.tipo_documento}

+ {/* Nota de verificación manual cuando hay errores */} + {data?.hay_errores && ( +
+ + + + Se detectaron errores en las respuestas de VUCEM. Revisa y corrige los documentos de error antes de reintentar el procesamiento. +
+ )} + + {/* Resultado del procesamiento PC */} + {data?.pc_resultado_procesamiento && (() => { + const r = data.pc_resultado_procesamiento; + const isEncolado = r.estado === 'encolado'; + const isYaDescargado = r.estado === 'ya_descargado'; + const isNoPuede = r.estado === 'no_puede_procesar'; + return ( +
+ {/* Header del resultado */} +
+

+ {isEncolado ? 'Procesamiento encolado' : isYaDescargado ? 'Pedimento completo ya descargado' : 'No se puede procesar'} +

+
+ + {r.fuente === 'datastage' ? 'Datastage' : 'Manual'} + +
+
+ + {/* Datos del pedimento */} +
+ {[ + { label: 'Aduana', value: r.datos?.aduana, ok: r.validacion?.aduana_valida }, + { label: 'Patente', value: r.datos?.patente, ok: r.validacion?.patente_valida }, + { label: 'Núm. pedimento', value: r.datos?.numero_pedimento, ok: r.validacion?.pedimento_valido }, + { label: 'Núm. operación', value: r.datos?.numero_operacion, ok: r.validacion?.numero_operacion_presente }, + { label: 'Contribuyente', value: r.datos?.contribuyente_rfc, ok: r.validacion?.tiene_contribuyente }, + { label: 'Credenciales VUCEM', value: r.validacion?.tiene_credenciales_vucem ? 'Configuradas' : 'No configuradas', ok: r.validacion?.tiene_credenciales_vucem }, + { label: 'Fecha pago', value: r.datos?.fecha_pago }, + ].map(({ label, value, ok }) => ( +
+ {label} + + {value ?? } + + {ok === false && ( + + + + )} + {ok === true && ( + + + + )}
))}
-
- )} - {/* Todo bien */} - {conError.length === 0 && auditOk && ( -

Todos los XMLs analizados están correctos

- )} -
- ); - })()} + {/* Razones por las que no puede procesar */} + {isNoPuede && r.validacion?.razones_no_puede_procesar?.length > 0 && ( +
+ {r.validacion.razones_no_puede_procesar.map((razon, i) => ( +

• {razon}

+ ))} +
+ )} - {/* Pendientes */} - {data?.pendientes && data.pendientes.length > 0 && ( -
-

Pendientes ({data.pendientes.length})

-
- {data.pendientes.map((item, i) => ( -
- - - - {item} + {/* Task ID si fue encolado */} + {isEncolado && r.task_id && ( +
+ Task ID: + {r.task_id} +
+ )}
- ))} -
-
- )} + ); + })()} - {/* COVEs para remesa */} - {tipo === 'rm' && data?.coves && data.coves.length > 0 && ( -
-

COVEs registrados ({data.coves.length})

-
- {data.coves.map((cove, i) => ( - - {cove} - - ))} -
-
+ {/* Sin pendientes ni errores */} + {data?.pc_descargado && !data?.hay_errores && ( +

Pedimento completo descargado sin errores

+ )} + + ) : ( + <> + {/* Mensaje principal */} +

{data?.mensaje}

+ + {/* Resumen numérico */} + {data?.resumen && ( +
+ {Object.entries(data.resumen).map(([key, val]) => ( +
+ {val} + + {key.replace(/_/g, ' ')} + +
+ ))} +
+ )} + + {/* Resumen de XMLs analizados */} + {data?.xmls_analizados && (() => { + const total = data.xmls_analizados.length; + const conError = data.xmls_analizados.filter(x => x.informacion_extraida?.tiene_error === true); + const auditOk = data.auditoria_completa !== false; + return ( +
+
+
+ {data.archivos_xml_encontrados ?? total} + Encontrados +
+
+ {total} + Analizados +
+
0 ? 'bg-red-50 border-red-100' : 'bg-green-50 border-green-100'}`}> + 0 ? 'text-red-700' : 'text-green-700'}`}>{conError.length} + 0 ? 'text-red-500' : 'text-green-500'}`}>Con error +
+
+ {!auditOk && ( +
+ + + + Auditoría incompleta — algunos documentos no pudieron verificarse +
+ )} + {conError.length > 0 && ( +
+ + XMLs con error ({conError.length}) + + + + +
+ {conError.map((xml) => ( +
+

{xml.nombre_archivo}

+

{xml.tipo_documento}

+
+ ))} +
+
+ )} + {conError.length === 0 && auditOk && ( +

Todos los XMLs analizados están correctos

+ )} +
+ ); + })()} + + {/* Pendientes (lista plana para rm/ac/cove/edoc/pt) */} + {data?.pendientes && Array.isArray(data.pendientes) && data.pendientes.length > 0 && ( +
+

Pendientes ({data.pendientes.length})

+
+ {data.pendientes.map((item, i) => ( +
+ + + + {item} +
+ ))} +
+
+ )} + + {/* COVEs para remesa */} + {tipo === 'rm' && data?.coves && data.coves.length > 0 && ( +
+

COVEs registrados ({data.coves.length})

+
+ {data.coves.map((cove, i) => ( + + {cove} + + ))} +
+
+ )} + )}
{/* Footer */} -
+
{mostrarBotonProcesar && ( - {/* nuevo botón “view” */} -
{/* RM - Remesas */} diff --git a/src/pages/PedimentoDetail.jsx b/src/pages/PedimentoDetail.jsx index 55bc4d4..55c9ba6 100644 --- a/src/pages/PedimentoDetail.jsx +++ b/src/pages/PedimentoDetail.jsx @@ -22,7 +22,7 @@ import { fetchPedimentoCompleto} from '../api/pedimentoCompleto'; import { fetchWithAuth, postWithAuth, putWithAuth, postFormDataWithAuth } from '../fetchWithAuth'; import { fetchTasks } from '../api/procesos.ts'; import { fetchPedimentoCoves, downloadCove, downloadAcuseCove } from '../api/coves'; -import { fetchPedimentoEdocuments, downloadEdocument, downloadAcuseEdocument } from '../api/edocuments'; +import { fetchPedimentoEdocuments, downloadEdocument, downloadAcuseEdocument, resetAcuseEdocument } from '../api/edocuments'; import { getTaskStatusLabel, getTaskStatusColor, isTaskActionable, isTaskFinal } from '../api/taskStatus'; import { useParams, Link } from 'react-router-dom'; import { useNotification } from '../context/NotificationContext'; @@ -338,6 +338,10 @@ const handleDeleteSelectedPedimentoDocuments = async () => { const [processingAcuseEdoc, setProcessingAcuseEdoc] = useState(null); // Modal de advertencia por documentos con errores en EDocs const [edocErrorModal, setEdocErrorModal] = useState({ open: false, edoc: null, tipo: null }); + // Modal de advertencia por documentos con errores en COVEs + const [coveErrorModal, setCoveErrorModal] = useState({ open: false, cove: null, tipo: null }); + // Modal de confirmación para reprocesar partidas ya descargadas + const [partidaModal, setPartidaModal] = useState({ open: false, partida: null }); // Agregar estado para el modal de documentos const [showDocumentsModal, setShowDocumentsModal] = useState(false); @@ -346,8 +350,9 @@ const handleDeleteSelectedPedimentoDocuments = async () => { // Función para manejar la visualización de documentos COVE const handleShowCoveDocuments = (cove) => { - if (cove.documentos && cove.documentos.length > 0) { - setSelectedVUDocuments(cove.documentos); + const buenos = (cove.documentos || []).filter(d => d.document_type >= 1 && d.document_type <= 9); + if (buenos.length > 0) { + setSelectedVUDocuments(buenos); setSelectedVUNumber(cove.numero_cove); setShowDocumentsModal(true); } @@ -364,8 +369,9 @@ const handleDeleteSelectedPedimentoDocuments = async () => { // Función para manejar la visualización de Edocuments const handleShowEDocuments = (edocument) => { - if (edocument.documentos && edocument.documentos.length > 0) { - setSelectedVUDocuments(edocument.documentos); + const buenos = (edocument.documentos || []).filter(d => d.document_type >= 1 && d.document_type <= 9); + if (buenos.length > 0) { + setSelectedVUDocuments(buenos); setSelectedVUNumber(edocument.numero_edocument); setShowDocumentsModal(true); } @@ -2297,6 +2303,20 @@ const handleDeleteSelectedPedimentoDocuments = async () => { } }; + // Restablece acuse_descargado=False (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); + } catch (error) { + showMessage(`Error al restablecer el acuse: ${error.message}`, 'error'); + fetchEdocs(id, edocsPage, edocsPageSize, edocsFilters); + } + }; + const handleEdocRequest = async (edoc) => { console.log('Request edoc:', edoc); showMessage(`Procesando petición para EDocs #${edoc.numero_edocument}...`, 'info'); @@ -4754,16 +4774,24 @@ useEffect(() => {
{/* Botón Petición (solo activo si está pendiente) */} + +
+ + ) : edocErrorModal.tipo === 'ya_descargado_edoc' || edocErrorModal.tipo === 'ya_descargado_acuse' ? ( + /* Caso: ya descargado correctamente, usuario quiere forzar reintento */ + <> +
+
+ + + +
+

+ {edocErrorModal.tipo === 'ya_descargado_acuse' ? 'Acuse ya descargado' : 'EDoc ya descargado'} +

+
+

+ Este documento ({edocErrorModal.edoc.numero_edocument}) ya está descargado. +

+

+ Si desea reintentar la descarga, tenga en cuenta que es bajo su responsabilidad. +

+
+ + +
+ + ) : ( + /* Caso normal: errores previos conocidos (tipo 22/26) */ + <> +
+
+ + + +
+

+ {edocErrorModal.tipo === 'acuse' ? 'Acuse de EDoc con errores' : 'EDoc con errores'} +

+
+

+ Este EDocument ({edocErrorModal.edoc.numero_edocument}) cuenta con errores en la respuesta recibida. + Revisa el documento de error antes de volver a intentarlo. +

+

+ Puedes continuar con el flujo de trabajo, pero se recomienda corregir el error primero. +

+ {/* Lista de documentos de error */} + {(() => { + const tipoFiltro = edocErrorModal.tipo === 'acuse' ? 26 : 22; + const docsError = edocErrorModal.edoc.documentos?.filter(d => d.document_type === tipoFiltro) || []; + return docsError.length > 0 ? ( +
+

Documentos de error

+
    + {docsError.map(doc => { + const nombreArchivo = (doc.archivo || '').split('/').pop() || 'Documento'; + return ( +
  • + + {nombreArchivo} + + +
  • + ); + })} +
+
+ ) : null; + })()} +
+ + +
+ + )} +
+
+ )} + + {/* Modal de advertencia: COVE/Acuse con documentos de error */} + {/* Modal de confirmación: Partida ya descargada */} + {partidaModal.open && partidaModal.partida && (
-
- - +
+ +
-

- {edocErrorModal.tipo === 'acuse' ? 'Acuse de EDoc con errores' : 'EDoc con errores'} -

+

Partida ya descargada

-

- Este EDocument ({edocErrorModal.edoc.numero_edocument}) cuenta con errores en la respuesta recibida. - Revisa el documento de error antes de volver a intentarlo. + La partida {partidaModal.partida.numero_partida} ya está descargada.

- Puedes continuar con el flujo de trabajo, pero se recomienda corregir el error primero. + Si desea reintentar la descarga, tenga en cuenta que es bajo su responsabilidad.

- - {/* Lista de documentos de error */} - {(() => { - const tipoFiltro = edocErrorModal.tipo === 'acuse' ? 26 : 22; - const docsError = edocErrorModal.edoc.documentos?.filter(d => d.document_type === tipoFiltro) || []; - return docsError.length > 0 ? ( -
-

Documentos de error

-
    - {docsError.map(doc => { - const nombreArchivo = (doc.archivo || '').split('/').pop() || 'Documento'; - return ( -
  • - - {nombreArchivo} - - -
  • - ); - })} -
-
- ) : null; - })()} -
)} + {coveErrorModal.open && coveErrorModal.cove && ( +
+
+ + {coveErrorModal.tipo === 'ya_descargado_cove' || coveErrorModal.tipo === 'ya_descargado_acuse_cove' ? ( + /* Caso: ya descargado correctamente, usuario quiere forzar reintento */ + <> +
+
+ + + +
+

+ {coveErrorModal.tipo === 'ya_descargado_acuse_cove' ? 'Acuse de COVE ya descargado' : 'COVE ya descargado'} +

+
+

+ Este documento ({coveErrorModal.cove.numero_cove}) ya está descargado. +

+

+ Si desea reintentar la descarga, tenga en cuenta que es bajo su responsabilidad. +

+
+ + +
+ + ) : ( + /* Caso normal: errores previos conocidos (tipo 20/24) */ + <> +
+
+ + + +
+

+ {coveErrorModal.tipo === 'acuse' ? 'Acuse de COVE con errores' : 'COVE con errores'} +

+
+ +

+ Este COVE ({coveErrorModal.cove.numero_cove}) cuenta con errores en la respuesta recibida. + Revisa el documento de error antes de volver a intentarlo. +

+

+ Puedes continuar con el flujo de trabajo, pero se recomienda corregir el error primero. +

+ + {/* Lista de documentos de error */} + {(() => { + const tipoFiltro = coveErrorModal.tipo === 'acuse' ? 24 : 20; + const docsError = coveErrorModal.cove.documentos?.filter(d => d.document_type === tipoFiltro) || []; + return docsError.length > 0 ? ( +
+

Documentos de error

+
    + {docsError.map(doc => { + const nombreArchivo = (doc.archivo || '').split('/').pop() || 'Documento'; + return ( +
  • + + {nombreArchivo} + + +
  • + ); + })} +
+
+ ) : null; + })()} + +
+ + +
+ + )} +
+
+ )} +
); } \ No newline at end of file -- 2.49.1