import os import tempfile from datetime import datetime from django.db import models from celery import shared_task, group from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocument 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__) def _crear_notificacion_auditoria(user_id: str, task_id: str, label: str, resultado: dict): """Crea una Notificacion persistente cuando una tarea de auditoría masiva completa.""" try: from api.notificaciones.models import Notificacion, TipoNotificacion from api.cuser.models import CustomUser tipo, _ = TipoNotificacion.objects.get_or_create( tipo="auditoria_completada", defaults={"descripcion": "Auditoría masiva completada"}, ) usuario = CustomUser.objects.filter(id=user_id).first() if not usuario: return total = resultado.get('total_pedimentos', 0) completados = resultado.get('completados', resultado.get('procesados', 0)) pendientes = resultado.get('con_pendientes', 0) errores = resultado.get('con_errores', 0) partes = [f"Auditoría de {label} completada — {completados}/{total} pedimentos"] if pendientes: partes.append(f"{pendientes} con pendientes") if errores: partes.append(f"{errores} con errores") Notificacion.objects.create( tipo=tipo, dirigido=usuario, mensaje=", ".join(partes), datos={ "task_id": task_id, "label": label, "resultado": resultado, }, ) except Exception as exc: logger.error(f"[auditoria] Error creando notificación para tarea {task_id}: {exc}") def obtener_pedimentos(organizacion_id): return Pedimento.objects.filter(organizacion_id=organizacion_id) def extraer_coves(pedimento): remesas = pedimento.documents.filter(document_type=3).first() with open(f'./media/{remesas.archivo}', 'r') as f: xml_content = f.read() xml_data = xml_remesas_controller.extract_remesas(xml_content) return xml_data def modificar_estado_procesamiento(pedimento, servicio_id, nuevo_estado): procesamiento = ProcesamientoPedimento.objects.filter( pedimento=pedimento, servicio_id=servicio_id, organizacion=pedimento.organizacion ).first() if procesamiento: procesamiento.estado_id = nuevo_estado procesamiento.save() return True return False def auditor_descargas(pedimento, servicio, related_name, variable, mensaje): pedimento_id = pedimento.id docs = getattr(pedimento, related_name).all() print(f"pedimento: {pedimento}, servicio: {servicio}, related_name: {related_name}, variable: {variable}, mensaje: {mensaje}") logger.info(f"pedimento: {pedimento}, servicio: {servicio}, related_name: {related_name}, variable: {variable}, mensaje: {mensaje}") # Si no hay documentos, marcar como completado if not docs.exists(): proceso = modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=3) # Estado "completado" print(f"✓ Pedimento {pedimento_id} no tiene {mensaje}s para procesar.") logger.info(f"✓ Pedimento {pedimento_id} no tiene {mensaje}s para procesar.") else: all_docs = all(getattr(doc, variable) for doc in docs) if all_docs: proceso = modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=3) # Estado "completado" print(f"✓ Pedimento {pedimento_id} tiene todos sus {mensaje} descargados.") logger.info(f"✓ Pedimento {pedimento_id} tiene todos sus {mensaje} descargados.") else: proceso = modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=4) # Estado "en progreso" print(f"✗ Pedimento {pedimento_id} NO tiene todos sus {mensaje} descargados.") logger.info(f"✗ Pedimento {pedimento_id} NO tiene todos sus {mensaje} descargados.") if proceso: print(f"✓ Proceso de auditoría para pedimento {pedimento_id} completado.") logger.info(f"✓ Proceso de auditoría para pedimento {pedimento_id} completado.") else: print(f"✗ No se encontró proceso de auditoría para pedimento {pedimento_id}.") logger.info(f"✗ No se encontró proceso de auditoría para pedimento {pedimento_id}.") ## Auditar pedimentos @shared_task def auditar_procesamiento_remesa_por_pedimento(pedimento_id): """ Audita el procesamiento de remesa para un pedimento específico. Args: pedimento_id: UUID del pedimento a auditar Returns: dict: Resultado de la auditoría con detalles del procesamiento """ try: pedimento = Pedimento.objects.get(id=pedimento_id) resultado = { 'pedimento_id': str(pedimento_id), 'pedimento_numero': pedimento.pedimento, 'tiene_remesas': pedimento.remesas, 'procesamiento_creado': False, 'coves_creados': [] } if not pedimento.remesas: resultado['mensaje'] = 'El pedimento no tiene remesas para procesar' return resultado # Verificar documento tipo remesa if not pedimento.documents.filter(document_type=3).exists(): # Crear procesamiento si no existe documento de remesa procesamiento, creado = ProcesamientoPedimento.objects.get_or_create( pedimento=pedimento, servicio_id=5, # ID del servicio de remesas organizacion=pedimento.organizacion_id ) resultado['procesamiento_creado'] = creado resultado['mensaje'] = 'Procesamiento de remesa creado - documento no encontrado' else: # Procesar XML de remesas xml_data = extraer_coves(pedimento) if xml_data: for remesa in xml_data: numero_cove = remesa.get('comprobanteVE') cove, creado = Cove.objects.get_or_create( pedimento=pedimento, numero_cove=numero_cove, organizacion=pedimento.organizacion_id ) if creado: resultado['coves_creados'].append(numero_cove) resultado['mensaje'] = f"Procesados {len(xml_data)} remesas, creados {len(resultado['coves_creados'])} COVEs nuevos" else: resultado['mensaje'] = 'No se encontraron datos de remesas en el XML' 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 procesando pedimento {pedimento_id}: {str(e)}', 'pedimento_id': str(pedimento_id) } @shared_task(bind=True) def crear_partidas(self, organizacion_id, user_id=None): from api.customs.models import Partida task_id = self.request.id pedimentos = obtener_pedimentos(organizacion_id) total_pedimentos = pedimentos.count() publish_task_event(task_id, "processing", f"Creando partidas: {total_pedimentos} pedimentos", progress=0) completados = [] con_pendientes = [] sin_datos = [] errores = [] for idx, pedimento in enumerate(pedimentos): try: if not pedimento.numero_partidas or pedimento.numero_partidas <= 0: sin_datos.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'razon': f'numero_partidas inválido ({pedimento.numero_partidas})', }) continue for i in range(1, pedimento.numero_partidas + 1): Partida.objects.get_or_create( pedimento=pedimento, numero_partida=i, defaults={'organizacion_id': organizacion_id} ) partidas = list(pedimento.partidas.order_by('numero_partida')) no_descargadas = [p.numero_partida for p in partidas if not p.descargado] if not no_descargadas: completados.append(str(pedimento.id)) else: con_pendientes.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'total_partidas': len(partidas), 'descargadas': len(partidas) - len(no_descargadas), 'no_descargadas': no_descargadas, }) except Exception as e: errores.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(e), }) logger.error(f"Error creando partidas para pedimento {pedimento.id}: {e}") if total_pedimentos > 0 and (idx + 1) % 10 == 0: pct = int(((idx + 1) / total_pedimentos) * 100) publish_task_event(task_id, "processing", f"Creando partidas: {idx + 1}/{total_pedimentos}", progress=pct) resultado = { 'organizacion_id': str(organizacion_id), 'auditoria': 'partidas', 'total_pedimentos': total_pedimentos, 'completados': len(completados), 'con_pendientes': len(con_pendientes), 'sin_datos': len(sin_datos), 'con_errores': len(errores), 'detalle_pendientes': con_pendientes, 'detalle_sin_datos': sin_datos, 'detalle_errores': errores, } publish_task_event(task_id, "completed", "Creación de partidas completada", resultado=resultado, progress=100) if user_id: _crear_notificacion_auditoria(user_id, task_id, "Partidas", resultado) return resultado @shared_task def crear_partidas_por_pedimento(pedimento_id): try: pedimento = Pedimento.objects.get(id=pedimento_id) except Pedimento.DoesNotExist: print(f"Error: Pedimento con ID {pedimento_id} no encontrado") return print(f"Procesando pedimento individual {pedimento_id}...") logger.info(f"Procesando pedimento individual {pedimento_id}...") partidas_agregadas = 0 # Validar que numero_partidas no sea None y sea mayor que 0 if pedimento.numero_partidas is not None and pedimento.numero_partidas > 0: partidas_existentes = pedimento.partidas.count() if pedimento.numero_partidas > partidas_existentes: print(f"Pedimento {pedimento_id} - Partidas existentes: {partidas_existentes}, Requeridas: {pedimento.numero_partidas}") logger.info(f"Pedimento {pedimento_id} - Partidas existentes: {partidas_existentes}, Requeridas: {pedimento.numero_partidas}") for i in range(1, pedimento.numero_partidas + 1): from api.customs.models import Partida partida, created = Partida.objects.get_or_create( pedimento=pedimento, numero_partida=i, organizacion_id=pedimento.organizacion_id ) if created: partidas_agregadas += 1 print(f"✓ Partidas agregadas para pedimento {pedimento_id}: {partidas_agregadas}") logger.info(f"✓ Partidas agregadas para pedimento {pedimento_id}: {partidas_agregadas}") else: print(f"Pedimento {pedimento_id} ya tiene todas sus partidas ({partidas_existentes}/{pedimento.numero_partidas})") logger.info(f"Pedimento {pedimento_id} ya tiene todas sus partidas ({partidas_existentes}/{pedimento.numero_partidas})") else: print(f"Error: Pedimento {pedimento_id} tiene numero_partidas inválido: {pedimento.numero_partidas}") logger.info(f"Error: Pedimento {pedimento_id} tiene numero_partidas inválido: {pedimento.numero_partidas}") def _auditar_organizacion(organizacion_id, servicio, related_name, variable, label, task_id=None, user_id=None): """ Itera todos los pedimentos de una organización auditando el campo `variable` en la relación `related_name`. Retorna un resumen estructurado por pedimento. Publica eventos SSE en Redis si se proporciona task_id. """ pedimentos = obtener_pedimentos(organizacion_id) total_pedimentos = pedimentos.count() if task_id: publish_task_event(task_id, "processing", f"Auditando {label}: {total_pedimentos} pedimentos", progress=0) completados = [] pendientes = [] errores = [] for idx, pedimento in enumerate(pedimentos): try: docs = list(getattr(pedimento, related_name).all()) total = len(docs) faltantes = [ getattr(doc, 'numero_cove', None) or getattr(doc, 'numero_edocument', None) for doc in docs if not getattr(doc, variable) ] if total == 0 or len(faltantes) == 0: nuevo_estado = 3 completados.append(str(pedimento.id)) else: nuevo_estado = 4 pendientes.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, f'faltantes_{label}': faltantes, 'total': total, 'descargados': total - len(faltantes), }) modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=nuevo_estado) # Publicar progreso cada 10 pedimentos para no saturar Redis if task_id and total_pedimentos > 0 and (idx + 1) % 10 == 0: pct = int(((idx + 1) / total_pedimentos) * 100) publish_task_event(task_id, "processing", f"Auditando {label}: {idx + 1}/{total_pedimentos}", progress=pct) except Exception as e: errores.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(e), }) logger.error(f"Error auditando pedimento {pedimento.id} [{label}]: {e}") resultado = { 'organizacion_id': str(organizacion_id), 'auditoria': label, 'total_pedimentos': total_pedimentos, 'completados': len(completados), 'con_pendientes': len(pendientes), 'con_errores': len(errores), 'detalle_pendientes': pendientes, 'detalle_errores': errores, } if task_id: publish_task_event(task_id, "completed", f"Auditoría de {label} completada", resultado=resultado, progress=100) if user_id: _crear_notificacion_auditoria(user_id, task_id, label, resultado) return resultado @shared_task(bind=True) def auditar_coves(self, organizacion_id, user_id=None): return _auditar_organizacion( organizacion_id, servicio=8, related_name='coves', variable='cove_descargado', label='cove', task_id=self.request.id, user_id=user_id, ) @shared_task(bind=True) def auditar_acuse_cove(self, organizacion_id, user_id=None): return _auditar_organizacion( organizacion_id, servicio=9, related_name='coves', variable='acuse_cove_descargado', label='acuse_cove', task_id=self.request.id, user_id=user_id, ) @shared_task(bind=True) def auditar_edocuments(self, organizacion_id, user_id=None): return _auditar_organizacion( organizacion_id, servicio=7, related_name='documentos', variable='edocument_descargado', label='edocument', task_id=self.request.id, user_id=user_id, ) @shared_task(bind=True) def auditar_acuse(self, organizacion_id, user_id=None): return _auditar_organizacion( organizacion_id, servicio=6, related_name='documentos', variable='acuse_descargado', label='acuse', task_id=self.request.id, user_id=user_id, ) @shared_task(bind=True) def auditar_remesas(self, organizacion_id, user_id=None): """ Audita el estado de descarga de remesas para todos los pedimentos de una organización. A diferencia de coves/edocuments, las remesas no tienen campo booleano propio — se verifica la existencia de un documento de tipo 3 (Remesa) en el pedimento. """ task_id = self.request.id pedimentos = obtener_pedimentos(organizacion_id) total_pedimentos = pedimentos.count() if task_id: publish_task_event(task_id, "processing", f"Auditando remesas: {total_pedimentos} pedimentos", progress=0) completados = [] pendientes = [] errores = [] for idx, pedimento in enumerate(pedimentos): try: if not pedimento.remesas: modificar_estado_procesamiento(pedimento, servicio_id=5, nuevo_estado=3) completados.append(str(pedimento.id)) elif pedimento.documents.filter(document_type=3).exists(): modificar_estado_procesamiento(pedimento, servicio_id=5, nuevo_estado=3) completados.append(str(pedimento.id)) else: modificar_estado_procesamiento(pedimento, servicio_id=5, nuevo_estado=4) pendientes.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, }) except Exception as e: errores.append({ 'pedimento_id': str(pedimento.id), 'pedimento': pedimento.pedimento, 'error': str(e), }) logger.error(f"Error auditando remesa de pedimento {pedimento.id}: {e}") if task_id and 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': 'remesa', 'total_pedimentos': total_pedimentos, 'completados': len(completados), 'con_pendientes': len(pendientes), 'con_errores': len(errores), 'detalle_pendientes': pendientes, 'detalle_errores': errores, } if task_id: publish_task_event(task_id, "completed", "Auditoría de remesas completada", resultado=resultado, progress=100) if user_id: _crear_notificacion_auditoria(user_id, task_id, "Remesas", resultado) return resultado @shared_task def auditar_cove_por_pedimento(pedimento_id): try: print(f"auditar_cove_por_pedimento >>>> {pedimento_id}") logger.info(f"auditar_cove_por_pedimento >>>> {pedimento_id}") from api.customs.models import Pedimento pedimento = Pedimento.objects.get(id=pedimento_id) auditor_descargas( pedimento, servicio=8, related_name='coves', variable='cove_descargado', mensaje='COVE' ) return {'success': True, 'pedimento_id': str(pedimento_id)} except Exception as e: return {'success': False, 'error': str(e), 'pedimento_id': str(pedimento_id)} @shared_task def auditar_acuse_cove_por_pedimento(pedimento_id): try: from api.customs.models import Pedimento pedimento = Pedimento.objects.get(id=pedimento_id) auditor_descargas( pedimento, servicio=9, related_name='coves', variable='acuse_cove_descargado', mensaje='acuse de COVE' ) return {'success': True, 'pedimento_id': str(pedimento_id)} except Exception as e: return {'success': False, 'error': str(e), 'pedimento_id': str(pedimento_id)} @shared_task def auditar_edocument_por_pedimento(pedimento_id): try: from api.customs.models import Pedimento pedimento = Pedimento.objects.get(id=pedimento_id) auditor_descargas( pedimento, servicio=7, related_name='documentos', variable='edocument_descargado', mensaje='EDocument' ) return {'success': True, 'pedimento_id': str(pedimento_id)} except Exception as e: return {'success': False, 'error': str(e), 'pedimento_id': str(pedimento_id)} @shared_task def auditar_acuse_por_pedimento(pedimento_id): try: from api.customs.models import Pedimento pedimento = Pedimento.objects.get(id=pedimento_id) auditor_descargas( pedimento, servicio=6, related_name='documentos', variable='acuse_descargado', mensaje='acuse' ) return {'success': True, 'pedimento_id': str(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): """ 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'] = ' 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) }