fix: Se crean endpoints para mostrar la informacion de peticiones y respuestas de los webservices, en el area del auditor del sistema.

This commit is contained in:
2026-01-02 08:07:30 -07:00
parent 1cb2830d71
commit 8a4e732703
7 changed files with 1780 additions and 11 deletions

View File

@@ -14,6 +14,7 @@ from rest_framework.exceptions import ValidationError
from .serializers import DocumentSerializer, FuenteSerializer, DocumentTypeSerializer
from .models import Document, Fuente, DocumentType
from ..customs.models import Pedimento
from ..vucem.models import CredencialesImportador
from api.organization.models import UsoAlmacenamiento
from io import BytesIO
import zipfile
@@ -35,9 +36,91 @@ logger = logging.getLogger(__name__)
import os
from django.core.files.storage import default_storage
from django.conf import settings
import requests
import re
from mixins.filtrado_organizacion import DocumentosFiltradosMixin
# Configuración de patrones y mapeos (definirlo fuera del try para reutilización)
DOCUMENT_PATTERNS = {
'REQUEST': {
'VU_PC': (r".*VU_PC.*REQUEST\.xml$", 13, "Request Pedimento Completo VU"),
'VU_ED': (r".*VU_ED.*REQUEST\.xml$", 21, "Request E-Document VU"),
'VU_PT': (r".*VU_PT.*REQUEST\.xml$", 17, "Request Partidas VU"),
'VU_AC_COVE': (r".*VU_AC_COVE.*REQUEST\.xml$", 23, "Request Acuses COVES VU"),
'VU_COVE': (r".*VU_COVE.*REQUEST\.xml$", 19, "Request COVES VU"),
'VU_RM': (r".*VU_RM.*REQUEST\.xml$", 15, "Request Remesas VU"),
'VU_AC': (r".*VU_AC.*REQUEST\.xml$", 25, "Request Acuses VU"),
},
'ERROR': {
'VU_PC': (r".*VU_PC.*ERROR\.xml$", 14, "Error Pedimento Completo VU"),
'VU_ED': (r".*VU_ED.*ERROR\.xml$", 22, "Error E-Document VU"),
'VU_PT': (r".*VU_PT.*ERROR\.xml$", 18, "Error Partidas VU"),
'VU_AC_COVE': (r".*VU_AC_COVE.*ERROR\.xml$", 24, "Error Acuses COVES VU"),
'VU_COVE': (r".*VU_COVE.*ERROR\.xml$", 20, "Error COVES VU"),
'VU_RM': (r".*VU_RM.*ERROR\.xml$", 16, "Error Remesas VU"),
'VU_AC': (r".*VU_AC.*ERROR\.xml$", 26, "Error Acuses VU"),
}
}
def eliminar_documentos_existentes(organizacion, nombre_sin_extension, pedimento_id):
"""Elimina documentos existentes con el mismo nombre base"""
documentos_existentes = Document.objects.filter(
archivo__icontains=nombre_sin_extension,
organizacion=organizacion,
pedimento_id=pedimento_id
)
if not documentos_existentes.exists():
return
for doc_existente in documentos_existentes:
# Eliminar archivo físico si existe
if doc_existente.archivo and os.path.exists(doc_existente.archivo.path):
try:
os.remove(doc_existente.archivo.path)
except Exception as e:
logger.error(f"Error al eliminar archivo físico: {e}")
# Eliminar registros de la base de datos
documentos_existentes.delete()
def obtener_tipo_documento_por_patron(nombre_archivo, organizacion, pedimento_id):
"""Determina el tipo de documento basado en patrones de nombre"""
nombre_sin_extension = nombre_archivo.rsplit('.', 1)[0]
# Verificar patrones REQUEST
for doc_key, (patron, type_id, descripcion) in DOCUMENT_PATTERNS['REQUEST'].items():
if re.search(patron, nombre_archivo, re.IGNORECASE):
try:
# Eliminar documentos existentes
eliminar_documentos_existentes(organizacion, nombre_sin_extension, pedimento_id)
# Obtener tipo de documento
return DocumentType.objects.get(id=type_id)
except DocumentType.DoesNotExist:
raise ValidationError({
"error": f"El tipo de documento '{descripcion}' no existe. Por favor, créelo primero."
})
# Verificar patrones ERROR (si es necesario procesarlos)
for doc_key, (patron, type_id, descripcion) in DOCUMENT_PATTERNS['ERROR'].items():
if re.search(patron, nombre_archivo, re.IGNORECASE):
try:
# Eliminar documentos existentes
eliminar_documentos_existentes(organizacion, nombre_sin_extension, pedimento_id)
# Obtener tipo de documento
return DocumentType.objects.get(id=type_id)
except DocumentType.DoesNotExist:
raise ValidationError({
"error": f"El tipo de documento '{descripcion}' no existe. Por favor, créelo primero."
})
return None
class CustomPagination(PageNumberPagination):
"""
@@ -144,12 +227,45 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
"codigo": "storage_limit_exceeded"
}, code=status.HTTP_400_BAD_REQUEST)
# Guardar documento y actualizar espacio atómicamente
documento = serializer.save(
organizacion=organizacion,
size=archivo.size,
extension=archivo.name.split('.')[-1].lower()
)
try:
pedimento_id = serializer.validated_data.get('pedimento').id if serializer.validated_data.get('pedimento') else None
document_type = serializer.validated_data.get('document_type')
# Determinar el tipo de documento basado en el nombre del archivo
detected_type = obtener_tipo_documento_por_patron(archivo.name, organizacion, pedimento_id)
if detected_type:
document_type = detected_type
else:
# Lógica para archivos que no coinciden con los patrones conocidos
logger.warning(f"No se encontró patrón para archivo: {archivo.name}")
# Puedes mantener el document_type original o manejarlo de otra forma
except ValidationError as ve:
raise ve
except Exception as e:
# Como fallback, intentar obtener cualquier DocumentType existente
logger.error(f"Error al determinar el tipo de documento basado en el nombre del archivo: {e}")
try:
# Guardar documento y actualizar espacio atómicamente
documento = serializer.save(
document_type=document_type,
organizacion=organizacion,
size=archivo.size,
extension=archivo.name.split('.')[-1].lower()
)
except Exception as e:
# Guardar documento y actualizar espacio atómicamente
documento = serializer.save(
organizacion=organizacion,
size=archivo.size,
extension=archivo.name.split('.')[-1].lower()
)
uso.espacio_utilizado = nuevo_espacio_utilizado
uso.save()
@@ -833,7 +949,114 @@ class PedimentoDocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
queryset = queryset.filter(pedimento__pedimento_app=pedimento_numero)
return queryset
class TriggerPedimentoCompletoView(APIView):
"""
Endpoint interno para disparar la descarga de pedimento completo
en el microservicio FastAPI. Reenvía el payload tal cual y devuelve
la respuesta del microservicio (normalmente un `task_id`).
"""
# permission_classes = [IsAuthenticated]
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
my_tags = ['Microservice - Pedimento Completo']
def post(self, request):
if not request.user.is_authenticated or not hasattr(request.user, 'organizacion'):
return Response({"error": "Usuario no autenticado o sin organización"}, status=401)
# Validación mínima
# if not payload.get('credencial') or not payload.get('pedimento_id'):
# return Response({"error": "Se requieren 'credencial' y 'pedimento'"}, status=status.HTTP_400_BAD_REQUEST)
if not request.data.get('pedimento_id'):
return Response({"error": "Se requieren 'credencial' y 'pedimento'"}, status=status.HTTP_400_BAD_REQUEST)
pedimento_id = request.data.get('pedimento_id')
# Verificar que el pedimento existe y pertenece a la organización del usuario
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
if not pedimento.contribuyente:
return Response({"error": "El pedimento no tiene un contribuyente asociado"}, status=status.HTTP_400_BAD_REQUEST)
contribuyente_rfc = pedimento.contribuyente.rfc
payload = {
"pedimento": {
"id": str(pedimento.id),
"pedimento": pedimento.pedimento,
"pedimento_app": pedimento.pedimento_app,
"aduana": pedimento.aduana,
"patente": pedimento.patente,
"contribuyente": contribuyente_rfc,
"organizacion": str(pedimento.organizacion.id)
},
"credencial": {
"id": "",
"user": "",
"password": "",
"efirma": "",
"key": "",
"cer": "",
"is_active": False,
"organizacion": ""
}
}
except Pedimento.DoesNotExist:
return Response({"error": "Pedimento no encontrado"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({"error": f"Error al buscar pedimento: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
try:
credenciales = CredencialesImportador.objects.get(rfc=contribuyente_rfc)
vucem = credenciales.vucem
# Obtener las rutas de los archivos, no los objetos FieldFile
key_path = vucem.key.path if vucem.key else ""
cer_path = vucem.cer.path if vucem.cer else ""
payload['credencial'] = {
"id": str(credenciales.id),
"user": vucem.usuario if vucem.usuario else "",
"password": vucem.password if vucem.password else "",
"efirma": vucem.efirma if vucem.efirma else "",
"key": key_path,
"cer": cer_path,
"is_active": vucem.is_active if vucem.is_active else False,
"organizacion": str(credenciales.organizacion.id) if credenciales.organizacion else ""
}
except CredencialesImportador.DoesNotExist:
return Response({"error": "No se encontró credencial VUCEM para la organización del pedimento"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({"error": f"Error al buscar credencial VUCEM: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
try:
# Obtener la URL desde las variables de entorno
api_url = os.getenv('SERVICE_API_URL_V2')
logger.info(f"Usando MICROSERVICE_BASE_URL: {api_url}")
endpoint = f"{api_url.rstrip('/')}/services/auditar_pedimento_completo"
# endpoint = "http://localhost:8001/api/v2/services/auditar_pedimento_completo"
except requests.exceptions.RequestException as e:
logger.error(f"Error obteniendo MICROSERVICE_BASE_URL: {e}")
return Response({"error": "Error obteniendo la URL del microservicio", "detail": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
logger.error(f"Error inesperado obteniendo MICROSERVICE_BASE_URL: {e}")
return Response({"error": "Error inesperado obteniendo la URL del microservicio", "detail": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
try:
resp = requests.post(endpoint, json=payload, timeout=30)
except requests.exceptions.RequestException as e:
logger.error(f"Error comunicándose con microservice: {e}")
return Response({"error": "No se pudo conectar con el microservicio", "detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
try:
content = resp.json()
except ValueError:
content = {"detail": resp.text}
return Response(content, status=resp.status_code)