diff --git a/examples/test_extract_file.py b/examples/test_extract_file.py new file mode 100644 index 0000000..e0978f5 --- /dev/null +++ b/examples/test_extract_file.py @@ -0,0 +1,13 @@ +import os +from utils.peticiones import extract_pdf_bytes_from_xml + +xml_path = os.path.abspath("./test.xml") + +result = extract_pdf_bytes_from_xml(xml_path) + +if result and result["pdf_bytes"]: + with open("output_test.pdf", "wb") as f: + f.write(result["pdf_bytes"]) + print("PDF extraído y guardado como output_test.pdf") +else: + print("No se pudo extraer el PDF del XML.") diff --git a/main.py b/main.py index 8bce169..79af994 100644 --- a/main.py +++ b/main.py @@ -5,40 +5,40 @@ from api.api_v1.api import api_router from fastapi.middleware.cors import CORSMiddleware # Configuración inicial del logging (debe estar al inicio del archivo) -logging.config.dictConfig({ - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "default": { - "()": "uvicorn.logging.DefaultFormatter", - "fmt": "%(levelprefix)s %(asctime)s | %(name)s | %(message)s", - "datefmt": "%Y-%m-%d %H:%M:%S", - "use_colors": True, - }, - "access": { - "()": "uvicorn.logging.AccessFormatter", - "fmt": '%(levelprefix)s %(asctime)s | %(client_addr)s | "%(request_line)s" %(status_code)s', - "datefmt": "%Y-%m-%d %H:%M:%S", - }, - }, - "handlers": { - "default": { - "formatter": "default", - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout", - }, - "access": { - "formatter": "access", - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout", - }, - }, - "loggers": { - "": {"handlers": ["default"], "level": "DEBUG"}, - "uvicorn.error": {"level": "DEBUG"}, - "uvicorn.access": {"handlers": ["access"], "level": "DEBUG", "propagate": False}, - }, -}) +# logging.config.dictConfig({ +# "version": 1, +# "disable_existing_loggers": False, +# "formatters": { +# "default": { +# "()": "uvicorn.logging.DefaultFormatter", +# "fmt": "%(levelprefix)s %(asctime)s | %(name)s | %(message)s", +# "datefmt": "%Y-%m-%d %H:%M:%S", +# "use_colors": True, +# }, +# "access": { +# "()": "uvicorn.logging.AccessFormatter", +# "fmt": '%(levelprefix)s %(asctime)s | %(client_addr)s | "%(request_line)s" %(status_code)s', +# "datefmt": "%Y-%m-%d %H:%M:%S", +# }, +# }, +# "handlers": { +# "default": { +# "formatter": "default", +# "class": "logging.StreamHandler", +# "stream": "ext://sys.stdout", +# }, +# "access": { +# "formatter": "access", +# "class": "logging.StreamHandler", +# "stream": "ext://sys.stdout", +# }, +# }, +# "loggers": { +# "": {"handlers": ["default"], "level": "DEBUG"}, +# "uvicorn.error": {"level": "DEBUG"}, +# "uvicorn.access": {"handlers": ["access"], "level": "DEBUG", "propagate": False}, +# }, +# }) def create_application() -> FastAPI: """Función factory para crear la aplicación FastAPI""" diff --git a/test.xml b/test.xml deleted file mode 100644 index 54335bb..0000000 --- a/test.xml +++ /dev/null @@ -1,439 +0,0 @@ - - - - - - 2025-07-10T14:24:04Z - 2025-07-10T14:25:04Z - - - - - - false - 21277177344 - - 2001238 - - - 1 - Importacion - - - IN - IMPORTACION TEMPORAL DE INSUMOS POR IMMEX - - - 9 - INTERIOR DEL PAIS - - - 230 - NOGALES, NOGALES, SONORA. - - 20.76130 - 6745.682 - - 7 - CARRETERO - - - 7 - CARRETERO - - - 7 - CARRETERO - - GUMM710831HSRZRG08 - GLG1502247K9 - 0.00 - 1642523.00 - 1642523.00 - - - - 230 - NOGALES, NOGALES, SONORA. - - - 230 - NOGALES, NOGALES, SONORA. - - - IN - IMPORTACION TEMPORAL DE INSUMOS POR IMMEX - - 2022-07-25-06:00 - 2009506 - 1653 - 2022-07-15-06:00 - - - MTK861014317 - MAQUILAS TETA KAWI S.A. DE C.V. - - CARRETERA INTERNACIONAL GUADALAJARA-NOGALES - KM 1969 - Empalme - 85340 - - 0.00 - 0.00 - 0.00 - 0.00 - - 230 - NOGALES, NOGALES, SONORA. - - - 2022-07-05-06:00 - - 1 - FECHA DE ENTRADA A TERRITORIO NAL. - - - - 2022-07-15-06:00 - - 2 - FECHA DE PAGO DE LAS CONTRIBUCIONES - - - 1412.00 - 0 - 1412.00 - - MEX - MEXICO (ESTADOS UNIDOS MEXICANOS) - - - - - 15 - PREVALIDAAAA - - - 2 - ESPECIFICO - - 240.0000000000 - - 0 - EFECTIVO - - 240.00 - - - - 23 - IVA PREV - - - 1 - PORCENTUAL - - 16.0000000000 - - 0 - EFECTIVO - - 38.00 - - - - 1 - DTA - - - 4 - ESPECIFICO (CUOTA FIJA) DTA - - 378.0000000000 - - 0 - EFECTIVO - - 1134.00 - - - 84-401607200 - LIBRA GUAYMAS LLC - 0.000000 - 0.00 - - - 84-401607200 - LIBRA GUAYMAS LLC - 0.000000 - 0.00 - - - 84-401607200 - LIBRA GUAYMAS LLC - 0.000000 - 0.00 - - - COVE2258M9IT4 - - FCA - FRANCO TRANSPORTISTA (... LUGAR DESIGNADO) - - 0.00 - 0.000000 - 84-401607200 - LIBRA GUAYMAS LLC - - - COVE2257S9033 - - FCA - FRANCO TRANSPORTISTA (... LUGAR DESIGNADO) - - 0.00 - 0.000000 - 84-401607200 - LIBRA GUAYMAS LLC - - - COVE2257PY1Z4 - - FCA - FRANCO TRANSPORTISTA (... LUGAR DESIGNADO) - - 0.00 - 0.000000 - 84-401607200 - LIBRA GUAYMAS LLC - - - - - PC - PEDIMENTO CONSOLIDADO - - - - - ED - E_DOCUMENT DOCUMENTO DIGITALIZADO - - 0170220NCKKN2 - - - - ED - E_DOCUMENT DOCUMENTO DIGITALIZADO - - 0433220889CP2 - - - - ED - E_DOCUMENT DOCUMENTO DIGITALIZADO - - 0436220ER86M4 - - - - SO - SOCIO COMERCIAL CERTIFICADO - - AA - - - - ED - E_DOCUMENT DOCUMENTO DIGITALIZADO - - 0436220ER86H4 - - - - CI - CERTIFICACION EN MATERIA DE IVA E IEPS - - AAA - - - - IM - AUTORIZACION DE EMPRESA CON PROGRAMA IMMEX - - 45242006 - - - - PP - PROGRAMAS DE PROMOCIÓN SECTORIAL. - - 20011635 - - - - ED - E_DOCUMENT DOCUMENTO DIGITALIZADO - - 0170220NG6SJ4 - - - - RC - REMESAS DE CONSOLIDADO - - 1-3 - - - - ED - E_DOCUMENT DOCUMENTO DIGITALIZADO - - 0436220ESLMS1 - - - - IC - IMPORTADOR CERTIFICADO - - O - - - PEDIMENTO CONSOLIDADO DE IMPORTACION DE CONFORMIDAD CON LOS - ARTICULOS 37, 37-A DE LA LEY ADUANERA Y REGLA DE COMERCIO EXTERIOR 1.9.19., - CORRESPONDIENTE A LA SEMANA DEL 04 DE JULIO AL 10 DE JULIO DEL 2022. DE - CONFORMIDAD CON EL ARTICULO 89DE LA LEY ADUANERA SE REALIZA RECTIFICACION DE - PEDIMENTO PARA MODIFICAR LO SIGUIENTE: SE RECTIFICA PEDIMENTO EN: VALOR - COMERCIAL DICE: 73,817.52 DEBE DECIR: 79,114.63 PESO BRUTO DICE: 6695.390 DEBE - DECIR: 6745.682 SE RECTIFICA COVE DE FACTURA LIBRA7902RM DICE: COVE2257X6DZ1 - DEBE DECIR: COVE2258M9IT4 SE CORRIGE PARTIDA # 81 EN CANTIDAD Y UNIDAD DE MEDIDA - SE AGREGAN PARTIDAS # 93, 94, 95 y 96. SE DIGITALIZA FACTURA E DOCUMENT - 0170220NG6SJ4 SE DIGITALIZA REGLA 8va E DOCUMENT 0436220ESLMS1 - - Relacion de - facturas - - FACTR: LIBRA GUAYMAS - LLC,84-401607200,LIBRA7896RM,05-07-2022,COVE2257PY1Z4,USD,26,664.250 FACTR: - LIBRA GUAYMAS - LLC,84-401607200,LIBRA7898RM,06-07-2022,COVE2257S9033,USD,5,422.700 FACTR: LIBRA - GUAYMAS LLC,84-401607200,LIBRA7902RM,08-07-2022,COVE2258M9IT4,USD,47,027.680 - 96 - 56 - 7 - 69 - 43 - 95 - 64 - 70 - 8 - 65 - 16 - 79 - 20 - 48 - 26 - 18 - 27 - 54 - 34 - 93 - 44 - 17 - 90 - 37 - 9 - 11 - 51 - 73 - 36 - 66 - 63 - 40 - 88 - 19 - 59 - 15 - 2 - 94 - 68 - 78 - 32 - 39 - 89 - 62 - 4 - 47 - 74 - 41 - 80 - 6 - 42 - 28 - 92 - 49 - 12 - 13 - 84 - 10 - 31 - 87 - 50 - 76 - 22 - 30 - 53 - 71 - 38 - 3 - 33 - 45 - 23 - 25 - 21 - 67 - 85 - 14 - 46 - 29 - 35 - 91 - 24 - 72 - 82 - 61 - 86 - 83 - 77 - 75 - 58 - 81 - 55 - 57 - 5 - 52 - 60 - 1 - - - DTA - 1 - - 364.00 - - 0 - EFECTIVO - - - - - - \ No newline at end of file diff --git a/utils/peticiones.py b/utils/peticiones.py index cb20a33..d5310d0 100644 --- a/utils/peticiones.py +++ b/utils/peticiones.py @@ -129,36 +129,54 @@ def extract_acuse_documento_from_soap(soap_response_text): # Testeado logger.error(f"Error extrayendo acuseDocumento: {e}") return None + def extract_pdf_bytes_from_xml(xml_path): - tree = ET.parse(xml_path) - root = tree.getroot() - # Busca el tag (ajusta el namespace si es necesario) + """ + Igual que extract_pdf_bytes_from_xml_content pero recibe una ruta de archivo. + """ + with open(xml_path, 'r', encoding='utf-8') as f: + xml_content = f.read() + return extract_pdf_bytes_from_xml_content(xml_content) + +def extract_pdf_bytes_from_xml_content(xml_content: str): + """ + Extrae el PDF y metadatos desde un string XML. + """ + root = ET.fromstring(xml_content) file_elem = root.find('.//File') - if file_elem is not None and file_elem.text: - # Limpia el contenido base64 + if file_elem is None: + for elem in root.iter(): + if elem.tag.endswith('File') and elem.text: + file_elem = elem + break + if file_elem is not None and file_elem.text and file_elem.text.strip(): base64_data = file_elem.text.strip().replace('\n', '').replace('\r', '') pdf_bytes = base64.b64decode(base64_data) cadena_original = None sello_digital = None - - # Buscar CadenaOriginal y SelloDigital en el XML cadena_elem = root.find('.//CadenaOriginal') + if cadena_elem is None: + for elem in root.iter(): + if elem.tag.endswith('CadenaOriginal') and elem.text: + cadena_elem = elem + break if cadena_elem is not None and cadena_elem.text: cadena_original = cadena_elem.text.strip() - sello_elem = root.find('.//SelloDigital') + if sello_elem is None: + for elem in root.iter(): + if elem.tag.endswith('SelloDigital') and elem.text: + sello_elem = elem + break if sello_elem is not None and sello_elem.text: sello_digital = sello_elem.text.strip() - return { "pdf_bytes": pdf_bytes, "cadena_original": cadena_original, "sello_digital": sello_digital } - return pdf_bytes - else: - raise ValueError("No se encontró el tag con contenido válido.") + raise ValueError("No se encontró el tag con contenido válido. Verifique que el XML contiene el tag con datos base64.") def decode_acuse_base64_content(base64_content): # Testeado """ @@ -846,32 +864,20 @@ async def get_soap_edocument(credenciales, response_service, soap_controller, ed # Extraer contenido Base64 del acuse logger.info("Extrayendo documento binario del edocument...") - response = extract_pdf_bytes_from_xml(soap_response.text) + response = extract_pdf_bytes_from_xml_content(soap_response.text) pdf_bytes = response.get('pdf_bytes') # cadena_original = response.get('cadena_original') # sello_digital = response.get('sello_digital') - if not acuse_base64: - logger.error("No se pudo extraer el contenido del acuseDocumento") - raise HTTPException(status_code=500, detail="No se pudo extraer el documento del acuse") - - # Decodificar contenido Base64 - - response_edoc = rest_controller.put_edocument(edocument_id=ide, data={ - "numero_edocument": edocument['numero_edocument'], - "pedimento": response_service['pedimento'], - # "cadena_original": cadena_original, - # "sello_digital": sello_digital - }) if not pdf_bytes: - logger.error("No se pudo decodificar el contenido Base64 del acuse") - raise HTTPException(status_code=500, detail="No se pudo decodificar el documento del acuse") - + logger.error("No se pudo decodificar el contenido Base64 del documento e-document") + raise HTTPException(status_code=500, detail="No se pudo decodificar el documento del e-document") + # Verificar que es un PDF válido if not pdf_bytes.startswith(b'%PDF'): logger.warning("El contenido decodificado no parece ser un PDF válido") # Continuar de todos modos, podría ser otro tipo de documento - + # Generar nombre del archivo remesas = 1 if response_service['pedimento'].get('remesas', 0) else 0 patente = response_service['pedimento'].get('patente', 'N/A') @@ -880,7 +886,7 @@ async def get_soap_edocument(credenciales, response_service, soap_controller, ed tipo_operacion = response_service['pedimento'].get('tipo_operacion', 'N/A') pedimento = response_service['pedimento'].get('pedimento', 'N/A') _file_name = f"vu_EDC_{remesas}{no_partidas}{tipo_operacion}_{aduana}_{patente}_{pedimento}_{idx}.pdf" - + # Enviar el documento PDF usando binary_content logger.info(f"Enviando documento PDF: {_file_name} ({len(pdf_bytes)} bytes)") document_response = await rest_controller.post_document( @@ -904,7 +910,7 @@ async def get_soap_edocument(credenciales, response_service, soap_controller, ed logger.error(f"Error inesperado en get_acuse: {e}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") - raise HTTPException(status_code=500, detail=f"Error interno al procesar acuse: {str(e)}") + raise HTTPException(status_code=500, detail=f"Error interno al procesar acuse") async def get_soap_cove(credenciales, response_service, soap_controller, cove, idx): """