Primera version estable de microservicios
This commit is contained in:
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
884
utils/peticiones.py
Normal file
884
utils/peticiones.py
Normal file
@@ -0,0 +1,884 @@
|
||||
import logging
|
||||
logger = logging.getLogger("app.api")
|
||||
from fastapi import HTTPException
|
||||
from controllers.RESTController import rest_controller
|
||||
from typing import Dict, Any
|
||||
import xml.etree.ElementTree as ET
|
||||
import base64
|
||||
import re
|
||||
|
||||
from schemas.serviceSchema import ServiceBaseSchema
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from controllers.XMLController import xml_controller
|
||||
|
||||
|
||||
def validate_pedimento_data(response_service: Dict[str, Any], credenciales: Dict[str, Any]) -> tuple: # Testeado
|
||||
"""
|
||||
Valida y extrae los datos necesarios para la petición SOAP.
|
||||
|
||||
Args:
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
credenciales: Credenciales VUCEM
|
||||
|
||||
Returns:
|
||||
tuple: (username, password, aduana, patente, pedimento)
|
||||
|
||||
Raises:
|
||||
HTTPException: Si faltan datos requeridos
|
||||
"""
|
||||
# Validar credenciales
|
||||
username = credenciales.get('usuario')
|
||||
password = credenciales.get('password')
|
||||
|
||||
if not username or not password:
|
||||
logger.error("Credenciales VUCEM incompletas")
|
||||
raise HTTPException(status_code=400, detail="Credenciales VUCEM incompletas")
|
||||
|
||||
# Validar datos del pedimento
|
||||
pedimento_data = response_service.get('pedimento', {})
|
||||
aduana = pedimento_data.get('aduana')
|
||||
patente = pedimento_data.get('patente')
|
||||
pedimento = pedimento_data.get('pedimento')
|
||||
numero_operacion = pedimento_data.get('numero_operacion')
|
||||
|
||||
if not all([aduana, patente, pedimento]):
|
||||
logger.error(f"Datos del pedimento incompletos - Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}")
|
||||
raise HTTPException(status_code=400, detail="Datos del pedimento incompletos")
|
||||
|
||||
return username, password, aduana, patente, pedimento, numero_operacion
|
||||
|
||||
def extract_acuse_documento_from_soap(soap_response_text): # Testeado
|
||||
"""
|
||||
Extrae el contenido del tag <acuseDocumento> de la respuesta SOAP multipart.
|
||||
|
||||
Args:
|
||||
soap_response_text (str): Contenido de la respuesta SOAP
|
||||
|
||||
Returns:
|
||||
str: Contenido Base64 del acuseDocumento o None si no se encuentra
|
||||
"""
|
||||
try:
|
||||
# Primero, extraer la parte XML del contenido multipart
|
||||
xml_start = soap_response_text.find('<?xml')
|
||||
if xml_start == -1:
|
||||
logger.error("No se encontró contenido XML en la respuesta SOAP")
|
||||
return None
|
||||
|
||||
# Extraer solo la parte XML
|
||||
xml_content = soap_response_text[xml_start:]
|
||||
|
||||
# Si hay más contenido multipart después, cortarlo
|
||||
boundary_end = xml_content.find('--uuid:')
|
||||
if boundary_end != -1:
|
||||
xml_content = xml_content[:boundary_end]
|
||||
|
||||
# Parsear el XML
|
||||
root = ET.fromstring(xml_content.strip())
|
||||
|
||||
# Buscar el elemento acuseDocumento con namespaces
|
||||
namespaces = {
|
||||
'S': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||
'ns3': 'http://www.ventanillaunica.gob.mx/ws/consulta/acuses/'
|
||||
}
|
||||
|
||||
# Buscar el elemento acuseDocumento
|
||||
acuse_elemento = root.find('.//ns3:responseConsultaAcuses/acuseDocumento', namespaces)
|
||||
|
||||
if acuse_elemento is None:
|
||||
# Intentar sin namespace
|
||||
acuse_elemento = root.find('.//acuseDocumento')
|
||||
|
||||
if acuse_elemento is not None and acuse_elemento.text:
|
||||
return acuse_elemento.text.strip()
|
||||
else:
|
||||
logger.error("No se encontró el tag <acuseDocumento> o está vacío")
|
||||
return None
|
||||
|
||||
except ET.ParseError as e:
|
||||
logger.error(f"Error parseando XML: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
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 <File> (ajusta el namespace si es necesario)
|
||||
file_elem = root.find('.//File')
|
||||
if file_elem is not None and file_elem.text:
|
||||
# Limpia el contenido base64
|
||||
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 not None and cadena_elem.text:
|
||||
cadena_original = cadena_elem.text.strip()
|
||||
|
||||
sello_elem = root.find('.//SelloDigital')
|
||||
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 <File> con contenido válido.")
|
||||
|
||||
def decode_acuse_base64_content(base64_content): # Testeado
|
||||
"""
|
||||
Decodifica el contenido Base64 del acuse y limpia caracteres especiales.
|
||||
|
||||
Args:
|
||||
base64_content (str): Contenido codificado en Base64
|
||||
|
||||
Returns:
|
||||
bytes: Contenido decodificado o None si hay error
|
||||
"""
|
||||
try:
|
||||
# Limpiar el contenido Base64 de manera exhaustiva
|
||||
cleaned_content = base64_content
|
||||
|
||||
# Remover entidades HTML/XML como 
, 
, etc.
|
||||
cleaned_content = re.sub(r'&#x[0-9a-fA-F]+;', '', cleaned_content)
|
||||
cleaned_content = re.sub(r'&#[0-9]+;', '', cleaned_content)
|
||||
|
||||
# Remover espacios en blanco, saltos de línea, etc.
|
||||
cleaned_content = re.sub(r'[\s\n\r\t]', '', cleaned_content)
|
||||
|
||||
# Remover caracteres no válidos para Base64
|
||||
cleaned_content = re.sub(r'[^A-Za-z0-9+/=]', '', cleaned_content)
|
||||
|
||||
logger.info(f"Contenido Base64 limpiado: {len(cleaned_content)} caracteres")
|
||||
|
||||
# Agregar padding si es necesario
|
||||
missing_padding = len(cleaned_content) % 4
|
||||
if missing_padding:
|
||||
cleaned_content += '=' * (4 - missing_padding)
|
||||
logger.info(f"Padding agregado: {4 - missing_padding} caracteres '='")
|
||||
|
||||
# Decodificar Base64
|
||||
decoded_content = base64.b64decode(cleaned_content)
|
||||
|
||||
logger.info(f"Contenido decodificado exitosamente: {len(decoded_content)} bytes")
|
||||
return decoded_content
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error decodificando Base64: {e}")
|
||||
|
||||
# Intentar con validación estricta deshabilitada
|
||||
try:
|
||||
logger.info("Intentando decodificación con validación relajada...")
|
||||
decoded_content = base64.b64decode(cleaned_content, validate=False)
|
||||
logger.info("¡Decodificación exitosa con validación relajada!")
|
||||
return decoded_content
|
||||
except Exception as e2:
|
||||
logger.error(f"Error también con validación relajada: {e2}")
|
||||
return None
|
||||
|
||||
def soap_error(soap_response): # Testeado
|
||||
"""
|
||||
Verifica si la respuesta SOAP no contiene errores.
|
||||
|
||||
Args:
|
||||
soap_response: Respuesta del servicio SOAP
|
||||
|
||||
Returns:
|
||||
bool: True si no hay errores, False en caso contrario
|
||||
"""
|
||||
if '<ns3:tieneError>true</ns3:tieneError>' in soap_response.text:
|
||||
return True
|
||||
|
||||
# Aquí podrías agregar más lógica para verificar errores específicos en el XML
|
||||
return False
|
||||
|
||||
async def get_soap_pedimento_completo(credenciales, response_service, soap_controller): # Testeado
|
||||
"""
|
||||
Procesa la petición SOAP para obtener el pedimento completo y guarda el documento.
|
||||
|
||||
Args:
|
||||
credenciales: Diccionario con credenciales VUCEM (usuario, password)
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
soap_controller: Instancia del controlador SOAP
|
||||
|
||||
Returns:
|
||||
dict: Respuesta con el servicio, respuesta SOAP y documento guardado
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay errores en la petición SOAP o al guardar el documento
|
||||
"""
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, _ = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_pedimento_completo_template(
|
||||
username=username,
|
||||
password=password,
|
||||
aduana=aduana,
|
||||
patente=patente,
|
||||
pedimento=pedimento
|
||||
)
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8'
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request_async(
|
||||
"ventanilla-ws-pedimentos/ConsultarPedimentoCompletoService?wsdl",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
|
||||
data = xml_controller.extract_data(soap_response.text)
|
||||
remesas = 1 if data.get('remesas', 0) else 2
|
||||
patente = response_service['pedimento'].get('patente', 'N/A')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = data.get('numero_partidas', 0)
|
||||
tipo_operacion = data.get('tipo_operacion', 'N/A')
|
||||
pedimento = response_service['pedimento'].get('pedimento', 'N/A')
|
||||
|
||||
_file_name = f"vu_PC_{remesas}{no_partidas}{tipo_operacion}_{aduana}_{patente}_{pedimento}.xml"
|
||||
# Enviar el documento XML como respuesta
|
||||
document_response = await rest_controller.post_document(
|
||||
soap_response=soap_response,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=2
|
||||
)
|
||||
|
||||
data['organizacion'] = response_service['organizacion']
|
||||
data['id'] = response_service['pedimento']['id']
|
||||
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response,
|
||||
"xml_content": data
|
||||
}
|
||||
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error inesperado en get_pedimento_completo: {e}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Error interno al procesar pedimento completo: {str(e)}")
|
||||
|
||||
async def get_soap_remesas(credenciales, response_service, soap_controller): # Testeado
|
||||
"""
|
||||
Procesa la petición SOAP para obtener remesas y guarda el documento.
|
||||
|
||||
Args:
|
||||
credenciales: Diccionario con credenciales VUCEM (usuario, password)
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
soap_controller: Instancia del controlador SOAP
|
||||
|
||||
Returns:
|
||||
dict: Respuesta con el servicio, respuesta SOAP y documento guardado
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay errores en la petición SOAP o al guardar el documento
|
||||
"""
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, numero_operacion = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}, Numero Operacion: {numero_operacion}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_remesas_template(
|
||||
username=username,
|
||||
password=password,
|
||||
aduana=aduana,
|
||||
patente=patente,
|
||||
pedimento=pedimento,
|
||||
numero_operacion=numero_operacion
|
||||
)
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8'
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request_async(
|
||||
"ventanilla-ws-pedimentos/ConsultarRemesasService?wsdl",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
|
||||
# data = xml_controller.extract_data(soap_response.text)
|
||||
# # Enviar el documento XML como respuesta
|
||||
remesas = 1 if response_service['pedimento'].get('remesas', 0) else 0
|
||||
patente = response_service['pedimento'].get('patente', 'N/A')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = response_service['pedimento'].get('numero_partidas', 0)
|
||||
tipo_operacion = response_service['pedimento'].get('tipo_operacion', 'N/A')
|
||||
pedimento = response_service['pedimento'].get('pedimento', 'N/A')
|
||||
|
||||
_file_name = f"vu_RM_{remesas}{no_partidas}{tipo_operacion}_{aduana}_{patente}_{pedimento}.xml"
|
||||
|
||||
document_response = await rest_controller.post_document(
|
||||
soap_response=soap_response,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=3
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response
|
||||
}
|
||||
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error inesperado en get_remesas: {e}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Error interno al procesar remesas: {str(e)}")
|
||||
|
||||
async def get_soap_partidas(credenciales, response_service, soap_controller, partida): # Testeado
|
||||
"""
|
||||
Procesa la petición SOAP para obtener partidas de un pedimento y guarda el documento.
|
||||
|
||||
Args:
|
||||
credenciales: Diccionario con credenciales VUCEM (usuario, password)
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
soap_controller: Instancia del controlador SOAP
|
||||
partida: Número de partida a consultar
|
||||
|
||||
Returns:
|
||||
dict: Respuesta con el servicio, respuesta SOAP y documento guardado
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay errores en la petición SOAP o al guardar el documento
|
||||
"""
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, numero_operacion = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}, Numero Operacion: {numero_operacion}, Partida: {partida}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_partidas_template(
|
||||
username=username,
|
||||
password=password,
|
||||
aduana=aduana,
|
||||
patente=patente,
|
||||
pedimento=pedimento,
|
||||
numero_operacion=numero_operacion,
|
||||
partida=partida
|
||||
)
|
||||
|
||||
### >>> AQUÍ SE AÑADE EL LOGGER.DEBUG <<< ###
|
||||
logger.debug(f"XML SOAP generado: {soap_xml}") # 👈 Registra el XML completo
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8'
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request_async(
|
||||
"ventanilla-ws-pedimentos/ConsultarPartidaService?wsdl",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
# partida = get_partida_data(soap_response.text)
|
||||
remesas = 1 if response_service['pedimento'].get('remesas', 0) else 0
|
||||
patente = response_service['pedimento'].get('patente', 'N/A')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = response_service['pedimento'].get('numero_partidas', 0)
|
||||
tipo_operacion = response_service['pedimento'].get('tipo_operacion', 'N/A')
|
||||
pedimento = response_service['pedimento'].get('pedimento', 'N/A')
|
||||
|
||||
_file_name = f"vu_PT_{remesas}{no_partidas}{tipo_operacion}_{aduana}_{patente}_{pedimento}_{partida}.xml"
|
||||
|
||||
|
||||
# Aqui entra proceso de alonso
|
||||
|
||||
# respuesta = scraping_partida(xml)
|
||||
|
||||
# 1. Leer XML
|
||||
|
||||
# 2. Obtener Datos del XML
|
||||
|
||||
# 3. Subir los datos a la base datos
|
||||
|
||||
# 4. Generar una respues
|
||||
|
||||
# Enviar el documento XML como respuesta
|
||||
document_response = await rest_controller.post_document(
|
||||
soap_response=soap_response,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=1
|
||||
)
|
||||
|
||||
|
||||
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response
|
||||
}
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error inesperado en get_partidas: {e}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Error interno al procesar partidas: {str(e)}")
|
||||
|
||||
async def get_soap_acuse(credenciales, response_service, soap_controller, edocument, idx): # Testeado
|
||||
"""
|
||||
Procesa la petición SOAP para obtener el acuse de un pedimento y guarda el documento.
|
||||
|
||||
Args:
|
||||
credenciales: Diccionario con credenciales VUCEM (usuario, password)
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
soap_controller: Instancia del controlador SOAP
|
||||
|
||||
Returns:
|
||||
dict: Respuesta con el servicio, respuesta SOAP y documento guardado
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay errores en la petición SOAP o al guardar el documento
|
||||
"""
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, numero_operacion = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}, Numero Operacion: {numero_operacion}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_acuse_template(
|
||||
username=username,
|
||||
password=password,
|
||||
idEDocument=edocument['numero_edocument']
|
||||
)
|
||||
|
||||
### >>> AQUÍ SE AÑADE EL LOGGER.DEBUG <<< ###
|
||||
logger.debug(f"XML SOAP generado: {soap_xml}") # 👈 Registra el XML completo
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
'SOAPAction': 'http://www.ventanillaunica.gob.mx/ventanilla/ConsultaAcusesService/consultarAcuseEdocument',# AcuseCove
|
||||
'Accept-Encoding': 'gzip,deflate',
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request_async(
|
||||
"ventanilla-acuses-HA/ConsultaAcusesServiceWS?wsdl",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
|
||||
# Extraer contenido Base64 del acuse
|
||||
logger.info("Extrayendo documento binario del acuse...")
|
||||
acuse_base64 = extract_acuse_documento_from_soap(soap_response.text)
|
||||
|
||||
|
||||
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
|
||||
logger.info("Decodificando contenido Base64...")
|
||||
pdf_bytes = decode_acuse_base64_content(acuse_base64)
|
||||
|
||||
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")
|
||||
|
||||
# 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')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = response_service['pedimento'].get('numero_partidas', 0)
|
||||
tipo_operacion = response_service['pedimento'].get('tipo_operacion', 'N/A')
|
||||
pedimento = response_service['pedimento'].get('pedimento', 'N/A')
|
||||
_file_name = f"vu_AC_{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(
|
||||
binary_content=pdf_bytes,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=4
|
||||
)
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response
|
||||
}
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
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)}")
|
||||
|
||||
async def get_soap_acuseCOVE(credenciales, response_service, soap_controller, cove, idx): # Testeado
|
||||
"""
|
||||
Procesa la petición SOAP para obtener el acuse COVE de un pedimento y guarda el documento.
|
||||
|
||||
Args:
|
||||
credenciales: Diccionario con credenciales VUCEM (usuario, password)
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
soap_controller: Instancia del controlador SOAP
|
||||
|
||||
Returns:
|
||||
dict: Respuesta con el servicio, respuesta SOAP y documento guardado
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay errores en la petición SOAP o al guardar el documento
|
||||
"""
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, numero_operacion = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}, Numero Operacion: {numero_operacion}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_acuse_template(
|
||||
username=username,
|
||||
password=password,
|
||||
idEDocument=cove['numero_cove']
|
||||
)
|
||||
|
||||
### >>> AQUÍ SE AÑADE EL LOGGER.DEBUG <<< ###
|
||||
logger.debug(f"XML SOAP generado: {soap_xml}") # 👈 Registra el XML completo
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
'SOAPAction': 'http://www.ventanillaunica.gob.mx/ventanilla/ConsultaAcusesService/consultarAcuseCove',
|
||||
'Accept-Encoding': 'gzip,deflate',
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request_async(
|
||||
"ventanilla-acuses-HA/ConsultaAcusesServiceWS?wsdl",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
|
||||
# Extraer contenido Base64 del acuse
|
||||
logger.info("Extrayendo documento binario del acuse cove...")
|
||||
acuse_base64 = extract_acuse_documento_from_soap(soap_response.text)
|
||||
|
||||
|
||||
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
|
||||
logger.info("Decodificando contenido Base64...")
|
||||
pdf_bytes = decode_acuse_base64_content(acuse_base64)
|
||||
|
||||
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")
|
||||
|
||||
# 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')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = response_service['pedimento'].get('numero_partidas', 0)
|
||||
tipo_operacion = response_service['pedimento'].get('tipo_operacion', 'N/A')
|
||||
pedimento = response_service['pedimento'].get('pedimento', 'N/A')
|
||||
_file_name = f"vu_AC_COVE_{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(
|
||||
binary_content=pdf_bytes,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=7
|
||||
)
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response
|
||||
}
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error inesperado en get_acuse cove: {e}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Error interno al procesar acuse cove: {str(e)}")
|
||||
|
||||
async def get_estado_pedimento(credenciales, response_service, soap_controller): # Sin testear
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, numero_operacion = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_estado_pedimento_template(
|
||||
username=username,
|
||||
password=password,
|
||||
aduana=aduana,
|
||||
patente=patente,
|
||||
pedimento=pedimento,
|
||||
numero_operacion=numero_operacion
|
||||
)
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8'
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request_async(
|
||||
"webservice-pedimentos-HA/consultarEstadoPedimento?wsdl",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
|
||||
data = xml_controller.extract_data(soap_response.text)
|
||||
remesas = 1 if data.get('remesas', 0) else 2
|
||||
patente = response_service['pedimento'].get('patente', 'N/A')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = data.get('numero_partidas', 0)
|
||||
tipo_operacion = data.get('tipo_operacion', 'N/A')
|
||||
pedimento = response_service['pedimento'].get('pedimento', 'N/A')
|
||||
|
||||
_file_name = f"vu_EP_{remesas}{no_partidas}{tipo_operacion}_{aduana}_{patente}_{pedimento}.xml"
|
||||
# Enviar el documento XML como respuesta
|
||||
document_response = await rest_controller.post_document(
|
||||
soap_response=soap_response,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=6
|
||||
)
|
||||
|
||||
data['organizacion'] = response_service['organizacion']
|
||||
data['id'] = response_service['pedimento']['id']
|
||||
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response,
|
||||
"xml_content": data
|
||||
}
|
||||
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error inesperado en get_pedimento_completo: {e}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Error interno al procesar pedimento completo: {str(e)}")
|
||||
|
||||
async def get_soap_edocument(credenciales, response_service, soap_controller, edocument, idx):
|
||||
"""
|
||||
Procesa la petición SOAP para obtener el acuse de un pedimento y guarda el documento.
|
||||
|
||||
Args:
|
||||
credenciales: Diccionario con credenciales VUCEM (usuario, password)
|
||||
response_service: Respuesta del servicio con datos del pedimento
|
||||
soap_controller: Instancia del controlador SOAP
|
||||
|
||||
Returns:
|
||||
dict: Respuesta con el servicio, respuesta SOAP y documento guardado
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay errores en la petición SOAP o al guardar el documento
|
||||
"""
|
||||
try:
|
||||
# Extraer credenciales
|
||||
username, password, aduana, patente, pedimento, numero_operacion = validate_pedimento_data(response_service, credenciales)
|
||||
|
||||
logger.info(f"Datos para SOAP - Usuario: {username}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento}, Numero Operacion: {numero_operacion}")
|
||||
|
||||
# Generar template SOAP
|
||||
soap_xml = soap_controller.generate_edocument_template(
|
||||
username=username,
|
||||
password=password,
|
||||
idEDocument=edocument['numero_edocument']
|
||||
)
|
||||
|
||||
# Realizar petición SOAP
|
||||
logger.info("Realizando petición SOAP...")
|
||||
|
||||
# Headers específicos para este servicio SOAP
|
||||
soap_headers = {
|
||||
'Content-Type': 'text/xml; charset=utf-8',
|
||||
'SOAPAction': 'http://tempuri.org/IServicioEdocument/GetDocumento',
|
||||
'Accept-Encoding': 'gzip,deflate',
|
||||
}
|
||||
|
||||
soap_response = await soap_controller.make_request(
|
||||
"Ventanilla-HA/ServicioEdocument/ServicioEdocument.svc",
|
||||
data=soap_xml,
|
||||
headers=soap_headers
|
||||
)
|
||||
|
||||
|
||||
if (soap_response) and (not soap_error(soap_response)):
|
||||
logger.info(f"Petición SOAP exitosa - Status: {soap_response.status_code}")
|
||||
|
||||
# Extraer contenido Base64 del acuse
|
||||
logger.info("Extrayendo documento binario del edocument...")
|
||||
response = extract_pdf_bytes_from_xml(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")
|
||||
|
||||
# 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')
|
||||
aduana = response_service['pedimento'].get('aduana', 'N/A')
|
||||
no_partidas = response_service['pedimento'].get('numero_partidas', 0)
|
||||
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(
|
||||
binary_content=pdf_bytes,
|
||||
organizacion=response_service['organizacion'],
|
||||
pedimento=response_service['pedimento']['id'],
|
||||
file_name=_file_name,
|
||||
document_type=5
|
||||
)
|
||||
return {
|
||||
"servicio": response_service,
|
||||
"documento": document_response
|
||||
}
|
||||
else:
|
||||
logger.error("Error en petición SOAP")
|
||||
raise HTTPException(status_code=500, detail="Error en la petición SOAP al servicio VUCEM")
|
||||
except HTTPException:
|
||||
# Re-lanzar HTTPExceptions sin modificar
|
||||
raise
|
||||
except Exception as e:
|
||||
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)}")
|
||||
|
||||
|
||||
558
utils/servicios.py
Normal file
558
utils/servicios.py
Normal file
@@ -0,0 +1,558 @@
|
||||
from core.config import settings
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from schemas.pedimentoSchema import PedimentoRequest
|
||||
from schemas.serviceSchema import ServiceBaseSchema, ServiceRemesaSchema
|
||||
import asyncio
|
||||
import logging
|
||||
logger = logging.getLogger("app.api")
|
||||
import traceback
|
||||
from typing import Dict, Any, List, Optional
|
||||
from contextlib import asynccontextmanager
|
||||
from controllers.RESTController import rest_controller
|
||||
from controllers.SOAPController import soap_controller
|
||||
from utils.peticiones import get_soap_pedimento_completo, get_soap_remesas, get_soap_partidas, get_soap_acuse, get_soap_edocument
|
||||
from fastapi.responses import JSONResponse
|
||||
from core.config import settings
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
ESTADO_CREADO = 1
|
||||
ESTADO_EN_PROCESO = 2
|
||||
ESTADO_FINALIZADO = 3
|
||||
ESTADO_ERROR = 4
|
||||
|
||||
async def _validate_request_data(request_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Valida los datos básicos requeridos en las peticiones.
|
||||
|
||||
Args:
|
||||
request_data: Diccionario con datos de la petición
|
||||
|
||||
Raises:
|
||||
HTTPException: Si faltan datos requeridos
|
||||
"""
|
||||
if not request_data.get('pedimento'):
|
||||
logger.error("ID del pedimento no proporcionado en la petición")
|
||||
raise HTTPException(status_code=400, detail="ID del pedimento es requerido")
|
||||
|
||||
if not request_data.get('organizacion'):
|
||||
logger.error("ID de la organización no proporcionado en la petición")
|
||||
raise HTTPException(status_code=400, detail="ID de la organización es requerido")
|
||||
|
||||
logger.info(f"Validación exitosa - Pedimento: {request_data['pedimento']}, Organización: {request_data['organizacion']}")
|
||||
|
||||
async def _get_pedimento_service(pedimento_id: str, service_type: int, operation_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Obtiene el servicio de pedimento por tipo.
|
||||
|
||||
Args:
|
||||
pedimento_id: ID del pedimento
|
||||
service_type: Tipo de servicio a obtener
|
||||
operation_name: Nombre de la operación para logging
|
||||
|
||||
Returns:
|
||||
Dict con datos del servicio
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay error al obtener el servicio
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Obteniendo servicio tipo {service_type} para pedimento {pedimento_id} - Operación: {operation_name}")
|
||||
response_service = await rest_controller.get_pedimento_services(pedimento_id, service_type=service_type)
|
||||
logger.info(response_service)
|
||||
|
||||
if not response_service or len(response_service) == 0:
|
||||
logger.error(f"No se encontró servicio tipo {service_type} para pedimento {pedimento_id}")
|
||||
raise HTTPException(status_code=404, detail=f"No se encontró servicio de {operation_name}")
|
||||
|
||||
logger.info(f"Servicio obtenido exitosamente: {response_service[0].get('id', 'N/A')}")
|
||||
return response_service[0]
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error al obtener servicio de {operation_name}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"Error al obtener servicio de {operation_name}")
|
||||
|
||||
async def _get_vucem_credentials(contribuyente_id: str, operation_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Obtiene las credenciales VUCEM para un contribuyente.
|
||||
|
||||
Args:
|
||||
contribuyente_id: ID del contribuyente
|
||||
operation_name: Nombre de la operación para logging
|
||||
|
||||
Returns:
|
||||
Dict con credenciales VUCEM
|
||||
|
||||
Raises:
|
||||
HTTPException: Si hay error al obtener credenciales
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Obteniendo credenciales VUCEM para contribuyente {contribuyente_id} - Operación: {operation_name}")
|
||||
response_credentials = await rest_controller.get_vucem_credentials(contribuyente_id)
|
||||
|
||||
if not response_credentials or len(response_credentials) == 0:
|
||||
logger.error(f"No se encontraron credenciales VUCEM para contribuyente {contribuyente_id}")
|
||||
raise HTTPException(status_code=404, detail="Credenciales VUCEM no encontradas")
|
||||
|
||||
logger.info("Credenciales VUCEM obtenidas exitosamente")
|
||||
return response_credentials[0]
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error al obtener credenciales VUCEM para {operation_name}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail="Error al obtener credenciales VUCEM")
|
||||
|
||||
async def _post_edocuments(response_service: dict, identificadores_ed: list):
|
||||
"""
|
||||
Helper function para enviar documentos digitalizados a la API.
|
||||
|
||||
Args:
|
||||
response_service: Diccionario con datos del servicio
|
||||
identificadores_ed: Lista de identificadores ED a enviar
|
||||
"""
|
||||
responses = []
|
||||
|
||||
for identificador in identificadores_ed:
|
||||
# Preparar datos del documento
|
||||
document_data = {
|
||||
'clave': identificador['clave'],
|
||||
'descripcion': identificador['descripcion'],
|
||||
'numero_edocument': identificador['complemento1'],
|
||||
'organizacion': response_service['organizacion'],
|
||||
'pedimento': response_service['pedimento']['id']
|
||||
}
|
||||
|
||||
try:
|
||||
response = await rest_controller.post_edocument(document_data)
|
||||
if response is None:
|
||||
logger.warning(f"No se pudo enviar el documento {identificador['complemento1']}")
|
||||
continue
|
||||
responses.append(response)
|
||||
logger.info(f"Documento {identificador['complemento1']} enviado exitosamente")
|
||||
except Exception as e:
|
||||
logger.error(f"Error al enviar el documento {identificador['complemento1']}: {e}")
|
||||
continue
|
||||
|
||||
if not responses:
|
||||
raise HTTPException(status_code=500, detail="No se pudo enviar ningún documento digitalizado")
|
||||
|
||||
return responses
|
||||
|
||||
async def _post_coves(response_service: dict, coves: list) -> List[Dict[str, Any]]:
|
||||
responses = []
|
||||
|
||||
for cove in coves:
|
||||
# Preparar datos del documento
|
||||
document_data = {
|
||||
'numero_cove': cove,
|
||||
'organizacion': response_service['organizacion'],
|
||||
'pedimento': response_service['pedimento']['id']
|
||||
}
|
||||
|
||||
try:
|
||||
response = await rest_controller.post_cove(document_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error al enviar el numero de cove {cove}: {e}")
|
||||
continue
|
||||
|
||||
if not responses:
|
||||
raise HTTPException(status_code=500, detail="No se pudo enviar ningún numero de cove")
|
||||
|
||||
return responses
|
||||
|
||||
async def _update_service_status(service_id: int, estado: int, response_service: dict, operation_name: str = "operación") -> bool:
|
||||
"""
|
||||
Actualiza el estado del servicio de manera robusta.
|
||||
|
||||
Args:
|
||||
service_id: ID del servicio
|
||||
estado: Nuevo estado (1=creado, 2=en proceso, 3=finalizado, 4=error)
|
||||
response_service: Datos del servicio
|
||||
operation_name: Nombre de la operación para logging
|
||||
|
||||
Returns:
|
||||
bool: True si se actualizó exitosamente, False en caso contrario
|
||||
"""
|
||||
estado_nombres = {
|
||||
1: "CREADO",
|
||||
2: "EN_PROCESO",
|
||||
3: "FINALIZADO",
|
||||
4: "ERROR"
|
||||
}
|
||||
|
||||
estado_nombre = estado_nombres.get(estado, f"DESCONOCIDO({estado})")
|
||||
|
||||
try:
|
||||
logger.info(f"Actualizando estado del servicio {service_id} a {estado_nombre} - Operación: {operation_name}")
|
||||
|
||||
update_data = {
|
||||
"estado": estado,
|
||||
"pedimento": response_service['pedimento']['id'],
|
||||
"organizacion": response_service['organizacion'],
|
||||
}
|
||||
logger.info(f"Body enviado al endpoint PUT: {update_data}")
|
||||
print(f"Body enviado al endpoint PUT: {update_data}")
|
||||
result = await rest_controller.put_pedimento_service(service_id=service_id, data=update_data)
|
||||
|
||||
if result is None:
|
||||
logger.error(f"Falló la actualización del estado del servicio {service_id} a {estado_nombre}")
|
||||
return False
|
||||
|
||||
logger.info(f"Estado del servicio {service_id} actualizado exitosamente a {estado_nombre}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error al actualizar estado del servicio {service_id} a {estado_nombre} - Operación {operation_name}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
async def _create_response(service_data: dict, additional_data: Optional[Dict[str, Any]] = None,
|
||||
success_message: str = "Operación completada exitosamente") -> Dict[str, Any]:
|
||||
"""
|
||||
Crea una respuesta estandarizada para los endpoints.
|
||||
|
||||
Args:
|
||||
service_data: Datos del servicio
|
||||
additional_data: Datos adicionales a incluir en la respuesta
|
||||
success_message: Mensaje de éxito personalizado
|
||||
|
||||
Returns:
|
||||
Dict con estructura de respuesta estandarizada
|
||||
"""
|
||||
response = {
|
||||
"success": True,
|
||||
"message": success_message,
|
||||
"data": {
|
||||
"organizacion": service_data['organizacion'],
|
||||
"servicio": service_data['id'],
|
||||
"estado": ESTADO_FINALIZADO,
|
||||
"pedimento_id": service_data['pedimento']['id']
|
||||
}
|
||||
}
|
||||
|
||||
if additional_data:
|
||||
response["data"].update(additional_data)
|
||||
|
||||
logger.info(f"Respuesta creada exitosamente para servicio {service_data['id']}")
|
||||
return response
|
||||
|
||||
async def _execute_service_safely(service_func, request_data: Dict[str, Any], service_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Ejecuta un servicio de manera segura capturando errores.
|
||||
|
||||
Args:
|
||||
service_func: Función del servicio a ejecutar
|
||||
request_data: Datos para la petición
|
||||
service_name: Nombre del servicio para logging
|
||||
|
||||
Returns:
|
||||
Dict con resultado de la ejecución
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Iniciando ejecución automática de {service_name}...")
|
||||
|
||||
# Crear el objeto request apropiado
|
||||
from schemas.serviceSchema import ServiceRemesaSchema
|
||||
request_obj = ServiceRemesaSchema(**request_data)
|
||||
|
||||
# Ejecutar el servicio
|
||||
result = await service_func(request_obj)
|
||||
|
||||
logger.info(f"Servicio {service_name} ejecutado exitosamente")
|
||||
return {
|
||||
"success": True,
|
||||
"service_name": service_name,
|
||||
"result": result.body.decode() if hasattr(result, 'body') else str(result),
|
||||
"status_code": result.status_code if hasattr(result, 'status_code') else 200
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error en ejecución automática de {service_name}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
return {
|
||||
"success": False,
|
||||
"service_name": service_name,
|
||||
"error": str(e),
|
||||
"status_code": 500
|
||||
}
|
||||
|
||||
async def _execute_service_with_retry(service_func, request_data: Dict[str, Any],
|
||||
service_name: str, max_retries: int = 2) -> Dict[str, Any]:
|
||||
"""
|
||||
Ejecuta un servicio con reintentos automáticos en caso de fallo.
|
||||
|
||||
Args:
|
||||
service_func: Función del servicio a ejecutar
|
||||
request_data: Datos para la petición
|
||||
service_name: Nombre del servicio para logging
|
||||
max_retries: Número máximo de reintentos
|
||||
|
||||
Returns:
|
||||
Dict con resultado de la ejecución
|
||||
"""
|
||||
last_error = None
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
if attempt > 0:
|
||||
wait_time = min(2 ** attempt, 30) # Backoff exponencial, máximo 30 segundos
|
||||
logger.info(f"Reintentando {service_name} en {wait_time} segundos (intento {attempt + 1}/{max_retries + 1})")
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
result = await _execute_service_safely(service_func, request_data, service_name)
|
||||
|
||||
if result["success"]:
|
||||
if attempt > 0:
|
||||
logger.info(f"✅ Servicio {service_name} exitoso en intento {attempt + 1}")
|
||||
return result
|
||||
else:
|
||||
last_error = result.get("error", "Error desconocido")
|
||||
|
||||
except Exception as e:
|
||||
last_error = str(e)
|
||||
logger.warning(f"Intento {attempt + 1} fallido para {service_name}: {e}")
|
||||
|
||||
# Si llegamos aquí, todos los intentos fallaron
|
||||
logger.error(f"❌ Servicio {service_name} falló después de {max_retries + 1} intentos. Último error: {last_error}")
|
||||
return {
|
||||
"success": False,
|
||||
"service_name": service_name,
|
||||
"error": f"Falló después de {max_retries + 1} intentos. Último error: {last_error}",
|
||||
"status_code": 500,
|
||||
"retries_attempted": max_retries + 1
|
||||
}
|
||||
|
||||
async def _wait_for_service_creation(pedimento_id: str, service_type: int,
|
||||
timeout: int = 60, check_interval: int = 2) -> bool:
|
||||
"""
|
||||
Espera a que un servicio sea creado antes de intentar ejecutarlo.
|
||||
|
||||
Args:
|
||||
pedimento_id: ID del pedimento
|
||||
service_type: Tipo de servicio a esperar
|
||||
timeout: Tiempo máximo de espera en segundos
|
||||
check_interval: Intervalo entre verificaciones en segundos
|
||||
|
||||
Returns:
|
||||
bool: True si el servicio fue encontrado, False si se agotó el timeout
|
||||
"""
|
||||
start_time = asyncio.get_event_loop().time()
|
||||
|
||||
logger.info(f"Esperando creación de servicio tipo {service_type} para pedimento {pedimento_id} (timeout: {timeout}s)")
|
||||
|
||||
attempt = 0
|
||||
while (asyncio.get_event_loop().time() - start_time) < timeout:
|
||||
try:
|
||||
attempt += 1
|
||||
services = await rest_controller.get_pedimento_services(pedimento_id, service_type=service_type)
|
||||
|
||||
if services and len(services) > 0:
|
||||
logger.info(f"✅ Servicio tipo {service_type} encontrado para pedimento {pedimento_id} (intento {attempt})")
|
||||
return True
|
||||
else:
|
||||
if attempt % 10 == 0: # Log cada 20 segundos aprox
|
||||
logger.info(f"⏳ Servicio tipo {service_type} aún no encontrado para pedimento {pedimento_id} (intento {attempt}/{timeout//check_interval})")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error verificando servicio tipo {service_type}: {e}")
|
||||
|
||||
await asyncio.sleep(check_interval)
|
||||
|
||||
logger.error(f"❌ Timeout esperando servicio tipo {service_type} para pedimento {pedimento_id} después de {timeout}s")
|
||||
return False
|
||||
|
||||
async def _execute_follow_up_services(pedimento_id: str, organizacion_id: str,
|
||||
has_remesas: bool = False, has_partidas: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Ejecuta automáticamente los servicios de seguimiento después del pedimento completo.
|
||||
|
||||
Args:
|
||||
pedimento_id: ID del pedimento
|
||||
organizacion_id: ID de la organización
|
||||
has_remesas: Si el pedimento tiene remesas
|
||||
has_partidas: Si el pedimento tiene partidas
|
||||
|
||||
Returns:
|
||||
Dict con resultados de la ejecución
|
||||
"""
|
||||
logger.info(f"Iniciando ejecución automática de servicios para pedimento {pedimento_id}")
|
||||
|
||||
request_data = {
|
||||
"pedimento": pedimento_id,
|
||||
"organizacion": organizacion_id
|
||||
}
|
||||
|
||||
# Lista de servicios a ejecutar con sus tipos correspondientes
|
||||
services_to_execute = []
|
||||
|
||||
# Agregar partidas si el pedimento las tiene
|
||||
if has_partidas:
|
||||
services_to_execute.append(("partidas", get_partidas, 4))
|
||||
|
||||
# Agregar remesas si el pedimento las tiene
|
||||
if has_remesas:
|
||||
services_to_execute.append(("remesas", get_remesas, 5))
|
||||
|
||||
# Siempre agregar acuses (si existen documentos digitalizados)
|
||||
services_to_execute.append(("acuse", get_acuse, 6))
|
||||
|
||||
# Resultados de ejecución
|
||||
execution_results = {
|
||||
"total_services": len(services_to_execute),
|
||||
"successful_services": 0,
|
||||
"failed_services": 0,
|
||||
"results": []
|
||||
}
|
||||
|
||||
# Esperar un poco antes de iniciar para que se completen los servicios creados
|
||||
logger.info("Esperando a que se completen las creaciones de servicios...")
|
||||
await asyncio.sleep(10) # Aumentado de 5 a 10 segundos
|
||||
|
||||
# Ejecutar servicios secuencialmente para evitar sobrecarga
|
||||
for service_name, service_func, service_type in services_to_execute:
|
||||
try:
|
||||
logger.info(f"🔄 Iniciando procesamiento de {service_name}...")
|
||||
|
||||
# Verificar que el servicio exista antes de ejecutar
|
||||
service_exists = await _wait_for_service_creation(pedimento_id, service_type, timeout=60)
|
||||
|
||||
if not service_exists:
|
||||
execution_results["failed_services"] += 1
|
||||
execution_results["results"].append({
|
||||
"success": False,
|
||||
"service_name": service_name,
|
||||
"error": f"Servicio tipo {service_type} no encontrado después de esperar",
|
||||
"status_code": 404
|
||||
})
|
||||
logger.warning(f"⚠️ Servicio {service_name} no encontrado, saltando...")
|
||||
continue
|
||||
|
||||
# Ejecutar servicio con reintentos
|
||||
result = await _execute_service_with_retry(service_func, request_data, service_name, max_retries=2)
|
||||
execution_results["results"].append(result)
|
||||
|
||||
if result["success"]:
|
||||
execution_results["successful_services"] += 1
|
||||
logger.info(f"✅ Servicio {service_name} completado exitosamente")
|
||||
else:
|
||||
execution_results["failed_services"] += 1
|
||||
logger.warning(f"❌ Servicio {service_name} falló: {result.get('error', 'Error desconocido')}")
|
||||
|
||||
# Esperar entre servicios para no sobrecargar
|
||||
await asyncio.sleep(3)
|
||||
|
||||
except Exception as e:
|
||||
execution_results["failed_services"] += 1
|
||||
execution_results["results"].append({
|
||||
"success": False,
|
||||
"service_name": service_name,
|
||||
"error": f"Error crítico: {str(e)}",
|
||||
"status_code": 500
|
||||
})
|
||||
logger.error(f"💥 Error crítico en servicio {service_name}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
# Log de resumen
|
||||
success_rate = (execution_results["successful_services"] / execution_results["total_services"]) * 100 if execution_results["total_services"] > 0 else 0
|
||||
|
||||
if execution_results["successful_services"] == execution_results["total_services"]:
|
||||
logger.info(f"🎉 Ejecución automática completada exitosamente - {execution_results['successful_services']}/{execution_results['total_services']} (100%)")
|
||||
else:
|
||||
logger.warning(f"⚠️ Ejecución automática completada con errores - Éxito: {execution_results['successful_services']}/{execution_results['total_services']} ({success_rate:.1f}%)")
|
||||
|
||||
return execution_results
|
||||
|
||||
async def _schedule_follow_up_services(pedimento_id: str, organizacion_id: str,
|
||||
xml_content: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Programa la ejecución de servicios de seguimiento en segundo plano.
|
||||
|
||||
Args:
|
||||
pedimento_id: ID del pedimento
|
||||
organizacion_id: ID de la organización
|
||||
xml_content: Contenido XML del pedimento para determinar qué servicios ejecutar
|
||||
"""
|
||||
try:
|
||||
# Determinar qué servicios ejecutar basado en el contenido del pedimento
|
||||
has_remesas = bool(xml_content.get('remesas', 0))
|
||||
has_partidas = xml_content.get('numero_partidas', 0) > 0
|
||||
|
||||
logger.info(f"Programando servicios automáticos - Remesas: {has_remesas}, Partidas: {has_partidas}")
|
||||
|
||||
# Crear tarea en segundo plano
|
||||
task = asyncio.create_task(
|
||||
_execute_follow_up_services(
|
||||
pedimento_id=pedimento_id,
|
||||
organizacion_id=organizacion_id,
|
||||
has_remesas=has_remesas,
|
||||
has_partidas=has_partidas
|
||||
)
|
||||
)
|
||||
|
||||
# Agregar callback para logging cuando termine
|
||||
def log_completion(task):
|
||||
try:
|
||||
result = task.result()
|
||||
logger.info(f"Servicios automáticos completados para pedimento {pedimento_id}: {result['successful_services']}/{result['total_services']} exitosos")
|
||||
except Exception as e:
|
||||
logger.error(f"Error en servicios automáticos para pedimento {pedimento_id}: {e}")
|
||||
|
||||
task.add_done_callback(log_completion)
|
||||
|
||||
logger.info(f"Servicios automáticos programados exitosamente para pedimento {pedimento_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error al programar servicios automáticos: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
|
||||
logger.error(f"Error inesperado en {operation_name}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
# Actualizar estado a error si tenemos service_data
|
||||
if service_data:
|
||||
try:
|
||||
await _update_service_status(service_data['id'], ESTADO_ERROR, service_data, operation_name)
|
||||
except Exception as update_error:
|
||||
logger.error(f"Error al actualizar estado del servicio tras fallo: {update_error}")
|
||||
|
||||
raise HTTPException(status_code=500, detail=f"Error interno en {operation_name}: {str(e)}")
|
||||
|
||||
def _log_operation_summary(operation_name: str, service_id: int, success: bool,
|
||||
additional_info: Optional[str] = None) -> None:
|
||||
"""
|
||||
Registra un resumen de la operación realizada.
|
||||
|
||||
Args:
|
||||
operation_name: Nombre de la operación
|
||||
service_id: ID del servicio procesado
|
||||
success: Si la operación fue exitosa
|
||||
additional_info: Información adicional opcional
|
||||
"""
|
||||
status = "EXITOSO" if success else "FALLIDO"
|
||||
message = f"RESUMEN {operation_name.upper()}: {status} - Servicio ID: {service_id}"
|
||||
|
||||
if additional_info:
|
||||
message += f" - {additional_info}"
|
||||
|
||||
if success:
|
||||
logger.info(message)
|
||||
else:
|
||||
logger.error(message)
|
||||
|
||||
async def _validate_soap_controller() -> None:
|
||||
"""
|
||||
Valida que el controlador SOAP esté disponible.
|
||||
|
||||
Raises:
|
||||
HTTPException: Si el controlador SOAP no está disponible
|
||||
"""
|
||||
if not soap_controller:
|
||||
logger.error("Controlador SOAP no disponible")
|
||||
raise HTTPException(status_code=500, detail="Servicio SOAP no disponible")
|
||||
Reference in New Issue
Block a user