from http.client import HTTPException import base64 import re import logging import xml.etree.ElementTree as ET from utils.helpers import soap_error from .controllers import edocs_rest_controller, edocs_vu_controller logger = logging.getLogger("app.api") # --- FUNCIONES AUXILIARES --- def _decode_base64_content(base64_content): try: cleaned_content = re.sub(r'&#x[0-9a-fA-F]+;', '', base64_content) cleaned_content = re.sub(r'&#[0-9]+;', '', cleaned_content) cleaned_content = re.sub(r'[\s\n\r\t]', '', cleaned_content) cleaned_content = re.sub(r'[^A-Za-z0-9+/=]', '', cleaned_content) missing_padding = len(cleaned_content) % 4 if missing_padding: cleaned_content += '=' * (4 - missing_padding) return base64.b64decode(cleaned_content) except Exception as e: logger.error(f"Error al decodificar Base64: {e}") try: return base64.b64decode(cleaned_content, validate=False) except Exception: return None def _extract_edoc_data(soap_response_text: str) -> str: try: xml_start = soap_response_text.find(' str: pedimento = kwargs.get('pedimento', {}) pedimento_app = pedimento.get('pedimento_app', 'N/A') idEDocument = kwargs['edoc'].get('numero_edocument', 'N/A') return f"vu_ED_{pedimento_app}_{idEDocument}.pdf" # --- FUNCIONES DE SERVICIO --- async def obtener_edoc(**kwargs): credencial = kwargs.get('credencial', {}) usuario = credencial.get('user', '') password = credencial.get('password', '') numero_documento = kwargs['edoc'].get('numero_edocument', '') soap_headers = { 'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': 'http://tempuri.org/IServicioEdocument/GetDocumento' } soap_xml = edocs_vu_controller.generate_edocument_template(username=usuario, password=password, idEDocument=numero_documento) print(soap_xml) response = await edocs_vu_controller.make_request_async( "Ventanilla-HA/ServicioEdocument/ServicioEdocument.svc", data=soap_xml, headers=soap_headers ) print(response.text) if response is None: raise Exception("No se obtuvo respuesta del servicio SOAP.") if response.status_code != 200: raise Exception(f"Error en la solicitud SOAP: {response.status_code}") if soap_error(response): raise Exception("Respuesta SOAP contiene error de VUCEM.") edoc_base64 = extract_pdf_bytes_from_xml_content(response.text) if edoc_base64 is None: raise Exception("No se pudo extraer el documento de la respuesta SOAP.") pdf_bytes = edoc_base64['pdf_bytes'] if not pdf_bytes: raise HTTPException(status_code=500, detail="No se pudo decodificar el documento") if not pdf_bytes.startswith(b'%PDF'): logger.warning("El contenido decodificado no parece ser un PDF válido") pedimento = kwargs.get('pedimento', {}) numero_documento = kwargs['edoc'].get('numero_edocument', '') _file_name = _get_file_name(**kwargs) organizacion = pedimento.get("organizacion", None) pedimento_id = pedimento.get("id", None) try: with open(_file_name, "wb") as f: f.write(pdf_bytes) logger.info(f"PDF guardado localmente en {_file_name}") except Exception as e: logger.error(f"Error guardando el PDF localmente: {e}") rest_response = await edocs_rest_controller.post_document( binary_content=pdf_bytes, organizacion=organizacion, pedimento=pedimento_id, file_name=_file_name, document_type=5 ) if rest_response is None: raise Exception("No se pudo enviar el documento a la API interna.") if rest_response.get("id") is None: raise Exception("La respuesta de la API interna no contiene un ID válido.") logger.info("Documento enviado, actualizando status de Edoc") edoc_status_response = await change_edocument_status( edoc=kwargs.get('edoc'), status=True, pedimento=pedimento ) return { "document_response": rest_response, "file_name": _file_name, "numero_documento": numero_documento, "edoc_update_response": edoc_status_response if edoc_status_response else None } async def change_edocument_status(edoc: dict, status: bool, pedimento: dict): data = { "id": edoc.get("id"), "edocument_descargado": status, "numero_edocument": edoc.get("numero_edocument"), "pedimento": pedimento.get("id"), "organizacion": pedimento.get("organizacion"), } response = await edocs_rest_controller.put_edocument(edocument_id=edoc.get("id"), data=data) return response async def obtener_edocs_masivo(**kwargs): logger.info("Iniciando la orquestación de descarga masiva de Edocs.") numeros_documentos = kwargs.get("edocs", []) if not numeros_documentos: return {"status": "warning", "message": "No se encontraron números de documento para procesar."} for edoc in numeros_documentos: try: logger.info(f"Procesando Edoc: {edoc.get('numero_edocument', 'N/A')}") edoc = { "edoc": edoc, "pedimento": kwargs.get("pedimento"), "credencial": kwargs.get("credencial") } await obtener_edoc(**edoc) logger.info(f"Edoc {edoc.get('numero_edocument', 'N/A')} procesado exitosamente.") except Exception as e: logger.error(f"Error procesando Edoc {edoc.get('numero_edocument', 'N/A')}: {str(e)}", exc_info=True) continue # Continuar con el siguiente edoc en caso de error return { "status": "pending", "total_documentos": len(numeros_documentos), "message": "La orquestación de descarga masiva ha sido registrada." } 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 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 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 } else: raise ValueError("No se encontró el tag con contenido válido. Verifique que el XML contiene el tag con datos base64.")