fix/de los tickets T2026-05-027, T2025-09-004 y T2025-09-056

This commit is contained in:
2026-06-15 11:18:58 -06:00
parent 7644446267
commit 23ed52c78a
29 changed files with 2992 additions and 987 deletions

View File

@@ -0,0 +1,110 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from api.customs.models import EDocument, Cove, EstadoDescarga
from api.record.models import Document
from api.utils.storage_service import storage_service
class Command(BaseCommand):
"""
Reconciliación de estatus de descarga VUCEM (T2026-05-027).
Detecta registros marcados como 'descargado' cuyo documento no existe en BD
o cuyo archivo falta físicamente en storage (MinIO), y los transiciona a
estado 'error' para que sean visibles y reprocesables. Sin --apply solo
reporta (dry-run).
Uso:
python manage.py reconciliar_descargas # reporte
python manage.py reconciliar_descargas --apply # corrige
python manage.py reconciliar_descargas --organizacion <uuid>
"""
help = "Reconcilia estatus de descarga de EDocs/COVEs contra documentos reales (BD + storage)"
# Catálogo confirmado de document_type:
# 4 = acuse EDoc, 7 = acuse COVE, 19/23 = request COVE, 21/25 = request EDoc,
# 20 = error COVE, 22 = error EDoc, 24 = error acuse COVE, 26 = error acuse EDoc
EXCLUIR_EDOC_GENERAL = [4, 21, 22, 25, 26]
EXCLUIR_COVE_GENERAL = [7, 19, 20, 23, 24]
def add_arguments(self, parser):
parser.add_argument(
'--apply', action='store_true',
help='Aplica las correcciones; sin esta bandera solo reporta (dry-run)'
)
parser.add_argument(
'--organizacion', type=str, default=None,
help='Limitar la reconciliación a una organización (UUID)'
)
parser.add_argument(
'--pedimento', type=str, default=None,
help='Limitar la reconciliación a un pedimento (UUID)'
)
def handle(self, *args, **opts):
apply_changes = opts['apply']
detectados = []
flujos = [
# (modelo, campo_estado, campo_intentos, etiqueta, fn_documentos)
(EDocument, 'acuse_estado', 'acuse_intentos', 'edoc.acuse',
lambda r: Document.objects.filter(
pedimento=r.pedimento,
archivo__icontains=r.numero_edocument,
document_type_id=4)),
(EDocument, 'edocument_estado', 'edocument_intentos', 'edoc.general',
lambda r: Document.objects.filter(
pedimento=r.pedimento,
archivo__icontains=r.numero_edocument,
).exclude(document_type_id__in=self.EXCLUIR_EDOC_GENERAL)),
(Cove, 'acuse_cove_estado', 'acuse_cove_intentos', 'cove.acuse',
lambda r: Document.objects.filter(
pedimento=r.pedimento,
archivo__icontains=r.numero_cove,
document_type_id=7)),
(Cove, 'cove_estado', 'cove_intentos', 'cove.general',
lambda r: Document.objects.filter(
pedimento=r.pedimento,
archivo__icontains=r.numero_cove,
).exclude(document_type_id__in=self.EXCLUIR_COVE_GENERAL)),
]
for modelo, campo_estado, campo_intentos, etiqueta, fn_documentos in flujos:
qs = modelo.objects.filter(**{campo_estado: EstadoDescarga.DESCARGADO})
if opts['organizacion']:
qs = qs.filter(organizacion_id=opts['organizacion'])
if opts['pedimento']:
qs = qs.filter(pedimento_id=opts['pedimento'])
for registro in qs.select_related('pedimento').iterator():
numero = getattr(registro, 'numero_edocument', None) or registro.numero_cove
docs = fn_documentos(registro)
# Disponible = al menos un documento con fila en BD, tamaño > 0
# y archivo físicamente presente en storage
disponible = any(
doc.size and storage_service.file_exists(doc.archivo.name)
for doc in docs
)
if disponible:
continue
detectados.append((etiqueta, str(registro.id), numero, str(registro.pedimento_id)))
if apply_changes:
with transaction.atomic():
setattr(registro, campo_estado, EstadoDescarga.ERROR)
registro.ultimo_error = (
f"Reconciliación: {etiqueta} marcado como descargado "
f"sin archivo disponible en BD/storage"
)
# save() del modelo sincroniza el booleano legado
registro.save(update_fields=[campo_estado, 'ultimo_error'])
modo = 'CORREGIDOS' if apply_changes else 'DETECTADOS (dry-run, usa --apply para corregir)'
self.stdout.write(self.style.WARNING(f"{modo}: {len(detectados)}"))
for etiqueta, registro_id, numero, pedimento_id in detectados:
self.stdout.write(f" [{etiqueta}] id={registro_id} numero={numero} pedimento={pedimento_id}")
if not detectados:
self.stdout.write(self.style.SUCCESS("Sin inconsistencias: todos los 'descargado' tienen archivo disponible"))