feature/implementacion de hub en EFC

This commit is contained in:
2026-06-08 07:19:01 -06:00
parent a9931d2838
commit e1716d65a7
20 changed files with 3749 additions and 649 deletions

View File

@@ -1,4 +1,5 @@
import os
import tempfile
from datetime import datetime
from django.db import models
from celery import shared_task, group
@@ -7,6 +8,7 @@ from core.utils import xml_controller
import requests
from core.utils import xml_remesas_controller
from core.redis_events import publish_task_event
from api.utils.storage_service import storage_service
import logging
logger = logging.getLogger(__name__)
@@ -144,7 +146,7 @@ def auditar_procesamiento_remesa_por_pedimento(pedimento_id):
xml_data = extraer_coves(pedimento)
if xml_data:
for remesa in xml_data:
numero_cove = remesa.get('remesaSA')
numero_cove = remesa.get('comprobanteVE')
cove, creado = Cove.objects.get_or_create(
pedimento=pedimento,
numero_cove=numero_cove,
@@ -533,6 +535,903 @@ def auditar_acuse_por_pedimento(pedimento_id):
except Exception as e:
return {'success': False, 'error': str(e), 'pedimento_id': str(pedimento_id)}
def _leer_xml_documento(documento):
"""Lee el contenido de un documento desde MinIO (o filesystem de fallback)."""
ruta = str(documento.archivo)
with tempfile.NamedTemporaryFile(delete=False, suffix='.xml') as tmp:
tmp_path = tmp.name
try:
success = storage_service.download_file(ruta, tmp_path)
if not success:
logger.error(f"storage_service.download_file falló para {ruta}")
return None
with open(tmp_path, 'r', encoding='utf-8', errors='ignore') as f:
return f.read()
except Exception as exc:
logger.error(f"Error leyendo documento {ruta}: {exc}")
return None
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
def _leer_xml_pedimento_completo(pedimento):
"""Lee el XML del pedimento completo (document_type=2) vía storage_service."""
pc = pedimento.documents.filter(document_type__id=2).first()
if not pc:
return None
return _leer_xml_documento(pc)
# ──────────────────────────────────────────────────────────────────────────────
# Auditorías de integridad: comprueban que los registros en DB coincidan con
# lo que declara el XML del pedimento completo o la remesa.
# Son de solo lectura — no crean ni modifican registros de negocio.
# ──────────────────────────────────────────────────────────────────────────────
@shared_task(bind=True)
def auditar_integridad_partidas(self, organizacion_id, user_id=None):
"""
Compara pedimento.numero_partidas (extraído del XML) vs partidas.count() en DB.
Detecta pedimentos donde faltan registros de Partida sin crear ninguno.
"""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total_pedimentos = pedimentos.count()
publish_task_event(task_id, "processing", f"Auditando integridad de partidas: {total_pedimentos} pedimentos", progress=0)
completados = []
sin_datos_xml = []
con_faltantes = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
num_esperadas = pedimento.numero_partidas
num_en_db = pedimento.partidas.count()
if not num_esperadas or num_esperadas <= 0:
sin_datos_xml.append({
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'razon': f'numero_partidas no definido ({num_esperadas})',
})
continue
if num_en_db >= num_esperadas:
modificar_estado_procesamiento(pedimento, servicio_id=4, nuevo_estado=3)
completados.append(str(pedimento.id))
else:
modificar_estado_procesamiento(pedimento, servicio_id=4, nuevo_estado=4)
con_faltantes.append({
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'esperadas': num_esperadas,
'en_db': num_en_db,
'faltantes': num_esperadas - num_en_db,
})
except Exception as exc:
errores.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error auditando integridad de partidas para pedimento {pedimento.id}: {exc}")
if total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Auditando partidas: {idx + 1}/{total_pedimentos}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'integridad_partidas',
'total_pedimentos': total_pedimentos,
'completados': len(completados),
'sin_datos_xml': len(sin_datos_xml),
'con_faltantes': len(con_faltantes),
'con_errores': len(errores),
'detalle_faltantes': con_faltantes,
'detalle_sin_datos': sin_datos_xml,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Auditoría de integridad de partidas completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Integridad de Partidas", resultado)
return resultado
@shared_task
def auditar_integridad_partidas_por_pedimento(pedimento_id):
"""Versión por pedimento de auditar_integridad_partidas."""
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
num_esperadas = pedimento.numero_partidas
num_en_db = pedimento.partidas.count()
if not num_esperadas or num_esperadas <= 0:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'sin_datos_xml',
'mensaje': f'numero_partidas no definido ({num_esperadas})',
}
if num_en_db >= num_esperadas:
modificar_estado_procesamiento(pedimento, servicio_id=4, nuevo_estado=3)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'completado',
'esperadas': num_esperadas,
'en_db': num_en_db,
}
modificar_estado_procesamiento(pedimento, servicio_id=4, nuevo_estado=4)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'incompleto',
'esperadas': num_esperadas,
'en_db': num_en_db,
'faltantes': num_esperadas - num_en_db,
}
except Pedimento.DoesNotExist:
return {'error': f'Pedimento {pedimento_id} no encontrado'}
except Exception as exc:
return {'error': str(exc), 'pedimento_id': str(pedimento_id)}
@shared_task(bind=True)
def auditar_integridad_edocuments(self, organizacion_id, user_id=None):
"""
Compara la lista de e-documentos (identificadores_ed) del XML del pedimento completo
vs los EDocuments registrados en DB. Detecta registros faltantes sin crear nada.
"""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total_pedimentos = pedimentos.count()
publish_task_event(task_id, "processing", f"Auditando integridad de edocuments: {total_pedimentos} pedimentos", progress=0)
completados = []
sin_xml = []
con_faltantes = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
xml_content = _leer_xml_pedimento_completo(pedimento)
if not xml_content:
sin_xml.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento})
continue
xml_data = xml_controller.extract_data(xml_content)
edocs_xml = xml_data.get('identificadores_ed', []) or []
numeros_xml = {e.get('complemento1') for e in edocs_xml if e.get('complemento1')}
numeros_db = set(pedimento.documentos.values_list('numero_edocument', flat=True))
faltantes_en_db = numeros_xml - numeros_db
if not faltantes_en_db:
modificar_estado_procesamiento(pedimento, servicio_id=7, nuevo_estado=3)
completados.append(str(pedimento.id))
else:
modificar_estado_procesamiento(pedimento, servicio_id=7, nuevo_estado=4)
con_faltantes.append({
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'esperados_xml': len(numeros_xml),
'en_db': len(numeros_db),
'faltantes_en_db': sorted(faltantes_en_db),
})
except Exception as exc:
errores.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error auditando integridad de edocuments para pedimento {pedimento.id}: {exc}")
if total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Auditando edocuments: {idx + 1}/{total_pedimentos}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'integridad_edocuments',
'total_pedimentos': total_pedimentos,
'completados': len(completados),
'sin_xml': len(sin_xml),
'con_faltantes': len(con_faltantes),
'con_errores': len(errores),
'detalle_faltantes': con_faltantes,
'detalle_sin_xml': sin_xml,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Auditoría de integridad de edocuments completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Integridad de EDocuments", resultado)
return resultado
@shared_task
def auditar_integridad_edocuments_por_pedimento(pedimento_id):
"""Versión por pedimento de auditar_integridad_edocuments."""
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
xml_content = _leer_xml_pedimento_completo(pedimento)
if not xml_content:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'sin_xml',
'mensaje': 'No hay pedimento completo (document_type=2) descargado',
}
xml_data = xml_controller.extract_data(xml_content)
edocs_xml = xml_data.get('identificadores_ed', []) or []
numeros_xml = {e.get('complemento1') for e in edocs_xml if e.get('complemento1')}
numeros_db = set(pedimento.documentos.values_list('numero_edocument', flat=True))
faltantes_en_db = numeros_xml - numeros_db
if not faltantes_en_db:
modificar_estado_procesamiento(pedimento, servicio_id=7, nuevo_estado=3)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'completado',
'esperados_xml': len(numeros_xml),
'en_db': len(numeros_db),
}
modificar_estado_procesamiento(pedimento, servicio_id=7, nuevo_estado=4)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'incompleto',
'esperados_xml': len(numeros_xml),
'en_db': len(numeros_db),
'faltantes_en_db': sorted(faltantes_en_db),
}
except Pedimento.DoesNotExist:
return {'error': f'Pedimento {pedimento_id} no encontrado'}
except Exception as exc:
return {'error': str(exc), 'pedimento_id': str(pedimento_id)}
@shared_task(bind=True)
def auditar_integridad_coves(self, organizacion_id, user_id=None):
"""Verifica que los COVEs listados en el XML del pedimento completo existan en DB (nivel org)."""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total_pedimentos = pedimentos.count()
publish_task_event(task_id, "processing", f"Auditando integridad de COVEs (PC XML): {total_pedimentos} pedimentos", progress=0)
completados = []
sin_xml = []
con_faltantes = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
xml_content = _leer_xml_pedimento_completo(pedimento)
if not xml_content:
sin_xml.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento})
continue
xml_data = xml_controller.extract_data(xml_content)
coves_xml = set(xml_data.get('coves', []) or [])
coves_db = set(pedimento.coves.values_list('numero_cove', flat=True))
faltantes = coves_xml - coves_db
if faltantes:
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=4)
con_faltantes.append({
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'coves_xml': len(coves_xml),
'coves_db': len(coves_db),
'faltantes': sorted(faltantes),
})
else:
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=3)
completados.append(str(pedimento.id))
except Exception as exc:
errores.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error auditando integridad de COVEs para pedimento {pedimento.id}: {exc}")
if total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Auditando COVEs: {idx + 1}/{total_pedimentos}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'integridad_coves',
'total_pedimentos': total_pedimentos,
'completados': len(completados),
'sin_xml': len(sin_xml),
'con_faltantes': len(con_faltantes),
'con_errores': len(errores),
'detalle_faltantes': con_faltantes,
'detalle_sin_xml': sin_xml,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Auditoría de integridad de COVEs completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Integridad de COVEs", resultado)
return resultado
@shared_task(bind=True)
def auditar_integridad_remesa(self, organizacion_id, user_id=None):
"""Verifica que los COVEs declarados en el XML de remesa existan en DB (nivel org)."""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id).filter(remesas=True)
total_pedimentos = pedimentos.count()
publish_task_event(task_id, "processing", f"Auditando integridad de remesas: {total_pedimentos} pedimentos con remesas", progress=0)
completados = []
sin_xml = []
con_faltantes = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
doc_remesa = pedimento.documents.filter(document_type=3).first()
if not doc_remesa:
sin_xml.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'razon': 'Sin documento remesa (type=3)'})
continue
remesa_xml = _leer_xml_documento(doc_remesa)
if not remesa_xml:
sin_xml.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'razon': 'No se pudo leer el XML de remesa'})
continue
remesa_data = xml_remesas_controller.extract_remesas(remesa_xml)
coves_de_remesa = {r.get('comprobanteVE') for r in remesa_data if r.get('comprobanteVE')}
coves_db = set(pedimento.coves.values_list('numero_cove', flat=True))
faltantes = coves_de_remesa - coves_db
if faltantes:
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=4)
con_faltantes.append({
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'total_en_remesa': len(coves_de_remesa),
'en_db': len(coves_db),
'faltantes': sorted(faltantes),
})
else:
completados.append(str(pedimento.id))
except Exception as exc:
errores.append({'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error auditando integridad de remesa para pedimento {pedimento.id}: {exc}")
if total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Auditando remesas: {idx + 1}/{total_pedimentos}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'integridad_remesa',
'total_pedimentos': total_pedimentos,
'completados': len(completados),
'sin_xml': len(sin_xml),
'con_faltantes': len(con_faltantes),
'con_errores': len(errores),
'detalle_faltantes': con_faltantes,
'detalle_sin_xml': sin_xml,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Auditoría de integridad de remesas completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Integridad de Remesas", resultado)
return resultado
@shared_task
def auditar_integridad_coves_por_pedimento(pedimento_id):
"""Verifica que los COVEs del PC XML existan en DB para un pedimento específico."""
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
xml_content = _leer_xml_pedimento_completo(pedimento)
if not xml_content:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'sin_xml',
'mensaje': 'No hay pedimento completo (document_type=2) descargado',
}
xml_data = xml_controller.extract_data(xml_content)
coves_xml = set(xml_data.get('coves', []) or [])
coves_db = set(pedimento.coves.values_list('numero_cove', flat=True))
faltantes = coves_xml - coves_db
if not faltantes:
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=3)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'completado',
'coves_xml': len(coves_xml),
'coves_db': len(coves_db),
}
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=4)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'incompleto',
'coves_xml': len(coves_xml),
'coves_db': len(coves_db),
'faltantes': sorted(faltantes),
}
except Pedimento.DoesNotExist:
return {'error': f'Pedimento {pedimento_id} no encontrado'}
except Exception as exc:
return {'error': str(exc), 'pedimento_id': str(pedimento_id)}
@shared_task
def auditar_integridad_remesa_por_pedimento(pedimento_id):
"""Verifica que los COVEs del XML de remesa existan en DB para un pedimento específico."""
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
if not pedimento.remesas:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'sin_remesas',
'mensaje': 'Este pedimento no tiene remesas',
}
doc_remesa = pedimento.documents.filter(document_type=3).first()
if not doc_remesa:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'sin_xml',
'mensaje': 'No hay documento de remesa (document_type=3) descargado',
}
remesa_xml = _leer_xml_documento(doc_remesa)
if not remesa_xml:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'sin_xml',
'mensaje': 'No se pudo leer el archivo de remesa',
}
remesa_data = xml_remesas_controller.extract_remesas(remesa_xml)
coves_de_remesa = {r.get('comprobanteVE') for r in remesa_data if r.get('comprobanteVE')}
coves_db = set(pedimento.coves.values_list('numero_cove', flat=True))
faltantes = coves_de_remesa - coves_db
if not faltantes:
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'completado',
'total_en_remesa': len(coves_de_remesa),
'coves_db': len(coves_db),
}
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=4)
return {
'pedimento_id': str(pedimento_id),
'pedimento': pedimento.pedimento,
'estado': 'incompleto',
'total_en_remesa': len(coves_de_remesa),
'coves_db': len(coves_db),
'faltantes': sorted(faltantes),
}
except Pedimento.DoesNotExist:
return {'error': f'Pedimento {pedimento_id} no encontrado'}
except Exception as exc:
return {'error': str(exc), 'pedimento_id': str(pedimento_id)}
# ──────────────────────────────────────────────────────────────────────────────
# Correcciones de integridad: crean registros faltantes en DB y disparan
# procesamiento VUCEM. Helpers sincrónicos + tasks Celery para nivel org.
# ──────────────────────────────────────────────────────────────────────────────
def _corregir_integridad_partidas_pedimento(pedimento):
"""Crea Partida records faltantes y dispara procesar_partida_individual."""
from api.customs.models import Partida
from api.customs.tasks.microservice import procesar_partida_individual
num_esperadas = pedimento.numero_partidas
if not num_esperadas or num_esperadas <= 0:
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'sin_datos',
'razon': f'numero_partidas no definido ({num_esperadas})',
}
num_en_db = pedimento.partidas.count()
creadas = 0
for i in range(1, num_esperadas + 1):
_, created = Partida.objects.get_or_create(
pedimento=pedimento,
numero_partida=i,
defaults={'organizacion_id': pedimento.organizacion_id},
)
if created:
creadas += 1
if creadas > 0:
procesar_partida_individual.apply_async(args=[str(pedimento.id), str(pedimento.organizacion.id)])
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'corregido',
'partidas_en_db_antes': num_en_db,
'esperadas': num_esperadas,
'creadas': creadas,
'procesamiento_iniciado': creadas > 0,
}
def _corregir_integridad_edocuments_pedimento(pedimento):
"""Crea EDocument records faltantes desde el XML del pedimento completo y dispara procesamiento."""
from api.customs.tasks.microservice import procesar_edoc_individual
xml_content = _leer_xml_pedimento_completo(pedimento)
if not xml_content:
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'sin_xml',
}
xml_data = xml_controller.extract_data(xml_content)
edocs_xml = xml_data.get('identificadores_ed', []) or []
creados = []
for edoc in edocs_xml:
numero = edoc.get('complemento1')
if not numero:
continue
try:
_, created = EDocument.objects.get_or_create(
pedimento=pedimento,
organizacion=pedimento.organizacion,
numero_edocument=numero,
defaults={
'clave': edoc.get('clave', ''),
'descripcion': edoc.get('descripcion', ''),
},
)
if created:
creados.append(numero)
except Exception as exc:
logger.error(f"Error creando EDocument {numero} para pedimento {pedimento.id}: {exc}")
if pedimento.documentos.exists():
procesar_edoc_individual.apply_async(args=[str(pedimento.id), str(pedimento.organizacion.id)])
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'corregido',
'edocuments_creados': creados,
'procesamiento_iniciado': pedimento.documentos.exists(),
}
def _corregir_integridad_coves_pedimento(pedimento):
"""Crea COVE records faltantes del PC XML y dispara procesar_cove_individual."""
from api.customs.tasks.microservice import procesar_cove_individual
xml_content = _leer_xml_pedimento_completo(pedimento)
if not xml_content:
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'sin_xml',
}
xml_data = xml_controller.extract_data(xml_content)
coves_xml = xml_data.get('coves', []) or []
creados = []
for numero_cove in coves_xml:
try:
_, created = Cove.objects.get_or_create(
pedimento=pedimento,
organizacion=pedimento.organizacion,
numero_cove=numero_cove,
)
if created:
creados.append(numero_cove)
except Exception:
pass
coves_procesados = False
if pedimento.coves.exists():
procesar_cove_individual.apply_async(args=[str(pedimento.id), str(pedimento.organizacion.id)])
coves_procesados = True
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'corregido',
'coves_creados': creados,
'procesamiento_iniciado': coves_procesados,
}
def _corregir_integridad_remesa_pedimento(pedimento):
"""
Crea COVE records faltantes del XML de remesa y dispara procesamiento.
Si no hay XML de remesa, dispara procesar_remesa_individual para descargarlo.
"""
from api.customs.tasks.microservice import procesar_cove_individual, procesar_remesa_individual
if not pedimento.remesas:
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'sin_remesas',
'mensaje': 'Este pedimento no tiene remesas',
}
doc_remesa = pedimento.documents.filter(document_type=3).first()
if not doc_remesa:
procesar_remesa_individual.apply_async(args=[str(pedimento.id), str(pedimento.organizacion.id)])
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'remesa_iniciada',
'mensaje': 'XML de remesa no disponible — se inició la búsqueda en VUCEM',
'procesamiento_remesa_iniciado': True,
}
remesa_xml = _leer_xml_documento(doc_remesa)
if not remesa_xml:
procesar_remesa_individual.apply_async(args=[str(pedimento.id), str(pedimento.organizacion.id)])
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'remesa_iniciada',
'mensaje': 'No se pudo leer el XML de remesa — se reintentará la búsqueda en VUCEM',
'procesamiento_remesa_iniciado': True,
}
remesa_data = xml_remesas_controller.extract_remesas(remesa_xml)
creados = []
for r in remesa_data:
numero_cove = r.get('comprobanteVE')
if numero_cove:
try:
_, created = Cove.objects.get_or_create(
pedimento=pedimento,
organizacion=pedimento.organizacion,
numero_cove=numero_cove,
)
if created:
creados.append(numero_cove)
except Exception:
pass
coves_procesados = False
if pedimento.coves.exists():
procesar_cove_individual.apply_async(args=[str(pedimento.id), str(pedimento.organizacion.id)])
coves_procesados = True
return {
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento,
'estado': 'corregido',
'coves_creados': creados,
'procesamiento_coves_iniciado': coves_procesados,
}
@shared_task(bind=True)
def corregir_integridad_partidas(self, organizacion_id, user_id=None):
"""Crea Partida records faltantes en todos los pedimentos de la org y dispara procesamiento."""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total = pedimentos.count()
publish_task_event(task_id, "processing", f"Corrigiendo integridad de partidas: {total} pedimentos", progress=0)
corregidos = []
sin_datos = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
res = _corregir_integridad_partidas_pedimento(pedimento)
if res['estado'] == 'sin_datos':
sin_datos.append({'pedimento': pedimento.pedimento, 'razon': res.get('razon')})
elif res.get('creadas', 0) > 0:
corregidos.append({'pedimento': pedimento.pedimento, 'creadas': res['creadas']})
except Exception as exc:
errores.append({'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error corrigiendo partidas de pedimento {pedimento.id}: {exc}")
if total > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total) * 100)
publish_task_event(task_id, "processing", f"Corrigiendo partidas: {idx + 1}/{total}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'correccion_partidas',
'total_pedimentos': total,
'con_nuevas_partidas': len(corregidos),
'sin_datos': len(sin_datos),
'con_errores': len(errores),
'detalle_corregidos': corregidos,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Corrección de integridad de partidas completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Corrección de Partidas", resultado)
return resultado
@shared_task(bind=True)
def corregir_integridad_edocuments(self, organizacion_id, user_id=None):
"""Crea EDocument records faltantes en todos los pedimentos de la org y dispara procesamiento."""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total = pedimentos.count()
publish_task_event(task_id, "processing", f"Corrigiendo integridad de edocuments: {total} pedimentos", progress=0)
corregidos = []
sin_xml = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
res = _corregir_integridad_edocuments_pedimento(pedimento)
if res['estado'] == 'sin_xml':
sin_xml.append({'pedimento': pedimento.pedimento})
elif res.get('edocuments_creados'):
corregidos.append({'pedimento': pedimento.pedimento, 'creados': res['edocuments_creados']})
except Exception as exc:
errores.append({'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error corrigiendo edocuments de pedimento {pedimento.id}: {exc}")
if total > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total) * 100)
publish_task_event(task_id, "processing", f"Corrigiendo edocuments: {idx + 1}/{total}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'correccion_edocuments',
'total_pedimentos': total,
'con_nuevos_edocuments': len(corregidos),
'sin_xml': len(sin_xml),
'con_errores': len(errores),
'detalle_corregidos': corregidos,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Corrección de integridad de edocuments completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Corrección de EDocuments", resultado)
return resultado
@shared_task(bind=True)
def corregir_integridad_coves(self, organizacion_id, user_id=None):
"""Crea COVE records faltantes (PC XML) en todos los pedimentos de la org y dispara procesamiento."""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total = pedimentos.count()
publish_task_event(task_id, "processing", f"Corrigiendo integridad de COVEs: {total} pedimentos", progress=0)
corregidos = []
sin_xml = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
res = _corregir_integridad_coves_pedimento(pedimento)
if res['estado'] == 'sin_xml':
sin_xml.append({'pedimento': pedimento.pedimento})
elif res.get('coves_creados'):
corregidos.append({'pedimento': pedimento.pedimento, 'creados': res['coves_creados']})
except Exception as exc:
errores.append({'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error corrigiendo COVEs de pedimento {pedimento.id}: {exc}")
if total > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total) * 100)
publish_task_event(task_id, "processing", f"Corrigiendo COVEs: {idx + 1}/{total}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'correccion_coves',
'total_pedimentos': total,
'con_nuevos_coves': len(corregidos),
'sin_xml': len(sin_xml),
'con_errores': len(errores),
'detalle_corregidos': corregidos,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Corrección de integridad de COVEs completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Corrección de COVEs", resultado)
return resultado
@shared_task(bind=True)
def corregir_integridad_remesa(self, organizacion_id, user_id=None):
"""Crea COVE records faltantes (remesa XML) en pedimentos con remesas y dispara procesamiento."""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id).filter(remesas=True)
total = pedimentos.count()
publish_task_event(task_id, "processing", f"Corrigiendo integridad de remesas: {total} pedimentos con remesas", progress=0)
corregidos = []
sin_xml = []
errores = []
for idx, pedimento in enumerate(pedimentos):
try:
res = _corregir_integridad_remesa_pedimento(pedimento)
if res['estado'] in ('sin_xml', 'remesa_iniciada'):
sin_xml.append({'pedimento': pedimento.pedimento, 'estado': res['estado']})
elif res.get('coves_creados'):
corregidos.append({'pedimento': pedimento.pedimento, 'creados': res['coves_creados']})
except Exception as exc:
errores.append({'pedimento': pedimento.pedimento, 'error': str(exc)})
logger.error(f"Error corrigiendo remesa de pedimento {pedimento.id}: {exc}")
if total > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total) * 100)
publish_task_event(task_id, "processing", f"Corrigiendo remesas: {idx + 1}/{total}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'correccion_remesa',
'total_pedimentos': total,
'con_nuevos_coves': len(corregidos),
'sin_xml': len(sin_xml),
'con_errores': len(errores),
'detalle_corregidos': corregidos,
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Corrección de integridad de remesas completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Corrección de Remesas", resultado)
return resultado
@shared_task
def auditar_pedimento_por_id(pedimento_id):
"""