se estan creando el registro de las tareas sin problemas

This commit is contained in:
2025-10-08 21:15:03 -06:00
parent 48db0d72d8
commit 770e0a4d13
15 changed files with 858 additions and 258 deletions

View File

@@ -2,9 +2,8 @@ from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import JSONResponse
from typing import Dict, Any, List, Optional
from api.api_v2.modules.authentication.services import get_current_user
from api.api_v2.modules.tasks.services import register_task
from .schemas import AcuseSchema, AcuseMasivoSchema
from .tasks import process_acuse_request
@@ -19,7 +18,17 @@ async def obtener_acuse(acuse_request: AcuseSchema):
acuse_dict = acuse_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona
task = process_acuse_request.delay(acuse_dict)
# Puedes devolver el ID de la tarea para consultar el estado después
# Registra la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando acuse para pedimento {acuse_dict.get('pedimento', {}).get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=acuse_dict.get('pedimento', {}).get('id'),
organizacion_id=acuse_dict.get('pedimento', {}).get('organizacion'),
servicio=6 # 6 corresponde a "Acuse"
)
return {"task_id": task.id, "status": "submitted"}
@@ -39,5 +48,15 @@ async def obtener_acuses(acuse_request: AcuseMasivoSchema):
}
task = process_acuse_request.delay(acuse_dict)
task_ids.append(task.id)
# Registra cada tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando acuse masivo para pedimento {acuse_request_dict.get('pedimento', {}).get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=acuse_request_dict.get('pedimento', {}).get('id'),
organizacion_id=acuse_request_dict.get('pedimento', {}).get('organizacion'),
servicio=6 # 6 corresponde a "Acuse"
)
return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}

View File

@@ -1,10 +1,16 @@
from http.client import HTTPException
import base64
import re
import logging
from typing import Any, Dict, Optional
import xml.etree.ElementTree as ET
from fastapi import HTTPException
from .controllers import acuse_vu_controller, acuse_rest_controller
from utils.helpers import soap_error
from ..common import create_service_response, create_error_response
import xml.etree.ElementTree as ET
# Logger configurado para el módulo
logger = logging.getLogger("app.api")
soap_headers = {
'Content-Type': 'text/xml; charset=utf-8',
@@ -23,29 +29,68 @@ async def obtener_acuse(**kwargs):
)
if response is None:
raise Exception("No se obtuvo respuesta del servicio SOAP.")
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"]
)
)
if response.status_code != 200:
raise Exception(f"Error en la solicitud SOAP: {response.status}")
logger.error(f"Error en la solicitud SOAP: {response.status_code}")
raise HTTPException(
status_code=response.status_code,
detail=create_error_response(
message=f"Error en la solicitud SOAP: {response.status_code}",
data={"soap_response": response.text[:500]}
)
)
if (response) and (not soap_error(response)):
acuse_base64 = _extract_acuse_data(response.text)
if soap_error(response):
logger.error("Error SOAP detectado en la respuesta")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error en la respuesta del servicio SOAP",
data={"soap_response": response.text[:500]}
)
)
acuse_base64 = _extract_acuse_data(response.text)
if acuse_base64 is None:
raise Exception("No se pudo extraer el acuse del documento de la respuesta SOAP.")
pdf_bytes = _decode_acuse_base64_content(acuse_base64)
logger.error("No se pudo extraer el acuse del documento")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="No se pudo extraer el acuse del documento",
errors=["El formato de la respuesta SOAP no es el esperado"]
)
)
pdf_bytes = _decode_acuse_base64_content(acuse_base64)
if not pdf_bytes:
raise HTTPException(status_code=500, detail="No se pudo decodificar el documento del acuse")
logger.error("No se pudo decodificar el contenido Base64 del acuse")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="No se pudo decodificar el documento del acuse",
errors=["El contenido Base64 no es válido"]
)
)
# Validar que el PDF sea válido
if not pdf_bytes.startswith(b'%PDF'):
import logging
logger = logging.getLogger("app.api")
logger.warning("El contenido decodificado no parece ser un PDF válido")
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={"content_start": str(pdf_bytes[:20])}
)
)
# Mejorar el nombre del archivo usando todos los datos relevantes
pedimento = kwargs.get('pedimento', {})
@@ -65,21 +110,49 @@ async def obtener_acuse(**kwargs):
)
if rest_response is None:
raise Exception("No se pudo enviar el acuse a la API interna.")
logger.error("Error al enviar el acuse a la API interna")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al guardar el acuse en el sistema",
errors=["No se pudo enviar el documento a la API interna"],
metadata={"file_name": _file_name}
)
)
if rest_response.get("id") is None:
raise Exception("La respuesta de la API interna no contiene un ID válido.")
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}
)
)
acuse_update_response = await change_edocument_status(
edoc=kwargs.get('edoc'),
status=True,
pedimento=pedimento
)
return {
"document_response": rest_response,
"file_name": _file_name,
"pedimento": pedimento_num,
"acuse_update_response": acuse_update_response
}
return create_service_response(
message="Acuse procesado exitosamente",
data={
"document_response": rest_response,
"file_name": _file_name,
"pedimento": pedimento_num,
"acuse_update_response": acuse_update_response
},
metadata={
"document_type": 4,
"pedimento_app": pedimento.get('pedimento_app'),
"organizacion": organizacion,
"edoc_number": kwargs.get('edoc', {}).get('numero_edocument'),
"content_type": "application/pdf"
}
)
async def change_edocument_status(edoc: dict, status: bool, pedimento: dict):
data = {

View File

@@ -0,0 +1,4 @@
"""Módulo común para funcionalidades compartidas entre servicios."""
from .response import create_service_response, create_error_response
__all__ = ['create_service_response', 'create_error_response']

View File

@@ -0,0 +1,67 @@
"""Utilidades para estandarizar respuestas en los servicios."""
from typing import Any, Dict, List, Optional
def create_service_response(
success: bool = True,
message: str = "Operación completada exitosamente",
data: Optional[Dict[str, Any]] = None,
errors: Optional[List[str]] = None,
warnings: Optional[List[str]] = None,
metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Crea una respuesta estandarizada para los servicios.
Args:
success: Indica si la operación fue exitosa
message: Mensaje principal de la respuesta
data: Datos principales de la respuesta
errors: Lista de errores si los hay
warnings: Lista de advertencias si las hay
metadata: Metadatos adicionales de la operación
Returns:
Dict con estructura estandarizada de respuesta
"""
response = {
"success": success,
"message": message,
"data": data or {},
}
if errors:
response["errors"] = errors
if warnings:
response["warnings"] = warnings
if metadata:
response["metadata"] = metadata
return response
def create_error_response(
message: str,
errors: Optional[List[str]] = None,
data: Optional[Dict[str, Any]] = None,
metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Crea una respuesta de error estandarizada.
Args:
message: Mensaje principal de error
errors: Lista detallada de errores
data: Datos adicionales del error
metadata: Metadatos del error
Returns:
Dict con estructura estandarizada de error
"""
return create_service_response(
success=False,
message=message,
data=data,
errors=errors,
metadata=metadata
)

View File

@@ -1,58 +1,106 @@
from fastapi import APIRouter, HTTPException
from .schemas import CoveListSchema, CoveRequestSchema
from typing import List
from typing import List, Dict, Any
from uuid import UUID
from .tasks import process_cove_request, process_acuse_cove_request
from ..tasks.services import register_task
router = APIRouter()
# Aquí puedes definir tus endpoints relacionados con COVES usando el esquema CoveBaseSchema
@router.post("/services/cove/", response_model=dict)
@router.post("/services/cove/", response_model=Dict[str, Any])
async def get_cove(cove: CoveRequestSchema):
# Lógica para obtener un COVE
task = process_cove_request.delay(cove.model_dump())
"""Endpoint para obtener un COVE específico."""
cove_dict = cove.model_dump()
task = process_cove_request.delay(cove_dict)
# Registrar la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando COVE para pedimento {cove_dict.get('pedimento', {}).get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=cove_dict.get('pedimento', {}).get('id'),
organizacion_id=cove_dict.get('pedimento', {}).get('organizacion'),
servicio=8 # 8 corresponde a "Cove"
)
return {"task_id": task.id, "status": "submitted"}
@router.post("/services/all/coves", response_model=dict)
@router.post("/services/all/coves", response_model=Dict[str, Any])
async def get_coves(coves_request: CoveListSchema):
# Lógica para obtener un COVE
"""Endpoint para obtener múltiples COVEs asociados a un pedimento."""
task_ids = []
coves_dict = coves_request.model_dump()
pedimento = coves_dict.get('pedimento', {})
for cove in coves_dict.get('coves', []):
cove_dict = {
"cove": cove,
"pedimento": coves_dict.get('pedimento'),
"pedimento": pedimento,
"credencial": coves_dict.get('credencial')
}
task = process_cove_request.delay(cove_dict)
task_ids.append(task.id)
# Registrar cada tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando COVE masivo para pedimento {pedimento.get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=pedimento.get('id'),
organizacion_id=pedimento.get('organizacion'),
servicio=8 # 8 corresponde a "Cove"
)
return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}
return {"task_id": task.id, "coves_tasks_ids": task_ids, "status": "submitted"}
@router.post("/services/acuse/cove/", response_model=dict)
@router.post("/services/acuse/cove/", response_model=Dict[str, Any])
async def get_acuse_cove(cove: CoveRequestSchema):
# Lógica para obtener un COVE
task = process_acuse_cove_request.delay(cove.model_dump())
"""Endpoint para obtener el acuse de un COVE específico."""
cove_dict = cove.model_dump()
task = process_acuse_cove_request.delay(cove_dict)
# Registrar la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando acuse de COVE para pedimento {cove_dict.get('pedimento', {}).get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=cove_dict.get('pedimento', {}).get('id'),
organizacion_id=cove_dict.get('pedimento', {}).get('organizacion'),
servicio=9 # 9 corresponde a "Acuse Cove"
)
return {"task_id": task.id, "status": "submitted"}
@router.post("/services/all/acuse/cove/")
@router.post("/services/all/acuse/cove/", response_model=Dict[str, Any])
async def get_acuses_cove(coves_request: CoveListSchema):
# Lógica para obtener un COVE
"""Endpoint para obtener los acuses de múltiples COVEs asociados a un pedimento."""
task_ids = []
coves_dict = coves_request.model_dump()
pedimento = coves_dict.get('pedimento', {})
for cove in coves_dict.get('coves', []):
acuse_dict = {
"cove": cove,
"pedimento": coves_dict.get('pedimento'),
"pedimento": pedimento,
"credencial": coves_dict.get('credencial')
}
task = process_acuse_cove_request.delay(acuse_dict)
task_ids.append(task.id)
# Registrar cada tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando acuse masivo de COVE para pedimento {pedimento.get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=pedimento.get('id'),
organizacion_id=pedimento.get('organizacion'),
servicio=9 # 9 corresponde a "Acuse Cove"
)
return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}

View File

@@ -2,17 +2,20 @@ import base64
import os
import logging
import re
import tempfile
from typing import Any, Dict, List, Optional
import xml.etree.ElementTree as ET
from fastapi import HTTPException
from controllers.RESTController import rest_controller
from controllers.SOAPController import soap_controller
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_der_private_key
import tempfile
from utils.helpers import soap_error
from .controllers import coves_vu_controller, coves_rest_controller
from ..common import create_service_response, create_error_response
# Logger para el módulo
logger = logging.getLogger(__name__)
@@ -42,8 +45,26 @@ async def consume_ws_get_cove(**kwargs):
cove = kwargs['cove'].get('cove', None)
if not credenciales or not username or not cove:
raise Exception(
"Credenciales o COVE no proporcionados correctamente")
missing = []
if not credenciales:
missing.append("credenciales")
if not username:
missing.append("nombre de usuario")
if not cove:
missing.append("número de COVE")
raise HTTPException(
status_code=400,
detail=create_error_response(
message="Datos incompletos para procesar COVE",
errors=[f"Falta: {', '.join(missing)}"],
metadata={"provided_data": {
"has_credentials": bool(credenciales),
"has_username": bool(username),
"has_cove": bool(cove)
}}
)
)
logger.info(f"Procesando COVE: {cove} para usuario: {username}")
@@ -120,18 +141,38 @@ async def consume_ws_get_cove(**kwargs):
logger.info(f"COVE {cove} procesado exitosamente")
# Asegurar que la respuesta sea serializable
result = {
"documento": document_response if document_response else None,
"cove_update_response": cove_status_response if cove_status_response else None
}
return result
return create_service_response(
message=f"COVE {cove} procesado exitosamente",
data={
"documento": document_response if document_response else None,
"cove_update_response": cove_status_response if cove_status_response else None
},
metadata={
"cove_number": cove,
"file_name": _file_name,
"document_type": 8,
"pedimento_app": pedimento_app,
"username": username,
"organizacion": kwargs.get('pedimento', {}).get('organizacion')
}
)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error procesando COVE: {str(e)}", exc_info=True)
# Asegurar que no se retornen datos binarios en el error
raise Exception(f"Error interno al procesar COVE: {str(e)}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error interno al procesar COVE",
errors=[str(e)],
metadata={
"cove_number": cove,
"username": username,
"pedimento_app": pedimento_app
}
)
)
async def consume_ws_get_acuse_cove(**kwargs):
@@ -155,20 +196,57 @@ async def consume_ws_get_acuse_cove(**kwargs):
)
if response is None:
raise Exception("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 SOAP"],
metadata={
"cove_number": kwargs['cove'].get('cove'),
"pedimento_app": kwargs.get('pedimento', {}).get('pedimento_app')
}
)
)
if response.status_code != 200:
raise Exception(f"Error en la solicitud SOAP: {response.status}")
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,
"cove_number": kwargs['cove'].get('cove')
}
)
)
if soap_error(response):
rest_response = await coves_rest_controller.post_document(
soap_response=response,
organizacion=kwargs.get('pedimento').get('organizacion'),
pedimento=kwargs.get('pedimento').get('id'),
file_name=f"vu_AC_COVE_{kwargs.get('pedimento', {}).get('pedimento_app', 'N/A')}_{kwargs['cove'].get('cove', 'N/A')}_ERROR.xml",
document_type=10,
error_file_name = f"vu_AC_COVE_{kwargs.get('pedimento', {}).get('pedimento_app', 'N/A')}_{kwargs['cove'].get('cove', 'N/A')}_ERROR.xml"
try:
rest_response = await coves_rest_controller.post_document(
soap_response=response,
organizacion=kwargs.get('pedimento').get('organizacion'),
pedimento=kwargs.get('pedimento').get('id'),
file_name=error_file_name,
document_type=10,
)
except Exception as e:
logger.error(f"Error al guardar respuesta SOAP errónea: {e}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error en la respuesta del servicio SOAP",
errors=["Se detectó un error en la respuesta SOAP"],
data={"error_file": error_file_name} if 'rest_response' in locals() else None,
metadata={
"cove_number": kwargs['cove'].get('cove'),
"document_type": 10
}
)
)
raise Exception("Error detectado en la respuesta SOAP.")
if (response) and (not soap_error(response)):
logger.debug(f"Respuesta SOAP recibida, extrayendo acuse...")
acuse_base64 = _extract_acuse_data(response.text)
@@ -224,12 +302,22 @@ async def consume_ws_get_acuse_cove(**kwargs):
pedimento=kwargs.get('pedimento')
)
return {
"document_response": rest_response,
"file_name": _file_name,
"pedimento": pedimento_num,
"acuse_update": acuse_status
}
return create_service_response(
message="Acuse de COVE procesado exitosamente",
data={
"document_response": rest_response,
"file_name": _file_name,
"pedimento": pedimento_num,
"acuse_update": acuse_status
},
metadata={
"document_type": 7,
"pedimento_app": pedimento.get('pedimento_app'),
"organizacion": organizacion,
"cove_number": kwargs['cove'].get('cove'),
"content_type": "application/pdf"
}
)
def _decode_acuse_base64_content(base64_content): # Testeado
@@ -462,15 +550,26 @@ async def fetch_sign_and_cer(cadena_original: str, username: str, credenciales:
return firma, certificado, tmp_key_path
except Exception as e:
logger.error(
f"Error obteniendo certificado/llave o generando firma: {e}")
logger.error(f"Error obteniendo certificado/llave o generando firma: {e}")
# Limpiar archivo temporal si existe
if 'tmp_key_path' in locals() and os.path.exists(tmp_key_path):
try:
os.remove(tmp_key_path)
except:
pass
raise Exception(f"Error en fetch_sign_and_cer: {str(e)}")
except Exception as cleanup_error:
logger.warning(f"Error al limpiar archivo temporal: {cleanup_error}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al procesar certificado y firma",
errors=[str(e)],
metadata={
"username": username,
"has_key": bool(key_bytes) if 'key_bytes' in locals() else False,
"has_cert": bool(cer) if 'cer' in locals() else False
}
)
)
def sign_chain_original(key_path: str, password: str, cadena_original: str) -> str:

View File

@@ -3,6 +3,7 @@ from typing import Dict, Any, Optional
from .schemas import EdocumentsSchema, EdocumentsMasivoSchema
from .tasks import process_edoc_download_request
from api.api_v2.modules.authentication.services import get_current_user
from api.api_v2.modules.tasks.services import register_task
router = APIRouter()
@@ -17,6 +18,15 @@ async def download_edoc(edoc_request: EdocumentsSchema):
edoc_dict = edoc_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona
task = process_edoc_download_request.delay(edoc_dict)
# Registrar la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando descarga de E-Document {edoc_dict.get('edoc', {}).get('numero_edocument', 'N/A')}",
status="submitted",
pedimento_id=edoc_dict.get('pedimento', {}).get('id'),
organizacion_id=edoc_dict.get('pedimento', {}).get('organizacion'),
servicio=7 # 7 corresponde a "EDocument"
)
# Devuelve el ID de la tarea
return {"task_id": task.id, "status": "submitted"}
@@ -37,5 +47,14 @@ async def download_edocs_masivo(edoc_request: EdocumentsMasivoSchema):
}
task = process_edoc_download_request.delay(edoc_dict)
task_ids.append(task.id)
# Registrar cada tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando descarga masiva de E-Document {edoc.get('numero_edocument', 'N/A')}",
status="submitted",
pedimento_id=edoc_dict.get('pedimento', {}).get('id'),
organizacion_id=edoc_dict.get('pedimento', {}).get('organizacion'),
servicio=7 # 7 corresponde a "EDocument"
)
return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}

View File

@@ -1,68 +1,22 @@
from http.client import HTTPException
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 _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('<?xml')
if xml_start == -1:
return None
xml_content = soap_response_text[xml_start:]
boundary_end = xml_content.find('--uuid:')
if boundary_end != -1:
xml_content = xml_content[:boundary_end]
root = ET.fromstring(xml_content.strip())
namespaces = {
'S': 'http://schemas.xmlsoap.org/soap/envelope/',
'ns3': 'http://www.ventanillaunica.gob.mx/ws/consulta/edocs/'
}
edoc_elemento = root.find('.//ns3:responseConsultaEdocumento/documentoBase64', namespaces)
if edoc_elemento is None:
edoc_elemento = root.find('.//documentoBase64')
return edoc_elemento.text.strip() if edoc_elemento is not None and edoc_elemento.text else None
except ET.ParseError as e:
logger.error(f"Error parseando la respuesta SOAP para Edoc: {e}")
return None
except Exception as e:
logger.error(f"Error general en extracción de datos Edoc: {e}")
return None
def _get_file_name(**kwargs) -> str:
pedimento = kwargs.get('pedimento', {})
@@ -89,25 +43,102 @@ async def obtener_edoc(**kwargs):
data=soap_xml,
headers=soap_headers
)
# Validar respuesta del servicio SOAP
if response is None:
raise Exception("No se obtuvo respuesta del servicio SOAP.")
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:
raise Exception(f"Error en la solicitud SOAP: {response.status_code}")
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):
raise Exception("Respuesta SOAP contiene error de VUCEM.")
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}
)
)
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.")
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:
raise HTTPException(status_code=500, detail="No se pudo decodificar el documento")
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.warning("El contenido decodificado no parece ser un PDF válido")
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', '')
@@ -116,12 +147,8 @@ async def obtener_edoc(**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}")
# 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,
@@ -132,26 +159,61 @@ async def obtener_edoc(**kwargs):
)
if rest_response is None:
raise Exception("No se pudo enviar el documento a la API interna.")
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:
raise Exception("La respuesta de la API interna no contiene un ID válido.")
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")
edoc_status_response = await change_edocument_status(
edoc=doc,
status=True,
pedimento=pedimento
)
print(edoc_status_response)
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
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
}
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):
@@ -168,8 +230,6 @@ async def change_edocument_status(edoc: dict, status: bool, pedimento: dict):
return response
def extract_pdf_bytes_from_xml_content(xml_content: str):
"""
Extrae el PDF y metadatos desde un string XML.

View File

@@ -5,6 +5,7 @@ from typing import Dict, Any, List, Optional
from api.api_v2.modules.authentication.services import get_current_user
from api.api_v2.modules.tasks.services import register_task
from .schemas import PartidaRequestSchema, PartidaListSchema
from .tasks import process_partida_request
@@ -19,6 +20,15 @@ async def obtener_partida(partida_request: PartidaRequestSchema):
acuse_dict = partida_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona
task = process_partida_request.delay(acuse_dict)
# Registrar la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando partida {acuse_dict.get('partida', {}).get('numero', 'N/A')} para pedimento {acuse_dict.get('pedimento', {}).get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=acuse_dict.get('pedimento', {}).get('id'),
organizacion_id=acuse_dict.get('pedimento', {}).get('organizacion'),
servicio=4 # 4 corresponde a "Pedimento Partidas"
)
# Puedes devolver el ID de la tarea para consultar el estado después
return {"task_id": task.id, "status": "submitted"}
@@ -39,5 +49,14 @@ async def obtener_partidas(partidas_request: PartidaListSchema):
}
task = process_partida_request.delay(partida_dict)
task_ids.append(task.id)
# Registrar cada tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando partida masiva {partida.get('numero', 'N/A')} para pedimento {partida_request_dict.get('pedimento', {}).get('pedimento_app', 'N/A')}",
status="submitted",
pedimento_id=partida_request_dict.get('pedimento', {}).get('id'),
organizacion_id=partida_request_dict.get('pedimento', {}).get('organizacion'),
servicio=4 # 4 corresponde a "Pedimento Partidas"
)
return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}

View File

@@ -1,18 +1,8 @@
import base64
import os
import logging
import re
import xml.etree.ElementTree as ET
from fastapi import HTTPException
from controllers.RESTController import rest_controller
from controllers.SOAPController import soap_controller
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_der_private_key
import tempfile
from utils.helpers import soap_error
from .controllers import partida_rest_controller, partida_vu_controller
from ..common import create_service_response, create_error_response
# Logger para el módulo
logger = logging.getLogger(__name__)
@@ -30,99 +20,168 @@ async def consume_ws_get_partida(**kwargs):
Raises:
Exception: Si hay errores en el procesamiento
"""
try:
logger.info("Iniciando procesamiento de partidas")
credenciales = kwargs.get('credencial')
username = credenciales.get('user')
pedimento_app = kwargs.get('pedimento', {}).get('pedimento_app', 'N/A')
partida = kwargs.get('partida', {})
logger.info("Iniciando procesamiento de partidas")
credenciales = kwargs.get('credencial')
username = credenciales.get('user')
pedimento_app = kwargs.get('pedimento', {}).get('pedimento_app', 'N/A')
partida = kwargs.get('partida', {})
if not credenciales or not username or not partida:
raise Exception("Credenciales o Partida no proporcionados correctamente")
if not credenciales or not username or not partida:
logger.error("Credenciales o Partida faltantes")
raise HTTPException(
status_code=400,
detail=create_error_response(
message="Datos incompletos para procesar la partida",
errors=["Credenciales o datos de partida no proporcionados correctamente"],
metadata={
"has_credentials": bool(credenciales),
"has_username": bool(username),
"has_partida": bool(partida)
}
)
)
logger.info(f"Procesando Partida: {partida} para usuario: {username}")
logger.info(f"Procesando Partida {partida.get('numero', 'N/A')} para usuario {username}")
# Generar template SOAP
# Generar template SOAP
soap_xml = partida_vu_controller.generate_partidas_template(
username=username,
password=credenciales.get('password'),
aduana=kwargs.get('pedimento', {}).get('aduana', 'N/A'),
patente=kwargs.get('pedimento', {}).get('patente', 'N/A'),
pedimento=kwargs.get('pedimento', {}).get('pedimento', 'N/A'),
numero_operacion=kwargs.get('pedimento', {}).get('numero_operacion', ''),
partida=partida.get('numero', '')
)
soap_xml = partida_vu_controller.generate_partidas_template(
username=username,
password=credenciales.get('password'),
aduana=kwargs.get('pedimento', {}).get('aduana', 'N/A'),
patente=kwargs.get('pedimento', {}).get('patente', 'N/A'),
pedimento=kwargs.get('pedimento', {}).get('pedimento', 'N/A'),
numero_operacion=kwargs.get('pedimento', {}).get('numero_operacion', ''),
partida=partida.get('numero', '')
)
soap_headers = {
'Content-Type': 'text/xml; charset=utf-8'
}
soap_headers = {
'Content-Type': 'text/xml; charset=utf-8'
}
logger.info("Enviando petición SOAP a VUCEM")
soap_response = await partida_vu_controller.make_request_async(
"/ventanilla-ws-pedimentos/ConsultarPartidaService",
data=soap_xml,
headers=soap_headers
)
logger.info("Enviando petición SOAP a VUCEM")
soap_response = await partida_vu_controller.make_request_async(
"/ventanilla-ws-pedimentos/ConsultarPartidaService",
data=soap_xml,
headers=soap_headers
)
if not soap_response:
logger.error("No se recibió 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={
"partida_numero": partida.get('numero'),
"username": username
}
)
)
if not soap_response:
raise Exception("No se recibió respuesta del servicio SOAP")
if soap_error(soap_response):
if soap_error(soap_response):
error_file_name = f"vu_PT_{pedimento_app}_{partida.get('numero', '')}_ERROR.xml"
try:
document_response = await partida_rest_controller.post_document(
soap_response=soap_response,
organizacion=kwargs.get('pedimento').get('organizacion'),
pedimento=kwargs.get('pedimento').get('id'),
file_name=f"vu_PT_{pedimento_app}_{partida.get('numero', '')}_ERROR.xml",
file_name=error_file_name,
document_type=10,
)
raise Exception("Error en la respuesta del servicio SOAP")
logger.info("Respuesta SOAP exitosa, enviando documento")
# Enviar documento
_file_name = f"vu_PT_{pedimento_app}_{partida.get('numero', '')}.xml"
try:
document_response = await partida_rest_controller.post_document(
soap_response=soap_response,
organizacion=kwargs.get('pedimento').get('organizacion'),
pedimento=kwargs.get('pedimento').get('id'),
file_name=_file_name,
document_type=1,
)
except Exception as e:
logger.error(f"Error detectado en la respuesta SOAP: {str(e)}")
raise Exception(f"Error en la respuesta SOAP: {str(e)}")
logger.error(f"Error al guardar la respuesta de error: {e}")
# Continuamos con el error original
logger.error("Error en la respuesta del servicio SOAP")
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": soap_response.text[:500] if hasattr(soap_response, 'text') else None},
metadata={
"partida_numero": partida.get('numero'),
"error_file": error_file_name
}
)
)
logger.info("Respuesta SOAP exitosa, enviando documento")
# Enviar documento
_file_name = f"vu_PT_{pedimento_app}_{partida.get('numero', '')}.xml"
try:
document_response = await partida_rest_controller.post_document(
soap_response=soap_response,
organizacion=kwargs.get('pedimento').get('organizacion'),
pedimento=kwargs.get('pedimento').get('id'),
file_name=_file_name,
document_type=1,
)
except Exception as e:
logger.error(f"Error al enviar documento: {e}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al guardar el documento",
errors=[str(e)],
metadata={
"file_name": _file_name,
"partida_numero": partida.get('numero')
}
)
)
logger.info("Documento enviado, actualizando status de Partida")
# Actualizar status del partida
logger.info("Documento enviado, actualizando status de Partida")
# Actualizar status de la partida
try:
partida_status_response = await change_partida_status(
partida=kwargs.get('partida'),
status=True,
pedimento=kwargs.get('pedimento')
)
logger.info(f"Partida {partida.get('numero', '')} procesado exitosamente")
# Asegurar que la respuesta sea serializable
result = {
"documento": document_response if document_response else None,
"partida_update_response": partida_status_response if partida_status_response else None
}
return result
except Exception as e:
logger.error(f"Error procesando la partida: {str(e)}", exc_info=True)
# Asegurar que no se retornen datos binarios en el error
raise Exception(f"Error interno al procesar la partida: {str(e)}")
logger.warning(f"Error al actualizar estado de la partida: {e}")
# No fallamos aquí porque el documento ya se guardó exitosamente
partida_status_response = None
logger.info(f"Partida {partida.get('numero', '')} procesada exitosamente")
return create_service_response(
message=f"Partida {partida.get('numero', '')} procesada exitosamente",
data={
"documento": document_response,
"partida_update_response": partida_status_response,
"file_name": _file_name
},
metadata={
"document_type": 1,
"pedimento_app": pedimento_app,
"organizacion": kwargs.get('pedimento').get('organizacion'),
"content_type": "application/xml"
}
)
async def change_partida_status(partida: dict, status: bool, pedimento: dict):
"""
Actualiza el estado de una partida.
"""
if not partida or not pedimento:
logger.error("Datos insuficientes para actualizar estado de partida")
raise HTTPException(
status_code=400,
detail=create_error_response(
message="Datos incompletos para actualizar estado",
errors=["Faltan datos de partida o pedimento"],
metadata={
"has_partida": bool(partida),
"has_pedimento": bool(pedimento)
}
)
)
data = {
"id": partida.get("id"),
"numero_partida": partida.get("numero"),
@@ -130,7 +189,18 @@ async def change_partida_status(partida: dict, status: bool, pedimento: dict):
"pedimento": pedimento.get("id"),
"organizacion": pedimento.get("organizacion"),
}
print(data)
response = await partida_rest_controller.put_partida(partida_id=partida.get("id"), data=data)
if not response:
logger.error("Error al actualizar estado de la partida")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al actualizar estado de la partida",
errors=["No se recibió respuesta del servicio"],
metadata={"partida_id": partida.get("id")}
)
)
return response

View File

@@ -2,6 +2,7 @@ from fastapi import APIRouter, BackgroundTasks, status, HTTPException
from fastapi.responses import JSONResponse
from .schemas import PedimentoCompletoRequestSchema
from .tasks import process_pedimento_completo_request
from api.api_v2.modules.tasks.services import register_task
import logging
logger = logging.getLogger("app.api")
@@ -14,8 +15,16 @@ async def download_pedimento_completo(Pedimento: PedimentoCompletoRequestSchema)
"""
pedimento_dict = Pedimento.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona
task = process_pedimento_completo_request.delay(pedimento_dict)
# Registrar la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando descarga de pedimento completo {pedimento_dict.get('pedimento', 'N/A')}",
status="submitted",
pedimento_id=pedimento_dict.get('id'),
organizacion_id=pedimento_dict.get('organizacion'),
servicio=3 # 3 corresponde a "Pedimento Completo"
)
# Puedes devolver el ID de la tarea para consultar el estado después
return {"status": "submitted", "detail": "La solicitud de descarga del pedimento completo ha sido enviada.", "task_id": task.id}

View File

@@ -2,6 +2,7 @@ from fastapi import APIRouter, BackgroundTasks, status, HTTPException
from fastapi.responses import JSONResponse
from .schemas import RemesaBaseSchema
from .tasks import process_remesa_request
from api.api_v2.modules.tasks.services import register_task
import logging
logger = logging.getLogger("app.api")
@@ -14,8 +15,16 @@ async def download_remesa(remesa_request: RemesaBaseSchema):
"""
remesa_dict = remesa_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona
task = process_remesa_request.delay(remesa_dict)
# Registrar la tarea en el servicio de seguimiento
await register_task(
task_id=task.id,
message=f"Procesando descarga de remesa {remesa_dict.get('remesa', 'N/A')}",
status="submitted",
pedimento_id=remesa_dict.get('pedimento', {}).get('id'),
organizacion_id=remesa_dict.get('pedimento', {}).get('organizacion'),
servicio=5 # 5 corresponde a "Pedimento Remesas"
)
# Puedes devolver el ID de la tarea para consultar el estado después
return {"status": "submitted", "detail": "La solicitud de descarga de la remesa ha sido enviada.", "task_id": task.id}

View File

@@ -13,6 +13,7 @@ from .controllers import remesa_rest_controller, remesa_vu_controller, remesa_xm
from utils.helpers import soap_error
from ..common import create_service_response, create_error_response
# Logger configurado para el módulo
logger = logging.getLogger("app.api")
@@ -71,11 +72,8 @@ async def obtener_remesa(**kwargs) -> Dict[str, Any]:
)
# Generar nombre de archivo
file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}.xml"
# Enviar documento
try:
if soap_error(soap_response):
file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}.xml"
if soap_error(soap_response):
file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}_ERROR.xml"
document_response = await remesa_rest_controller.post_document(
soap_response=soap_response,
organizacion=pedimento_data.get('organizacion'),
@@ -84,15 +82,17 @@ async def obtener_remesa(**kwargs) -> Dict[str, Any]:
document_type=10,
)
raise HTTPException(status_code=500, detail="Error en la respuesta del servicio SOAP")
else:
document_response = await remesa_rest_controller.post_document(
soap_response=soap_response,
organizacion=pedimento_data.get('organizacion'),
pedimento=pedimento_data.get('id'),
file_name=file_name,
document_type=3,
)
# Enviar documento
try:
document_response = await remesa_rest_controller.post_document(
soap_response=soap_response,
organizacion=pedimento_data.get('organizacion'),
pedimento=pedimento_data.get('id'),
file_name=file_name,
document_type=3,
)
except Exception as e:
logger.error(f"Error al enviar documento: {e}")
raise HTTPException(status_code=500, detail="Error al guardar documento")
@@ -108,10 +108,19 @@ async def obtener_remesa(**kwargs) -> Dict[str, Any]:
logger.info(f"Remesa procesada exitosamente: {pedimento_data.get('pedimento')}")
return {
"documento": document_response,
"xml_content": remesas_data
}
return create_service_response(
message="Remesa procesada exitosamente",
data={
"documento": document_response,
"xml_content": remesas_data
},
metadata={
"file_name": file_name,
"document_type": 3,
"pedimento_app": pedimento_data.get('pedimento_app'),
"organizacion": pedimento_data.get('organizacion')
}
)
except HTTPException:
raise
@@ -170,7 +179,17 @@ async def post_remesa_data(**kwargs) -> Dict[str, Any]:
logger.info("Procesamiento de pedimento completo finalizado")
return result
# Crear respuesta estandarizada
return create_service_response(
success=True,
message="Procesamiento de remesa completado",
data=result,
warnings=[result["coves_error"]] if result.get("coves_error") else None,
metadata={
"total_coves_procesados": len(result.get("coves_procesados", [])) if result.get("coves_procesados") else 0
}
)

View File

@@ -0,0 +1,82 @@
import logging
import httpx
from typing import Dict, Any
from fastapi import HTTPException
from ..common import create_error_response
from core.config import settings
logger = logging.getLogger(__name__)
async def register_task(
task_id: str,
message: str,
status: str,
pedimento_id: str,
organizacion_id: str,
servicio: int
) -> Dict[str, Any]:
"""
Registra una tarea en el servicio de seguimiento.
Args:
task_id: ID de la tarea de Celery
message: Mensaje descriptivo de la tarea
status: Estado actual de la tarea
pedimento_id: ID del pedimento asociado
organizacion_id: ID de la organización
servicio: ID del tipo de servicio
1: Estado de pedimento
2: Listado de pedimentos
3: Pedimento Completo
4: Pedimento Partidas
5: Pedimento Remesas
6: Acuse
7: EDocument
8: Cove
9: Acuse Cove
Returns:
Dict con la respuesta del servicio
"""
try:
headers = {
"Authorization": f"Token {settings.API_TOKEN}"
}
async with httpx.AsyncClient() as client:
response = await client.post(
f"{settings.API_URL}/tasks/tasks/",
json={
"task_id": task_id,
"message": message,
"status": status,
"pedimento": pedimento_id,
"organizacion": organizacion_id,
"servicio": servicio
},
headers=headers
)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
logger.error(f"Error al registrar tarea {task_id}: {str(e)}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al registrar la tarea",
errors=[str(e)],
metadata={
"task_id": task_id,
"status": status,
"pedimento": pedimento_id
}
)
)
except Exception as e:
logger.error(f"Error inesperado al registrar tarea {task_id}: {str(e)}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error inesperado al registrar la tarea",
errors=[str(e)],
metadata={"task_id": task_id}
)
)

View File

@@ -22,5 +22,8 @@ def soap_error(soap_response): # Testeado
return True
if "<mensaje>El Cove o Adenda no existe, no está firmado o no cuenta con la autorización para consultarlo</mensaje>" in soap_response.text:
return True
if "<ns2:mensaje>No hay información para la búsqueda solicitada</ns2:mensaje>" in soap_response.text:
return True
# Aquí podrías agregar más lógica para verificar errores específicos en el XML
return False