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") soap_headers = { 'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': 'http://www.ventanillaunica.gob.mx/ventanilla/EdocumentosService/consultarEdocumento', 'Accept-Encoding': 'gzip,deflate', } # --- 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): soap_xml = edocs_vu_controller.generate_edoc_template(**kwargs) response = await edocs_vu_controller.make_request_async( "ventanilla-edocs-HA/EdocumentosServiceWS?wsdl", data=soap_xml, headers=soap_headers ) 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_edoc_data(response.text) if edoc_base64 is None: raise Exception("No se pudo extraer el documento de la respuesta SOAP.") pdf_bytes = _decode_base64_content(edoc_base64) 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"), "acuse_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." }