diff --git a/api/api_v2/modules/acuses/routers.py b/api/api_v2/modules/acuses/routers.py index 95d7612..8f3ee7a 100644 --- a/api/api_v2/modules/acuses/routers.py +++ b/api/api_v2/modules/acuses/routers.py @@ -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)} diff --git a/api/api_v2/modules/acuses/services.py b/api/api_v2/modules/acuses/services.py index b9d843b..691409e 100644 --- a/api/api_v2/modules/acuses/services.py +++ b/api/api_v2/modules/acuses/services.py @@ -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 = { diff --git a/api/api_v2/modules/common/__init__.py b/api/api_v2/modules/common/__init__.py new file mode 100644 index 0000000..138709b --- /dev/null +++ b/api/api_v2/modules/common/__init__.py @@ -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'] \ No newline at end of file diff --git a/api/api_v2/modules/common/response.py b/api/api_v2/modules/common/response.py new file mode 100644 index 0000000..45e9926 --- /dev/null +++ b/api/api_v2/modules/common/response.py @@ -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 + ) \ No newline at end of file diff --git a/api/api_v2/modules/coves/routers.py b/api/api_v2/modules/coves/routers.py index b09b2c6..56f664a 100644 --- a/api/api_v2/modules/coves/routers.py +++ b/api/api_v2/modules/coves/routers.py @@ -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)} \ No newline at end of file diff --git a/api/api_v2/modules/coves/services.py b/api/api_v2/modules/coves/services.py index 8f86d1c..59b3dee 100644 --- a/api/api_v2/modules/coves/services.py +++ b/api/api_v2/modules/coves/services.py @@ -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: diff --git a/api/api_v2/modules/edocs/routers.py b/api/api_v2/modules/edocs/routers.py index d7a1752..b98cf40 100644 --- a/api/api_v2/modules/edocs/routers.py +++ b/api/api_v2/modules/edocs/routers.py @@ -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)} \ No newline at end of file diff --git a/api/api_v2/modules/edocs/services.py b/api/api_v2/modules/edocs/services.py index 9d64cb7..193fae0 100644 --- a/api/api_v2/modules/edocs/services.py +++ b/api/api_v2/modules/edocs/services.py @@ -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(' 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. diff --git a/api/api_v2/modules/partidas/routers.py b/api/api_v2/modules/partidas/routers.py index b33e5a5..967016b 100644 --- a/api/api_v2/modules/partidas/routers.py +++ b/api/api_v2/modules/partidas/routers.py @@ -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)} \ No newline at end of file diff --git a/api/api_v2/modules/partidas/services.py b/api/api_v2/modules/partidas/services.py index 14013e5..112e246 100644 --- a/api/api_v2/modules/partidas/services.py +++ b/api/api_v2/modules/partidas/services.py @@ -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 \ No newline at end of file diff --git a/api/api_v2/modules/pedimentos/routers.py b/api/api_v2/modules/pedimentos/routers.py index a5f609d..2dae985 100644 --- a/api/api_v2/modules/pedimentos/routers.py +++ b/api/api_v2/modules/pedimentos/routers.py @@ -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} diff --git a/api/api_v2/modules/remesas/routers.py b/api/api_v2/modules/remesas/routers.py index e05ee2c..9bebabf 100644 --- a/api/api_v2/modules/remesas/routers.py +++ b/api/api_v2/modules/remesas/routers.py @@ -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} diff --git a/api/api_v2/modules/remesas/services.py b/api/api_v2/modules/remesas/services.py index f6b3c8a..9814a5a 100644 --- a/api/api_v2/modules/remesas/services.py +++ b/api/api_v2/modules/remesas/services.py @@ -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 + } + ) diff --git a/api/api_v2/modules/tasks/services.py b/api/api_v2/modules/tasks/services.py index e69de29..2edaea8 100644 --- a/api/api_v2/modules/tasks/services.py +++ b/api/api_v2/modules/tasks/services.py @@ -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} + ) + ) diff --git a/utils/helpers.py b/utils/helpers.py index ef939b1..8fef0ab 100644 --- a/utils/helpers.py +++ b/utils/helpers.py @@ -22,5 +22,8 @@ def soap_error(soap_response): # Testeado return True if "El Cove o Adenda no existe, no está firmado o no cuenta con la autorización para consultarlo" in soap_response.text: return True + if "No hay información para la búsqueda solicitada" in soap_response.text: + return True + # Aquí podrías agregar más lógica para verificar errores específicos en el XML return False \ No newline at end of file