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 fastapi.responses import JSONResponse
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from api.api_v2.modules.authentication.services import get_current_user 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 .schemas import AcuseSchema, AcuseMasivoSchema
from .tasks import process_acuse_request from .tasks import process_acuse_request
@@ -19,7 +18,17 @@ async def obtener_acuse(acuse_request: AcuseSchema):
acuse_dict = acuse_request.model_dump() acuse_dict = acuse_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona # Ejecuta la tarea de Celery de forma asíncrona
task = process_acuse_request.delay(acuse_dict) 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"} return {"task_id": task.id, "status": "submitted"}
@@ -40,4 +49,14 @@ async def obtener_acuses(acuse_request: AcuseMasivoSchema):
task = process_acuse_request.delay(acuse_dict) task = process_acuse_request.delay(acuse_dict)
task_ids.append(task.id) 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)} 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 base64
import re 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 .controllers import acuse_vu_controller, acuse_rest_controller
from utils.helpers import soap_error 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 = { soap_headers = {
'Content-Type': 'text/xml; charset=utf-8', 'Content-Type': 'text/xml; charset=utf-8',
@@ -23,29 +29,68 @@ async def obtener_acuse(**kwargs):
) )
if response is None: 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: 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)): if soap_error(response):
acuse_base64 = _extract_acuse_data(response.text) 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: if acuse_base64 is None:
raise Exception("No se pudo extraer el acuse del documento de la respuesta SOAP.") 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) pdf_bytes = _decode_acuse_base64_content(acuse_base64)
if not pdf_bytes: 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 # Validar que el PDF sea válido
if not pdf_bytes.startswith(b'%PDF'): if not pdf_bytes.startswith(b'%PDF'):
import logging logger.error("El contenido decodificado no es un PDF válido")
logger = logging.getLogger("app.api") raise HTTPException(
logger.warning("El contenido decodificado no parece ser un PDF válido") 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 # Mejorar el nombre del archivo usando todos los datos relevantes
pedimento = kwargs.get('pedimento', {}) pedimento = kwargs.get('pedimento', {})
@@ -65,21 +110,49 @@ async def obtener_acuse(**kwargs):
) )
if rest_response is None: 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: 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( acuse_update_response = await change_edocument_status(
edoc=kwargs.get('edoc'), edoc=kwargs.get('edoc'),
status=True, status=True,
pedimento=pedimento pedimento=pedimento
) )
return {
"document_response": rest_response, return create_service_response(
"file_name": _file_name, message="Acuse procesado exitosamente",
"pedimento": pedimento_num, data={
"acuse_update_response": acuse_update_response "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): async def change_edocument_status(edoc: dict, status: bool, pedimento: dict):
data = { 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 fastapi import APIRouter, HTTPException
from .schemas import CoveListSchema, CoveRequestSchema from .schemas import CoveListSchema, CoveRequestSchema
from typing import List from typing import List, Dict, Any
from uuid import UUID from uuid import UUID
from .tasks import process_cove_request, process_acuse_cove_request from .tasks import process_cove_request, process_acuse_cove_request
from ..tasks.services import register_task
router = APIRouter() router = APIRouter()
# Aquí puedes definir tus endpoints relacionados con COVES usando el esquema CoveBaseSchema # 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): async def get_cove(cove: CoveRequestSchema):
# Lógica para obtener un COVE """Endpoint para obtener un COVE específico."""
task = process_cove_request.delay(cove.model_dump()) 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"} 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): 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 = [] task_ids = []
coves_dict = coves_request.model_dump() coves_dict = coves_request.model_dump()
pedimento = coves_dict.get('pedimento', {})
for cove in coves_dict.get('coves', []): for cove in coves_dict.get('coves', []):
cove_dict = { cove_dict = {
"cove": cove, "cove": cove,
"pedimento": coves_dict.get('pedimento'), "pedimento": pedimento,
"credencial": coves_dict.get('credencial') "credencial": coves_dict.get('credencial')
} }
task = process_cove_request.delay(cove_dict) task = process_cove_request.delay(cove_dict)
task_ids.append(task.id) 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_id": task.id, "coves_tasks_ids": task_ids, "status": "submitted"} return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}
@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): async def get_acuse_cove(cove: CoveRequestSchema):
# Lógica para obtener un COVE """Endpoint para obtener el acuse de un COVE específico."""
task = process_acuse_cove_request.delay(cove.model_dump()) 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"} 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): 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 = [] task_ids = []
coves_dict = coves_request.model_dump() coves_dict = coves_request.model_dump()
pedimento = coves_dict.get('pedimento', {})
for cove in coves_dict.get('coves', []): for cove in coves_dict.get('coves', []):
acuse_dict = { acuse_dict = {
"cove": cove, "cove": cove,
"pedimento": coves_dict.get('pedimento'), "pedimento": pedimento,
"credencial": coves_dict.get('credencial') "credencial": coves_dict.get('credencial')
} }
task = process_acuse_cove_request.delay(acuse_dict) task = process_acuse_cove_request.delay(acuse_dict)
task_ids.append(task.id) 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)} return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}

View File

@@ -2,17 +2,20 @@ import base64
import os import os
import logging import logging
import re import re
import tempfile
from typing import Any, Dict, List, Optional
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from fastapi import HTTPException from fastapi import HTTPException
from controllers.RESTController import rest_controller from controllers.RESTController import rest_controller
from controllers.SOAPController import soap_controller from controllers.SOAPController import soap_controller
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_der_private_key from cryptography.hazmat.primitives.serialization import load_der_private_key
import tempfile
from utils.helpers import soap_error from utils.helpers import soap_error
from .controllers import coves_vu_controller, coves_rest_controller from .controllers import coves_vu_controller, coves_rest_controller
from ..common import create_service_response, create_error_response
# Logger para el módulo # Logger para el módulo
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -42,8 +45,26 @@ async def consume_ws_get_cove(**kwargs):
cove = kwargs['cove'].get('cove', None) cove = kwargs['cove'].get('cove', None)
if not credenciales or not username or not cove: if not credenciales or not username or not cove:
raise Exception( missing = []
"Credenciales o COVE no proporcionados correctamente") 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}") 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") logger.info(f"COVE {cove} procesado exitosamente")
# Asegurar que la respuesta sea serializable return create_service_response(
result = { message=f"COVE {cove} procesado exitosamente",
"documento": document_response if document_response else None, data={
"cove_update_response": cove_status_response if cove_status_response else None "documento": document_response if document_response else None,
} "cove_update_response": cove_status_response if cove_status_response else None
},
return result 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: except Exception as e:
logger.error(f"Error procesando COVE: {str(e)}", exc_info=True) logger.error(f"Error procesando COVE: {str(e)}", exc_info=True)
# Asegurar que no se retornen datos binarios en el error raise HTTPException(
raise Exception(f"Error interno al procesar COVE: {str(e)}") 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): async def consume_ws_get_acuse_cove(**kwargs):
@@ -155,20 +196,57 @@ async def consume_ws_get_acuse_cove(**kwargs):
) )
if response is None: 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: 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): if soap_error(response):
rest_response = await coves_rest_controller.post_document( error_file_name = f"vu_AC_COVE_{kwargs.get('pedimento', {}).get('pedimento_app', 'N/A')}_{kwargs['cove'].get('cove', 'N/A')}_ERROR.xml"
soap_response=response, try:
organizacion=kwargs.get('pedimento').get('organizacion'), rest_response = await coves_rest_controller.post_document(
pedimento=kwargs.get('pedimento').get('id'), soap_response=response,
file_name=f"vu_AC_COVE_{kwargs.get('pedimento', {}).get('pedimento_app', 'N/A')}_{kwargs['cove'].get('cove', 'N/A')}_ERROR.xml", organizacion=kwargs.get('pedimento').get('organizacion'),
document_type=10, 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)): if (response) and (not soap_error(response)):
logger.debug(f"Respuesta SOAP recibida, extrayendo acuse...") logger.debug(f"Respuesta SOAP recibida, extrayendo acuse...")
acuse_base64 = _extract_acuse_data(response.text) acuse_base64 = _extract_acuse_data(response.text)
@@ -224,12 +302,22 @@ async def consume_ws_get_acuse_cove(**kwargs):
pedimento=kwargs.get('pedimento') pedimento=kwargs.get('pedimento')
) )
return { return create_service_response(
"document_response": rest_response, message="Acuse de COVE procesado exitosamente",
"file_name": _file_name, data={
"pedimento": pedimento_num, "document_response": rest_response,
"acuse_update": acuse_status "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 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 return firma, certificado, tmp_key_path
except Exception as e: except Exception as e:
logger.error( logger.error(f"Error obteniendo certificado/llave o generando firma: {e}")
f"Error obteniendo certificado/llave o generando firma: {e}")
# Limpiar archivo temporal si existe # Limpiar archivo temporal si existe
if 'tmp_key_path' in locals() and os.path.exists(tmp_key_path): if 'tmp_key_path' in locals() and os.path.exists(tmp_key_path):
try: try:
os.remove(tmp_key_path) os.remove(tmp_key_path)
except: except Exception as cleanup_error:
pass logger.warning(f"Error al limpiar archivo temporal: {cleanup_error}")
raise Exception(f"Error en fetch_sign_and_cer: {str(e)}")
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: 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 .schemas import EdocumentsSchema, EdocumentsMasivoSchema
from .tasks import process_edoc_download_request from .tasks import process_edoc_download_request
from api.api_v2.modules.authentication.services import get_current_user from api.api_v2.modules.authentication.services import get_current_user
from api.api_v2.modules.tasks.services import register_task
router = APIRouter() router = APIRouter()
@@ -17,6 +18,15 @@ async def download_edoc(edoc_request: EdocumentsSchema):
edoc_dict = edoc_request.model_dump() edoc_dict = edoc_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona # Ejecuta la tarea de Celery de forma asíncrona
task = process_edoc_download_request.delay(edoc_dict) 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 # Devuelve el ID de la tarea
return {"task_id": task.id, "status": "submitted"} 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 = process_edoc_download_request.delay(edoc_dict)
task_ids.append(task.id) 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)} 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 base64
import re import re
import logging import logging
import os
from typing import Any, Dict, Optional
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from fastapi import HTTPException
from utils.helpers import soap_error from utils.helpers import soap_error
from .controllers import edocs_rest_controller, edocs_vu_controller 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") logger = logging.getLogger("app.api")
# --- FUNCIONES AUXILIARES --- # --- 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: def _get_file_name(**kwargs) -> str:
pedimento = kwargs.get('pedimento', {}) pedimento = kwargs.get('pedimento', {})
@@ -89,25 +43,102 @@ async def obtener_edoc(**kwargs):
data=soap_xml, data=soap_xml,
headers=soap_headers headers=soap_headers
) )
# Validar respuesta del servicio SOAP
if response is None: 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: 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): 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: 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'] pdf_bytes = edoc_base64['pdf_bytes']
if not 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'): 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', {}) pedimento = kwargs.get('pedimento', {})
numero_documento = kwargs['edoc'].get('numero_edocument', '') numero_documento = kwargs['edoc'].get('numero_edocument', '')
@@ -116,12 +147,8 @@ async def obtener_edoc(**kwargs):
organizacion = pedimento.get("organizacion", None) organizacion = pedimento.get("organizacion", None)
pedimento_id = pedimento.get("id", None) pedimento_id = pedimento.get("id", None)
try: # No guardaremos el archivo localmente por seguridad
with open(_file_name, "wb") as f: logger.debug(f"Procesando documento {numero_documento} para pedimento {pedimento_id}")
f.write(pdf_bytes)
logger.info(f"PDF guardado localmente en {_file_name}")
except Exception as e:
logger.error(f"Error guardando el PDF localmente: {e}")
rest_response = await edocs_rest_controller.post_document( rest_response = await edocs_rest_controller.post_document(
binary_content=pdf_bytes, binary_content=pdf_bytes,
@@ -132,27 +159,62 @@ async def obtener_edoc(**kwargs):
) )
if rest_response is None: 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: 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") logger.info("Documento enviado, actualizando status de Edoc")
edoc_status_response = await change_edocument_status( try:
edoc=doc, edoc_status_response = await change_edocument_status(
status=True, edoc=doc,
pedimento=pedimento status=True,
pedimento=pedimento
)
except Exception as e:
logger.warning(f"Error al actualizar estado del documento: {e}")
# No fallamos aquí porque el documento ya se guardó exitosamente
logger.info(f"E-document {numero_documento} procesado exitosamente")
return create_service_response(
message=f"E-document {numero_documento} procesado exitosamente",
data={
"document_response": rest_response,
"file_name": _file_name,
"numero_documento": numero_documento,
"edoc_update_response": edoc_status_response if edoc_status_response else None
},
metadata={
"document_type": 5,
"pedimento_app": pedimento.get('pedimento_app'),
"organizacion": organizacion,
"content_type": "application/pdf",
"has_cadena_original": bool(edoc_base64.get('cadena_original')),
"has_sello_digital": bool(edoc_base64.get('sello_digital'))
}
) )
print(edoc_status_response)
return {
"document_response": rest_response,
"file_name": _file_name,
"numero_documento": numero_documento,
"edoc_update_response": edoc_status_response if edoc_status_response else None
}
async def change_edocument_status(edoc: dict, status: bool, pedimento: dict): async def change_edocument_status(edoc: dict, status: bool, pedimento: dict):
data = { data = {
@@ -168,8 +230,6 @@ async def change_edocument_status(edoc: dict, status: bool, pedimento: dict):
return response return response
def extract_pdf_bytes_from_xml_content(xml_content: str): def extract_pdf_bytes_from_xml_content(xml_content: str):
""" """
Extrae el PDF y metadatos desde un string XML. 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.authentication.services import get_current_user
from api.api_v2.modules.tasks.services import register_task
from .schemas import PartidaRequestSchema, PartidaListSchema from .schemas import PartidaRequestSchema, PartidaListSchema
from .tasks import process_partida_request from .tasks import process_partida_request
@@ -19,6 +20,15 @@ async def obtener_partida(partida_request: PartidaRequestSchema):
acuse_dict = partida_request.model_dump() acuse_dict = partida_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona # Ejecuta la tarea de Celery de forma asíncrona
task = process_partida_request.delay(acuse_dict) 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 # Puedes devolver el ID de la tarea para consultar el estado después
return {"task_id": task.id, "status": "submitted"} 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 = process_partida_request.delay(partida_dict)
task_ids.append(task.id) 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)} return {"task_ids": task_ids, "status": "submitted", "total": len(task_ids)}

View File

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

View File

@@ -2,6 +2,7 @@ from fastapi import APIRouter, BackgroundTasks, status, HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from .schemas import PedimentoCompletoRequestSchema from .schemas import PedimentoCompletoRequestSchema
from .tasks import process_pedimento_completo_request from .tasks import process_pedimento_completo_request
from api.api_v2.modules.tasks.services import register_task
import logging import logging
logger = logging.getLogger("app.api") logger = logging.getLogger("app.api")
@@ -14,8 +15,16 @@ async def download_pedimento_completo(Pedimento: PedimentoCompletoRequestSchema)
""" """
pedimento_dict = Pedimento.model_dump() pedimento_dict = Pedimento.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona # Ejecuta la tarea de Celery de forma asíncrona
task = process_pedimento_completo_request.delay(pedimento_dict) 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 # 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} 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 fastapi.responses import JSONResponse
from .schemas import RemesaBaseSchema from .schemas import RemesaBaseSchema
from .tasks import process_remesa_request from .tasks import process_remesa_request
from api.api_v2.modules.tasks.services import register_task
import logging import logging
logger = logging.getLogger("app.api") logger = logging.getLogger("app.api")
@@ -14,8 +15,16 @@ async def download_remesa(remesa_request: RemesaBaseSchema):
""" """
remesa_dict = remesa_request.model_dump() remesa_dict = remesa_request.model_dump()
# Ejecuta la tarea de Celery de forma asíncrona # Ejecuta la tarea de Celery de forma asíncrona
task = process_remesa_request.delay(remesa_dict) 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 # 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} 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 utils.helpers import soap_error
from ..common import create_service_response, create_error_response
# Logger configurado para el módulo # Logger configurado para el módulo
logger = logging.getLogger("app.api") logger = logging.getLogger("app.api")
@@ -71,11 +72,8 @@ async def obtener_remesa(**kwargs) -> Dict[str, Any]:
) )
# Generar nombre de archivo # Generar nombre de archivo
file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}.xml" file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}.xml"
if soap_error(soap_response):
# Enviar documento file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}_ERROR.xml"
try:
if soap_error(soap_response):
file_name = f"vu_RM_{pedimento_data.get('pedimento_app', 'unknown')}.xml"
document_response = await remesa_rest_controller.post_document( document_response = await remesa_rest_controller.post_document(
soap_response=soap_response, soap_response=soap_response,
organizacion=pedimento_data.get('organizacion'), organizacion=pedimento_data.get('organizacion'),
@@ -84,14 +82,16 @@ async def obtener_remesa(**kwargs) -> Dict[str, Any]:
document_type=10, document_type=10,
) )
raise HTTPException(status_code=500, detail="Error en la respuesta del servicio SOAP") raise HTTPException(status_code=500, detail="Error en la respuesta del servicio SOAP")
else: # Enviar documento
document_response = await remesa_rest_controller.post_document( try:
soap_response=soap_response,
organizacion=pedimento_data.get('organizacion'), document_response = await remesa_rest_controller.post_document(
pedimento=pedimento_data.get('id'), soap_response=soap_response,
file_name=file_name, organizacion=pedimento_data.get('organizacion'),
document_type=3, pedimento=pedimento_data.get('id'),
) file_name=file_name,
document_type=3,
)
except Exception as e: except Exception as e:
logger.error(f"Error al enviar documento: {e}") logger.error(f"Error al enviar documento: {e}")
@@ -108,10 +108,19 @@ async def obtener_remesa(**kwargs) -> Dict[str, Any]:
logger.info(f"Remesa procesada exitosamente: {pedimento_data.get('pedimento')}") logger.info(f"Remesa procesada exitosamente: {pedimento_data.get('pedimento')}")
return { return create_service_response(
"documento": document_response, message="Remesa procesada exitosamente",
"xml_content": remesas_data 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: except HTTPException:
raise raise
@@ -170,7 +179,17 @@ async def post_remesa_data(**kwargs) -> Dict[str, Any]:
logger.info("Procesamiento de pedimento completo finalizado") 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 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: 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 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 # Aquí podrías agregar más lógica para verificar errores específicos en el XML
return False return False