2 Commits

7 changed files with 1836 additions and 61 deletions

View File

@@ -1,3 +1,6 @@
import os
from datetime import datetime
from django.db import models
from celery import shared_task, group from celery import shared_task, group
from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocument from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocument
from core.utils import xml_controller from core.utils import xml_controller
@@ -198,7 +201,7 @@ def auditar_coves(organizacion_id):
pedimento, pedimento,
servicio=8, servicio=8,
related_name='coves', related_name='coves',
variable='acuse_descargado', variable='cove_descargado',
mensaje='COVE' mensaje='COVE'
) )
@@ -247,7 +250,7 @@ def auditar_cove_por_pedimento(pedimento_id):
pedimento, pedimento,
servicio=8, servicio=8,
related_name='coves', related_name='coves',
variable='acuse_descargado', variable='cove_descargado',
mensaje='COVE' mensaje='COVE'
) )
return {'success': True, 'pedimento_id': str(pedimento_id)} return {'success': True, 'pedimento_id': str(pedimento_id)}
@@ -302,3 +305,154 @@ def auditar_acuse_por_pedimento(pedimento_id):
except Exception as e: except Exception as e:
return {'success': False, 'error': str(e), 'pedimento_id': str(pedimento_id)} return {'success': False, 'error': str(e), 'pedimento_id': str(pedimento_id)}
@shared_task
def auditar_pedimento_por_id(pedimento_id):
"""
Tarea para auditar un pedimento específico verificando todos sus documentos y datos.
"""
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
resultado = {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'pedimento_app': pedimento.pedimento_app,
'organizacion': str(pedimento.organizacion.id),
'fecha_auditoria': datetime.now().isoformat(),
'estado_general': 'EN_PROGRESO',
'detalles': {}
}
# 1. Verificar documentos XML
from api.record.models import Document
documentos_xml = Document.objects.filter(
pedimento=pedimento,
archivo__endswith='.xml'
)
resultado['detalles']['documentos_xml'] = {
'total': documentos_xml.count(),
'archivos': []
}
for doc in documentos_xml:
try:
xml_info = {
'id': str(doc.id),
'nombre': os.path.basename(doc.archivo.name),
'tamanio': doc.size,
'extension': doc.extension,
'tipo': doc.document_type.descripcion if doc.document_type else 'Desconocido'
}
# Verificar si el archivo existe físicamente
if os.path.exists(doc.archivo.path):
xml_info['existe_fisicamente'] = True
# Intentar leer el XML
try:
with open(doc.archivo.path, 'r', encoding='utf-8') as f:
content = f.read()
xml_info['es_xml_valido'] = '<?xml' in content[:100]
xml_info['tamanio_bytes'] = len(content)
except Exception as e:
xml_info['error_lectura'] = str(e)
else:
xml_info['existe_fisicamente'] = False
except Exception as e:
xml_info['error'] = str(e)
resultado['detalles']['documentos_xml']['archivos'].append(xml_info)
# 2. Verificar si hay documentos asociados
resultado['detalles']['documentos_totales'] = {
'total': pedimento.documents.count(),
'por_tipo': {}
}
for doc_type in pedimento.documents.values('document_type__descripcion').annotate(total=models.Count('id')):
tipo = doc_type['document_type__descripcion'] or 'Sin tipo'
resultado['detalles']['documentos_totales']['por_tipo'][tipo] = doc_type['total']
# 3. Verificar COVEs
resultado['detalles']['coves'] = {
'total': pedimento.coves.count(),
'descargados': pedimento.coves.filter(cove_descargado=True).count(),
'con_acuse': pedimento.coves.filter(acuse_cove_descargado=True).count()
}
# 4. Verificar EDocuments
resultado['detalles']['edocuments'] = {
'total': pedimento.documentos.count(),
'descargados': pedimento.documentos.filter(edocument_descargado=True).count(),
'con_acuse': pedimento.documentos.filter(acuse_descargado=True).count()
}
# 5. Verificar procesamientos
resultado['detalles']['procesamientos'] = {
'total': pedimento.procesamientos.count(),
'por_estado': {}
}
for proc in pedimento.procesamientos.values('estado__estado').annotate(total=models.Count('id')):
estado = proc['estado__estado'] or 'Sin estado'
resultado['detalles']['procesamientos']['por_estado'][estado] = proc['total']
# 6. Verificar campos importantes del pedimento
campos_revisados = {
'numero_operacion': bool(pedimento.numero_operacion),
'numero_partidas': bool(pedimento.numero_partidas),
'importe_total': bool(pedimento.importe_total),
'contribuyente': bool(pedimento.contribuyente),
'tiene_remesas': pedimento.remesas,
'partidas_creadas': pedimento.partidas.count() > 0,
'fecha_pago': bool(pedimento.fecha_pago)
}
resultado['detalles']['campos_pedimento'] = campos_revisados
resultado['detalles']['campos_completos'] = sum(campos_revisados.values())
resultado['detalles']['campos_totales'] = len(campos_revisados)
# 7. Determinar estado general
campos_completos = resultado['detalles']['campos_completos']
total_campos = resultado['detalles']['campos_totales']
if documentos_xml.count() == 0:
resultado['estado_general'] = 'SIN_XML'
resultado['mensaje'] = 'No se encontraron documentos XML'
elif campos_completos == total_campos:
resultado['estado_general'] = 'COMPLETO'
resultado['mensaje'] = 'Pedimento completamente procesado'
elif campos_completos >= total_campos * 0.7:
resultado['estado_general'] = 'PARCIAL'
resultado['mensaje'] = 'Pedimento parcialmente procesado'
else:
resultado['estado_general'] = 'INCOMPLETO'
resultado['mensaje'] = 'Pedimento con información incompleta'
resultado['porcentaje_completitud'] = (campos_completos / total_campos) * 100 if total_campos > 0 else 0
# 8. Sugerencias
sugerencias = []
if not pedimento.numero_operacion:
sugerencias.append("Falta el número de operación")
if not pedimento.numero_partidas:
sugerencias.append("Falta el número de partidas")
if pedimento.numero_partidas and pedimento.numero_partidas > pedimento.partidas.count():
sugerencias.append(f"Faltan partidas: {pedimento.numero_partidas - pedimento.partidas.count()} de {pedimento.numero_partidas}")
if not pedimento.contribuyente:
sugerencias.append("Falta el contribuyente asociado")
resultado['sugerencias'] = sugerencias
return resultado
except Pedimento.DoesNotExist:
return {
'error': f'Pedimento con ID {pedimento_id} no encontrado',
'pedimento_id': str(pedimento_id)
}
except Exception as e:
return {
'error': f'Error auditar pedimento {pedimento_id}: {str(e)}',
'pedimento_id': str(pedimento_id)
}

View File

@@ -0,0 +1,194 @@
# auditoria_xml.py
import xml.etree.ElementTree as ET
from datetime import datetime
def extraer_info_pedimento_xml(xml_content):
"""
Extrae información específica de un XML de pedimento.
"""
try:
# Parsear el XML
root = ET.fromstring(xml_content)
# Buscar el namespace (puede variar)
namespaces = {
'S': 'http://schemas.xmlsoap.org/soap/envelope/',
'ns2': 'http://www.ventanillaunica.gob.mx/pedimentos/ws/oxml/consultarpedimentocompleto',
'ns3': 'http://www.ventanillaunica.gob.mx/common/ws/oxml/respuesta'
}
resultado = {}
# Extraer número de operación
num_op = root.find('.//ns2:numeroOperacion', namespaces)
if num_op is not None and num_op.text:
resultado['numero_operacion'] = num_op.text
# Extraer información del pedimento
pedimento_elem = root.find('.//ns2:pedimento', namespaces)
if pedimento_elem is not None:
# Número de pedimento
ped_num = pedimento_elem.find('ns2:pedimento', namespaces)
if ped_num is not None and ped_num.text:
resultado['numero_pedimento'] = ped_num.text
# Número de partidas
partidas = pedimento_elem.find('ns2:partidas', namespaces)
if partidas is not None and partidas.text:
try:
resultado['numero_partidas'] = int(partidas.text)
except (ValueError, TypeError):
pass
# Tipo de operación clave
tipo_op_clave = pedimento_elem.find('.//ns2:tipoOperacion/ns2:clave', namespaces)
if tipo_op_clave is not None and tipo_op_clave.text:
if tipo_op_clave.text.strip() == '1':
resultado['tipo_operacion'] = 'Importacion'
resultado['tipo_operacion_descripcion'] = 'Indica operacion como Importaciones'
elif tipo_op_clave.text.strip() == '2':
resultado['tipo_operacion'] = 'Exportacion'
resultado['tipo_operacion_descripcion'] = 'Indica operacion de exportacion'
# Clave del documento (clave_pedimento)
clave_doc = pedimento_elem.find('.//ns2:claveDocumento/ns2:clave', namespaces)
if clave_doc is not None and clave_doc.text:
resultado['clave_pedimento'] = clave_doc.text.strip()
# Aduana (patente)
aduana = pedimento_elem.find('.//ns2:aduanaEntradaSalida/ns2:clave', namespaces)
if aduana is not None and aduana.text:
resultado['aduana_clave'] = aduana.text.strip()
# Importador/Exportador
importador = pedimento_elem.find('.//ns2:importadorExportador', namespaces)
if importador is not None:
rfc = importador.find('ns2:rfc', namespaces)
if rfc is not None and rfc.text:
resultado['contribuyente_rfc'] = rfc.text.strip()
razon_social = importador.find('ns2:razonSocial', namespaces)
if razon_social is not None and razon_social.text:
resultado['contribuyente_nombre'] = razon_social.text.strip()
# Valor en dólares
valor_dolares = importador.find('ns2:valorDolares', namespaces)
if valor_dolares is not None and valor_dolares.text:
try:
resultado['valor_dolares'] = float(valor_dolares.text)
except (ValueError, TypeError):
pass
# Aduana de despacho
aduana_despacho = importador.find('ns2:aaduanaDespacho/ns2:clave', namespaces)
if aduana_despacho is not None and aduana_despacho.text:
resultado['aduana_despacho'] = aduana_despacho.text.strip()
# Encabezado del pedimento
encabezado = pedimento_elem.find('ns2:encabezado', namespaces)
if encabezado is not None:
# Aduana
aduana = encabezado.find('ns2:aduanaEntradaSalida/ns2:clave', namespaces)
if aduana is not None and aduana.text:
resultado['aduana_clave'] = aduana.text.strip()
# Tipo de cambio
tipo_cambio = encabezado.find('ns2:tipoCambio', namespaces)
if tipo_cambio is not None and tipo_cambio.text:
try:
resultado['tipo_cambio'] = float(tipo_cambio.text)
except (ValueError, TypeError):
pass
# RFC Agente Aduanal
rfc_agente = encabezado.find('ns2:rfcAgenteAduanalSocFactura', namespaces)
if rfc_agente is not None and rfc_agente.text:
resultado['rfc_agente_aduanal'] = rfc_agente.text.strip()
# CURP Apoderado
curp_apoderado = encabezado.find('ns2:curpApoderadomandatario', namespaces)
if curp_apoderado is not None and curp_apoderado.text:
resultado['curp_apoderado'] = curp_apoderado.text.strip()
# Valor Aduanal Total
valor_aduanal = encabezado.find('ns2:valorAduanalTotal', namespaces)
if valor_aduanal is not None and valor_aduanal.text:
try:
resultado['valor_aduanal_total'] = float(valor_aduanal.text)
except (ValueError, TypeError):
pass
# Valor Comercial Total
valor_comercial = encabezado.find('ns2:valorComercialTotal', namespaces)
if valor_comercial is not None and valor_comercial.text:
try:
resultado['valor_comercial_total'] = float(valor_comercial.text)
except (ValueError, TypeError):
pass
# Fechas
fechas = pedimento_elem.findall('.//ns2:fechas', namespaces)
for fecha_elem in fechas:
fecha = fecha_elem.find('ns2:fecha', namespaces)
clave_fecha = fecha_elem.find('ns2:tipo/ns2:clave', namespaces)
if fecha is not None and fecha.text and clave_fecha is not None and clave_fecha.text:
fecha_texto = fecha.text.strip()
clave_fecha_texto = clave_fecha.text.strip()
# Mapeo de claves según especificación
if clave_fecha_texto == '1': # Entrada
resultado['fecha_entrada'] = fecha_texto
elif clave_fecha_texto == '2': # Pago
resultado['fecha_pago'] = fecha_texto
elif clave_fecha_texto == '3': # Extracción
resultado['fecha_extraccion'] = fecha_texto
elif clave_fecha_texto == '5': # Presentación
resultado['fecha_presentacion'] = fecha_texto
elif clave_fecha_texto == '6': # Importación
resultado['fecha_importacion'] = fecha_texto
elif clave_fecha_texto == '7': # Original
resultado['fecha_original'] = fecha_texto
else:
resultado[f'fecha_clave_{clave_fecha_texto}'] = fecha_texto
# Facturas (para COVEs)
facturas = pedimento_elem.findall('.//ns2:facturas', namespaces)
coves_encontrados = []
for factura in facturas:
numero = factura.find('ns2:numero', namespaces)
if numero is not None and numero.text:
coves_encontrados.append(numero.text.strip())
if coves_encontrados:
resultado['coves_en_xml'] = coves_encontrados
# E-Documents
identificadores = pedimento_elem.findall('.//ns2:identificadores/ns2:identificadores', namespaces)
edocs_encontrados = []
for ident in identificadores:
clave = ident.find('claveIdentificador/descripcion', namespaces)
complemento = ident.find('complemento1', namespaces)
if clave is not None and clave.text and 'E_DOCUMENT' in clave.text:
if complemento is not None and complemento.text:
edocs_encontrados.append(complemento.text.strip())
if edocs_encontrados:
resultado['edocuments_en_xml'] = edocs_encontrados
# Verificar si hay error en la respuesta
tiene_error = root.find('.//ns3:tieneError', namespaces)
if tiene_error is not None:
resultado['tiene_error'] = tiene_error.text.lower() == 'true'
return resultado
except ET.ParseError as e:
return {'error_parse': str(e)}
except Exception as e:
return {'error': str(e)}

View File

@@ -42,7 +42,24 @@ from .views_auditor import (
auditar_acuse_cove_pedimento_endpoint, auditar_acuse_cove_pedimento_endpoint,
auditar_edocument_pedimento_endpoint, auditar_edocument_pedimento_endpoint,
auditar_acuse_pedimento_endpoint, auditar_acuse_pedimento_endpoint,
auditor_procesar_pedimentos_organizacion auditar_procesamiento_remesa_pedimento_endpoint,
auditor_procesar_pedimentos_organizacion,
auditar_peticion_respuesta_pedimento_completo,
auditor_obtener_peticion_pedimento_vu,
auditor_obtener_respuesta_pedimento_vu,
auditor_obtener_peticion_remesa_vu,
auditor_obtener_respuesta_remesa_vu,
auditor_obtener_peticion_partidas_vu,
auditor_obtener_respuesta_partidas_vu,
auditor_obtener_peticion_acuse_vu,
auditor_obtener_respuesta_acuse_vu,
auditor_obtener_peticion_cove_vu,
auditor_obtener_respuesta_cove_vu,
auditor_obtener_peticion_acuse_cove_vu,
auditor_obtener_respuesta_acuse_cove_vu,
auditor_obtener_peticion_edocument_vu,
auditor_obtener_respuesta_edocument_vu,
auditar_pedimento_endpoint,
) )
urlpatterns = [ urlpatterns = [
@@ -58,5 +75,24 @@ urlpatterns = [
path('auditor/auditar-acuse-cove/pedimento/', auditar_acuse_cove_pedimento_endpoint, name='auditar-acuse-cove-pedimento'), path('auditor/auditar-acuse-cove/pedimento/', auditar_acuse_cove_pedimento_endpoint, name='auditar-acuse-cove-pedimento'),
path('auditor/auditar-edocument/pedimento/', auditar_edocument_pedimento_endpoint, name='auditar-edocument-pedimento'), path('auditor/auditar-edocument/pedimento/', auditar_edocument_pedimento_endpoint, name='auditar-edocument-pedimento'),
path('auditor/auditar-acuse/pedimento/', auditar_acuse_pedimento_endpoint, name='auditar-acuse-pedimento'), path('auditor/auditar-acuse/pedimento/', auditar_acuse_pedimento_endpoint, name='auditar-acuse-pedimento'),
path('auditor/auditar-remesa/pedimento/', auditar_procesamiento_remesa_pedimento_endpoint, name='auditar-remesa-pedimento'),
path('auditor/auditar-pedimento/', auditar_pedimento_endpoint, name='auditar-pedimento'),
path('auditor/procesar-pedimentos/organizaciones/', auditor_procesar_pedimentos_organizacion, name='procesar-pedimentos-organizaciones'), path('auditor/procesar-pedimentos/organizaciones/', auditor_procesar_pedimentos_organizacion, name='procesar-pedimentos-organizaciones'),
path('auditor/peticion-respuesta/pedimento-vu/', auditar_peticion_respuesta_pedimento_completo, name='peticion-respuesta-pedimento-vu'),
path('auditor/obtener-peticion/pedimento-vu/', auditor_obtener_peticion_pedimento_vu, name='obtener-peticion-pedimento-vu'),
path('auditor/obtener-respuesta/pedimento-vu/', auditor_obtener_respuesta_pedimento_vu, name='obtener-respuesta-pedimento-vu'),
path('auditor/obtener-peticion/remesa-vu/', auditor_obtener_peticion_remesa_vu, name='obtener-peticion-remesa-vu'),
path('auditor/obtener-respuesta/remesa-vu/', auditor_obtener_respuesta_remesa_vu, name='obtener-respuesta-remesa-vu'),
path('auditor/obtener-peticion/partidas-vu/', auditor_obtener_peticion_partidas_vu, name='obtener-peticion-partidas-vu'),
path('auditor/obtener-respuesta/partidas-vu/', auditor_obtener_respuesta_partidas_vu, name='obtener-respuesta-partidas-vu'),
path('auditor/obtener-peticion/acuse-vu/', auditor_obtener_peticion_acuse_vu, name='obtener-peticion-acuse-vu'),
path('auditor/obtener-respuesta/acuse-vu/', auditor_obtener_respuesta_acuse_vu, name='obtener-respuesta-acuse-vu'),
path('auditor/obtener-peticion/cove-vu/', auditor_obtener_peticion_cove_vu, name='obtener-peticion-cove-vu'),
path('auditor/obtener-respuesta/cove-vu/', auditor_obtener_respuesta_cove_vu, name='obtener-respuesta-cove-vu'),
path('auditor/obtener-peticion/acuse-cove-vu/', auditor_obtener_peticion_acuse_cove_vu, name='obtener-peticion-acuse-cove-vu'),
path('auditor/obtener-respuesta/acuse-cove-vu/', auditor_obtener_respuesta_acuse_cove_vu, name='obtener-respuesta-acuse-cove-vu'),
path('auditor/obtener-peticion/edocument-vu/', auditor_obtener_peticion_edocument_vu, name='obtener-peticion-edocument-vu'),
path('auditor/obtener-respuesta/edocument-vu/', auditor_obtener_respuesta_edocument_vu, name='obtener-respuesta-edocument-vu'),
] ]

View File

@@ -57,6 +57,9 @@ try:
except ImportError: except ImportError:
RAR_SUPPORT = False RAR_SUPPORT = False
# Importar tarea de procesamiento de pedimento (Celery)
from api.customs.tasks.microservice import procesar_pedimento_completo_individual
def get_available_extractors(): def get_available_extractors():
""" """
Devuelve lista de extractores disponibles en orden de preferencia Devuelve lista de extractores disponibles en orden de preferencia
@@ -369,6 +372,25 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
] ]
} }
@action(detail=True, methods=['post'], url_path='procesar-completo')
def procesar_completo(self, request, pk=None):
"""
Acción para disparar el procesamiento completo de un pedimento existente.
Dispara la tarea `procesar_pedimento_completo_individual` de forma asíncrona
y devuelve el `task_id`.
"""
pedimento = self.get_object()
try:
# Usar el nombre del servicio de Docker Compose en lugar de localhost
task = procesar_pedimento_completo_individual.delay(pedimento.id, pedimento.organizacion.id)
# Verificar si la respuesta fue exitosa
if task.id:
return Response({"status": "Recurso creado exitosamente en API", "task_id": task.id}, status=status.HTTP_202_ACCEPTED)
else:
return Response({"status": "Servicio API respondió con error", "task_id": 0}, status=status.HTTP_202_ACCEPTED)
except Exception as e:
return Response({"error": f"Error inesperado al llamar al servicio API: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=False, methods=['post'], url_path='bulk-delete') @action(detail=False, methods=['post'], url_path='bulk-delete')
def bulk_delete(self, request): def bulk_delete(self, request):
""" """
@@ -976,35 +998,41 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
temp_dir = None temp_dir = None
# Obtener DocumentType ANTES de la transacción atómica # Obtener DocumentType ANTES de la transacción atómica
print("Intentando obtener o crear DocumentType...") # print("Intentando obtener o crear DocumentType...")
try: try:
# Primero intentar obtener si ya existe # Primero intentar obtener si ya existe
try: try:
document_type = DocumentType.objects.get(nombre="Pedimento") document_type = DocumentType.objects.get(nombre="Pedimento")
print(f"DocumentType obtenido existente: {document_type.nombre} (ID: {document_type.id})") # print(f"DocumentType obtenido existente: {document_type.nombre} (ID: {document_type.id})")
except DocumentType.DoesNotExist: except DocumentType.DoesNotExist:
# Si no existe, crear uno nuevo # Si no existe, crear uno nuevo
document_type = DocumentType.objects.create( document_type = DocumentType.objects.create(
nombre="Pedimento", nombre="Pedimento",
descripcion="Documento de pedimento" descripcion="Documento de pedimento"
) )
print(f"DocumentType creado nuevo: {document_type.nombre} (ID: {document_type.id})") # print(f"DocumentType creado nuevo: {document_type.nombre} (ID: {document_type.id})")
except Exception as e: except Exception as e:
print(f"Error al obtener/crear DocumentType: {str(e)}") # print(f"Error al obtener/crear DocumentType: {str(e)}")
# Como fallback, intentar obtener cualquier DocumentType existente # Como fallback, intentar obtener cualquier DocumentType existente
try: try:
# document_type = DocumentType.objects.first()
# if document_type:
# print(f"Usando DocumentType existente como fallback: {document_type.nombre} (ID: {document_type.id})")
# else:
# print("No hay DocumentType disponible")
# return Response(
# {"error": "No se pudo configurar el tipo de documento y no hay tipos existentes"},
# status=status.HTTP_500_INTERNAL_SERVER_ERROR
# )
document_type = DocumentType.objects.first() document_type = DocumentType.objects.first()
if document_type: if not document_type:
print(f"Usando DocumentType existente como fallback: {document_type.nombre} (ID: {document_type.id})")
else:
print("No hay DocumentType disponible")
return Response( return Response(
{"error": "No se pudo configurar el tipo de documento y no hay tipos existentes"}, {"error": "No se pudo configurar el tipo de documento y no hay tipos existentes"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR status=status.HTTP_500_INTERNAL_SERVER_ERROR
) )
except Exception as fallback_error: except Exception as fallback_error:
print(f"Error en fallback: {str(fallback_error)}") # print(f"Error en fallback: {str(fallback_error)}")
return Response( return Response(
{ {
"tieneError": True, "tieneError": True,
@@ -1014,30 +1042,30 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
) )
try: try:
print("Iniciando transacción atómica...") # print("Iniciando transacción atómica...")
with transaction.atomic(): with transaction.atomic():
# Crear directorio temporal # Crear directorio temporal
temp_dir = tempfile.mkdtemp() temp_dir = tempfile.mkdtemp()
print(f"Directorio temporal creado: {temp_dir}") # print(f"Directorio temporal creado: {temp_dir}")
# Procesar cada archivo enviado # Procesar cada archivo enviado
for idx, archivo in enumerate(archivos): for idx, archivo in enumerate(archivos):
archivo_name = archivo.name.lower() archivo_name = archivo.name.lower()
print(f"Procesando archivo {idx + 1}/{len(archivos)}: {archivo_name}") # print(f"Procesando archivo {idx + 1}/{len(archivos)}: {archivo_name}")
# Crear subdirectorio para cada archivo usando el nombre del archivo sin extensión # Crear subdirectorio para cada archivo usando el nombre del archivo sin extensión
archivo_name_sin_extension = os.path.splitext(archivo.name)[0] archivo_name_sin_extension = os.path.splitext(archivo.name)[0]
sub_dir = os.path.join(temp_dir, archivo_name_sin_extension) sub_dir = os.path.join(temp_dir, archivo_name_sin_extension)
os.makedirs(sub_dir, exist_ok=True) os.makedirs(sub_dir, exist_ok=True)
print(f"Subdirectorio creado: {sub_dir}") # print(f"Subdirectorio creado: {sub_dir}")
if archivo_name.endswith('.zip'): if archivo_name.endswith('.zip'):
# Manejar archivo ZIP # Manejar archivo ZIP
print("Es un archivo ZIP") # print("Es un archivo ZIP")
try: try:
with zipfile.ZipFile(archivo, 'r') as zip_ref: with zipfile.ZipFile(archivo, 'r') as zip_ref:
zip_ref.extractall(sub_dir) zip_ref.extractall(sub_dir)
print("Archivo ZIP extraído exitosamente") # print("Archivo ZIP extraído exitosamente")
except zipfile.BadZipFile as e: except zipfile.BadZipFile as e:
return Response( return Response(
{ {
@@ -1063,7 +1091,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
f.write(chunk) f.write(chunk)
try: try:
extract_rar_to_dir(archivo_temp_path, sub_dir) extract_rar_to_dir(archivo_temp_path, sub_dir)
print(f"Archivo RAR {archivo.name} extraído en {sub_dir}") # print(f"Archivo RAR {archivo.name} extraído en {sub_dir}")
except Exception as e: except Exception as e:
error_msg = str(e) error_msg = str(e)
help_msg = "Instale 'unrar' o 'p7zip' (7z) y asegúrese de que estén en PATH, o instale y configure 'rarfile' con un backend." help_msg = "Instale 'unrar' o 'p7zip' (7z) y asegúrese de que estén en PATH, o instale y configure 'rarfile' con un backend."
@@ -1081,21 +1109,21 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
with open(archivo_path, 'wb') as f: with open(archivo_path, 'wb') as f:
for chunk in archivo.chunks(): for chunk in archivo.chunks():
f.write(chunk) f.write(chunk)
print(f"Archivo individual {archivo.name} guardado en sub_dir:", archivo_path) # print(f"Archivo individual {archivo.name} guardado en sub_dir:", archivo_path)
# Recorrer todos los archivos extraídos o el directorio # Recorrer todos los archivos extraídos o el directorio
print("Iniciando recorrido de archivos...") print("Iniciando recorrido de archivos...")
for root, dirs, files in os.walk(temp_dir): for root, dirs, files in os.walk(temp_dir):
print(f"Revisando directorio: {root}") # print(f"Revisando directorio: {root}")
print(f"Archivos encontrados: {files}") # print(f"Archivos encontrados: {files}")
for file_name in files: for file_name in files:
print(f"Procesando archivo: {file_name}") # print(f"Procesando archivo: {file_name}")
file_path = os.path.join(root, file_name) file_path = os.path.join(root, file_name)
# Obtener la ruta relativa para determinar la estructura de carpetas # Obtener la ruta relativa para determinar la estructura de carpetas
relative_path = os.path.relpath(file_path, temp_dir) relative_path = os.path.relpath(file_path, temp_dir)
print(f"Ruta relativa: {relative_path}") # print(f"Ruta relativa: {relative_path}")
# Determinar si el archivo está en una carpeta que sigue la nomenclatura # Determinar si el archivo está en una carpeta que sigue la nomenclatura
folder_name = None folder_name = None
@@ -1107,14 +1135,14 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
# El archivo está en la raíz, usar el nombre del archivo sin extensión # El archivo está en la raíz, usar el nombre del archivo sin extensión
folder_name = os.path.splitext(file_name)[0] folder_name = os.path.splitext(file_name)[0]
print(f"Folder name para validación: {folder_name}") # print(f"Folder name para validación: {folder_name}")
# Validar nomenclatura # Validar nomenclatura
match = nomenclatura_pattern.match(folder_name) match = nomenclatura_pattern.match(folder_name)
match_sin_anio = nomenclatura_pattern_sin_anio.match(folder_name) match_sin_anio = nomenclatura_pattern_sin_anio.match(folder_name)
if not match and not match_sin_anio: if not match and not match_sin_anio:
print(f"Nomenclatura inválida: {folder_name}") # print(f"Nomenclatura inválida: {folder_name}")
# Determinar el archivo original basado en el subdirectorio # Determinar el archivo original basado en el subdirectorio
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
failed_files.append({ failed_files.append({
@@ -1126,16 +1154,16 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
if match: if match:
print(f"Nomenclatura válida: {folder_name}") # print(f"Nomenclatura válida: {folder_name}")
anio, aduana, patente, pedimento_num = match.groups() anio, aduana, patente, pedimento_num = match.groups()
print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}") # print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
# Formato original: anio-aduana-patente-pedimento # Formato original: anio-aduana-patente-pedimento
# Crear fecha_pago basada en el año # Crear fecha_pago basada en el año
try: try:
# Convertir año de 2 dígitos a 4 dígitos # Convertir año de 2 dígitos a 4 dígitos
anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio) anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio)
fecha_pago = datetime(anio_completo, 1, 1).date() fecha_pago = datetime(anio_completo, 1, 1).date()
print(f"Fecha de pago calculada: {fecha_pago}") # print(f"Fecha de pago calculada: {fecha_pago}")
except ValueError: except ValueError:
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
failed_files.append({ failed_files.append({
@@ -1147,11 +1175,11 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
elif match_sin_anio: elif match_sin_anio:
print(f"Nomenclatura válida sin año: {folder_name}") # print(f"Nomenclatura válida sin año: {folder_name}")
# Formato sin año: aduana-patente-pedimento # Formato sin año: aduana-patente-pedimento
aduana, patente, pedimento_num = match_sin_anio.groups() aduana, patente, pedimento_num = match_sin_anio.groups()
print(f"Extraído - Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}") # print(f"Extraído - Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}")
# Obtener el primer dígito del pedimento # Obtener el primer dígito del pedimento
primer_digito_pedimento = int(pedimento_num[0]) if pedimento_num else 0 primer_digito_pedimento = int(pedimento_num[0]) if pedimento_num else 0
@@ -1176,12 +1204,12 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
# Crear fecha de pago (primer día del año) # Crear fecha de pago (primer día del año)
fecha_pago = datetime(año_final , 1, 1).date() fecha_pago = datetime(año_final , 1, 1).date()
print(f"Fecha de pago (año actual) calculada: {fecha_pago}") # print(f"Fecha de pago (año actual) calculada: {fecha_pago}")
# Generar pedimento_app # Generar pedimento_app
pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}" pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}"
print(f"Pedimento_app generado: {pedimento_app}") # print(f"Pedimento_app generado: {pedimento_app}")
print(f"Buscando pedimento existente con pedimento_app: {pedimento_app} y organización ID: {organizacion.id}") # print(f"Buscando pedimento existente con pedimento_app: {pedimento_app} y organización ID: {organizacion.id}")
# Verificar si el pedimento ya existe # Verificar si el pedimento ya existe
existing_pedimento = Pedimento.objects.filter( existing_pedimento = Pedimento.objects.filter(
pedimento=int(pedimento_num), pedimento=int(pedimento_num),
@@ -1189,18 +1217,18 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
organizacion=organizacion organizacion=organizacion
).first() ).first()
print(f"Pedimento existente: {existing_pedimento is not None}") # print(f"Pedimento existente: {existing_pedimento is not None}")
if not existing_pedimento: if not existing_pedimento:
print("📝 Pedimento no existe, creando nuevo...") # print("📝 Pedimento no existe, creando nuevo...")
# Crear nuevo pedimento # Crear nuevo pedimento
try: try:
print("🔄 Iniciando creación de pedimento...") # print("🔄 Iniciando creación de pedimento...")
importador = None importador = None
if contribuyente: if contribuyente:
# Obtener o crear el importador # Obtener o crear el importador
print(f"🏢 Buscando/creando importador con RFC: {contribuyente}") # print(f"🏢 Buscando/creando importador con RFC: {contribuyente}")
importador, created = Importador.objects.get_or_create( importador, created = Importador.objects.get_or_create(
rfc=contribuyente, rfc=contribuyente,
defaults={ defaults={
@@ -1208,10 +1236,10 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
'organizacion': organizacion 'organizacion': organizacion
} }
) )
if created: # if created:
print(f"✅ Importador creado: {importador.rfc} - {importador.nombre}") # print(f"✅ Importador creado: {importador.rfc} - {importador.nombre}")
else: # else:
print(f"♻️ Importador existente: {importador.rfc} - {importador.nombre}") # print(f"♻️ Importador existente: {importador.rfc} - {importador.nombre}")
if tipo_operacion_input: if tipo_operacion_input:
tipo_operacion_input = TipoOperacion.objects.get(id=tipo_operacion_input) tipo_operacion_input = TipoOperacion.objects.get(id=tipo_operacion_input)
@@ -1232,7 +1260,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
clave_pedimento=clave_pedimento_input if clave_pedimento_input else "A1" # Valor por defecto clave_pedimento=clave_pedimento_input if clave_pedimento_input else "A1" # Valor por defecto
) )
print(f"✅ Pedimento creado exitosamente: ID {pedimento.id}, pedimento_app: {pedimento_app}") # print(f"✅ Pedimento creado exitosamente: ID {pedimento.id}, pedimento_app: {pedimento_app}")
created_pedimentos.append({ created_pedimentos.append({
"id": str(pedimento.id), "id": str(pedimento.id),
@@ -1242,7 +1270,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
}) })
except Exception as e: except Exception as e:
print(f"❌ Error al crear pedimento: {str(e)}") # print(f"❌ Error al crear pedimento: {str(e)}")
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
failed_files.append({ failed_files.append({
"file": relative_path, "file": relative_path,
@@ -1251,7 +1279,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
}) })
continue continue
else: else:
print(f"♻️ Usando pedimento existente: ID {existing_pedimento.id}") # print(f"♻️ Usando pedimento existente: ID {existing_pedimento.id}")
# Usar pedimento existente # Usar pedimento existente
# # Actualizar Importador # # Actualizar Importador
if contribuyente: if contribuyente:
@@ -1325,15 +1353,15 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
pedimento = existing_pedimento pedimento = existing_pedimento
print(f"🔄 Iniciando creación de documento para pedimento ID: {pedimento.id}") # print(f"🔄 Iniciando creación de documento para pedimento ID: {pedimento.id}")
# Crear documento asociado al pedimento # Crear documento asociado al pedimento
try: try:
print("📖 Leyendo archivo desde directorio temporal...") # print("📖 Leyendo archivo desde directorio temporal...")
# Leer el archivo desde el directorio temporal # Leer el archivo desde el directorio temporal
with open(file_path, 'rb') as f: with open(file_path, 'rb') as f:
file_content = f.read() file_content = f.read()
print(f"📄 Archivo leído: {len(file_content)} bytes") # print(f"📄 Archivo leído: {len(file_content)} bytes")
# Crear ContentFile que Django puede manejar correctamente # Crear ContentFile que Django puede manejar correctamente
django_file = ContentFile(file_content, name=file_name) django_file = ContentFile(file_content, name=file_name)
@@ -1369,7 +1397,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
descripcion='Transmitido por la app de escritorio' descripcion='Transmitido por la app de escritorio'
) )
print(f"Creando documento para archivo: {file_name}") # print(f"Creando documento para archivo: {file_name}")
# Crear documento - Django automáticamente guardará el archivo en media/documents/ # Crear documento - Django automáticamente guardará el archivo en media/documents/
document = Document.objects.create( document = Document.objects.create(
organizacion=organizacion, organizacion=organizacion,
@@ -1380,13 +1408,13 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
size=len(file_content), size=len(file_content),
extension=os.path.splitext(file_name)[1].lower().lstrip('.') extension=os.path.splitext(file_name)[1].lower().lstrip('.')
) )
print(f"Documento creado exitosamente: {document.id}") # print(f"Documento creado exitosamente: {document.id}")
documents_created += 1 documents_created += 1
print(f"📊 Total documentos creados hasta ahora: {documents_created}") # print(f"📊 Total documentos creados hasta ahora: {documents_created}")
except Exception as e: except Exception as e:
print(f"❌ Error al crear documento: {str(e)}") # print(f"❌ Error al crear documento: {str(e)}")
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
failed_files.append({ failed_files.append({
"file": relative_path, "file": relative_path,
@@ -1395,7 +1423,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
}) })
continue continue
print(f"🏁 Procesamiento completado. Archivos procesados en este directorio.") # print(f"🏁 Procesamiento completado. Archivos procesados en este directorio.")
except Exception as e: except Exception as e:
return Response( return Response(
{ "tieneError": True, { "tieneError": True,

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,8 @@ from .views import (DocumentViewSet
, DocumentTypeView , DocumentTypeView
, ExpedienteZipDownloadView , ExpedienteZipDownloadView
, MultiPedimentoZipDownloadView , MultiPedimentoZipDownloadView
, PedimentoDocumentViewSet) , PedimentoDocumentViewSet
, TriggerPedimentoCompletoView)
# Create a router and register your viewsets with it # Create a router and register your viewsets with it
@@ -35,5 +36,6 @@ urlpatterns = [
path('documents/expediente-zip/', ExpedienteZipDownloadView.as_view(), name='expediente-zip-download'), path('documents/expediente-zip/', ExpedienteZipDownloadView.as_view(), name='expediente-zip-download'),
path('documents/multi-pedimento-zip/', MultiPedimentoZipDownloadView.as_view(), name='multi-pedimento-zip-download'), path('documents/multi-pedimento-zip/', MultiPedimentoZipDownloadView.as_view(), name='multi-pedimento-zip-download'),
path('pedimento-documents/', PedimentoDocumentViewSet.as_view({'get': 'list'}), name='pedimento-document-list'), path('pedimento-documents/', PedimentoDocumentViewSet.as_view({'get': 'list'}), name='pedimento-document-list'),
path('microservice/pedimento-completo/', TriggerPedimentoCompletoView.as_view(), name='trigger-pedimento-completo'),
path('', include(router.urls)), path('', include(router.urls)),
] ]

View File

@@ -14,6 +14,7 @@ from rest_framework.exceptions import ValidationError
from .serializers import DocumentSerializer, FuenteSerializer, DocumentTypeSerializer from .serializers import DocumentSerializer, FuenteSerializer, DocumentTypeSerializer
from .models import Document, Fuente, DocumentType from .models import Document, Fuente, DocumentType
from ..customs.models import Pedimento from ..customs.models import Pedimento
from ..vucem.models import CredencialesImportador
from api.organization.models import UsoAlmacenamiento from api.organization.models import UsoAlmacenamiento
from io import BytesIO from io import BytesIO
import zipfile import zipfile
@@ -35,9 +36,91 @@ logger = logging.getLogger(__name__)
import os import os
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.conf import settings
import requests
import re
from mixins.filtrado_organizacion import DocumentosFiltradosMixin 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): class CustomPagination(PageNumberPagination):
""" """
@@ -144,6 +227,39 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
"codigo": "storage_limit_exceeded" "codigo": "storage_limit_exceeded"
}, code=status.HTTP_400_BAD_REQUEST) }, code=status.HTTP_400_BAD_REQUEST)
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 # Guardar documento y actualizar espacio atómicamente
documento = serializer.save( documento = serializer.save(
organizacion=organizacion, organizacion=organizacion,
@@ -833,6 +949,113 @@ class PedimentoDocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
queryset = queryset.filter(pedimento__pedimento_app=pedimento_numero) queryset = queryset.filter(pedimento__pedimento_app=pedimento_numero)
return queryset 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)