import base64 import re import logging import os from typing import Any, Dict, Optional import xml.etree.ElementTree as ET from fastapi import HTTPException from utils.helpers import soap_error from .controllers import edocs_rest_controller, edocs_vu_controller from ..common import create_service_response, create_error_response # Logger para el módulo logger = logging.getLogger("app.api") # --- FUNCIONES AUXILIARES --- def _get_file_name(**kwargs) -> 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', '') doc = kwargs.get('edoc', {}) 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) # Enviar documento a EFC try: pedimento_efc = kwargs.get('pedimento', {}) pedimento_app = pedimento_efc.get('pedimento_app', 'N/A') organizacion_efc = pedimento_efc.get('organizacion', None) pedimento_id_efc = pedimento_efc.get("id", None) file_name_request = f"VU_ED_{pedimento_app}_{numero_documento}_REQUEST.xml" document_response = await edocs_rest_controller.post_document( soap_response=soap_xml, organizacion=organizacion_efc, pedimento=pedimento_id_efc, file_name=file_name_request, document_type=21 # Tipo de documento para request de e-document, ) except Exception as e: logger.error(f"Error al enviar documento request: {e}") response = await edocs_vu_controller.make_request_async( "Ventanilla-HA/ServicioEdocument/ServicioEdocument.svc", data=soap_xml, headers=soap_headers ) # Validar respuesta del servicio SOAP if response is None: logger.error("No se obtuvo respuesta del servicio SOAP") raise HTTPException( status_code=500, detail=create_error_response( message="Error al contactar el servicio SOAP", errors=["No se obtuvo respuesta del servicio"], metadata={ "edoc_number": numero_documento, "username": usuario } ) ) if response.status_code != 200: logger.error(f"Error en la solicitud SOAP: {response.status_code}") raise HTTPException( status_code=response.status_code, detail=create_error_response( message="Error en la solicitud SOAP", errors=[f"Código de estado: {response.status_code}"], data={"soap_response": response.text[:500]}, metadata={ "status_code": response.status_code, "edoc_number": numero_documento } ) ) if soap_error(response): logger.error("Respuesta SOAP contiene error de VUCEM") raise HTTPException( status_code=500, detail=create_error_response( message="Error en la respuesta del servicio SOAP", errors=["La respuesta contiene un error de VUCEM"], data={"soap_response": response.text[:500]}, metadata={"edoc_number": numero_documento} ) ) try: edoc_base64 = extract_pdf_bytes_from_xml_content(response.text) except ValueError as ve: logger.error(f"Error extrayendo contenido del XML: {ve}") raise HTTPException( status_code=500, detail=create_error_response( message="Error al procesar la respuesta SOAP", errors=[str(ve)], metadata={"edoc_number": numero_documento} ) ) if edoc_base64 is None: logger.error("No se pudo extraer el documento de la respuesta SOAP") raise HTTPException( status_code=500, detail=create_error_response( message="Error al extraer el documento", errors=["No se pudo encontrar el documento en la respuesta SOAP"], metadata={"edoc_number": numero_documento} ) ) pdf_bytes = edoc_base64['pdf_bytes'] if not pdf_bytes: logger.error("No se pudo decodificar el documento PDF") raise HTTPException( status_code=500, detail=create_error_response( message="Error al decodificar el documento", errors=["El contenido del documento está vacío o es inválido"], metadata={ "edoc_number": numero_documento, "has_cadena_original": bool(edoc_base64.get('cadena_original')), "has_sello_digital": bool(edoc_base64.get('sello_digital')) } ) ) # Validar formato PDF if not pdf_bytes.startswith(b'%PDF'): logger.error("El contenido decodificado no es un PDF válido") raise HTTPException( status_code=500, detail=create_error_response( message="El documento recibido no es un PDF válido", errors=["El contenido no tiene el formato PDF esperado"], metadata={ "edoc_number": numero_documento, "content_start": str(pdf_bytes[:20]) } ) ) 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) # No guardaremos el archivo localmente por seguridad logger.debug(f"Procesando documento {numero_documento} para pedimento {pedimento_id}") 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: logger.error("Error al enviar el documento a la API interna") raise HTTPException( status_code=500, detail=create_error_response( message="Error al guardar el documento en el sistema", errors=["No se pudo enviar el documento a la API interna"], metadata={ "file_name": _file_name, "edoc_number": numero_documento } ) ) if rest_response.get("id") is None: logger.error("Respuesta de API interna sin ID válido") raise HTTPException( status_code=500, detail=create_error_response( message="Error al procesar la respuesta del sistema", errors=["La respuesta de la API no contiene un ID válido"], data={"api_response": rest_response} ) ) logger.info("Documento enviado, actualizando status de Edoc") try: edoc_status_response = await change_edocument_status( edoc=doc, status=True, pedimento=pedimento ) except Exception as e: logger.warning(f"Error al actualizar estado del documento: {e}") # No fallamos aquí porque el documento ya se guardó exitosamente logger.info(f"E-document {numero_documento} procesado exitosamente") return create_service_response( message=f"E-document {numero_documento} procesado exitosamente", data={ "document_response": rest_response, "file_name": _file_name, "numero_documento": numero_documento, "edoc_update_response": edoc_status_response if edoc_status_response else None }, metadata={ "document_type": 5, "pedimento_app": pedimento.get('pedimento_app'), "organizacion": organizacion, "content_type": "application/pdf", "has_cadena_original": bool(edoc_base64.get('cadena_original')), "has_sello_digital": bool(edoc_base64.get('sello_digital')) } ) 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 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.")