feature/pedimentos-correccion-partidas

This commit is contained in:
2026-05-28 07:10:39 -06:00
parent 94846fec8a
commit 709a5dedab
29 changed files with 1908 additions and 87 deletions

View File

@@ -0,0 +1,117 @@
"""
Corrige el mismatch de case entre el campo `archivo` en BD y los nombres
reales de los objetos en MinIO.
Causa habitual: transferencia de archivos de producción a local lowercaseó
los filenames, pero la BD conserva los nombres originales con mayúsculas.
Estrategia: para cada Document cuyo `archivo` no exista en MinIO con el
nombre exacto, intenta el filename en minúsculas. Si lo encuentra, actualiza
el campo en BD. Los archivos que ya coinciden no se tocan.
Uso:
python manage.py fix_archivo_case --pedimento <UUID> --dry-run
python manage.py fix_archivo_case --pedimento <UUID>
python manage.py fix_archivo_case --organizacion <UUID> --dry-run
python manage.py fix_archivo_case --organizacion <UUID>
"""
import posixpath
from django.core.management.base import BaseCommand, CommandError
from api.customs.models import Pedimento
from api.record.models import Document
from api.utils.minio_client import minio_client
class Command(BaseCommand):
help = "Corrige mismatch de case entre campo archivo en BD y MinIO."
def add_arguments(self, parser):
parser.add_argument(
"--pedimento", metavar="UUID",
help="UUID del pedimento a corregir.",
)
parser.add_argument(
"--organizacion", metavar="UUID",
help="UUID de la organización.",
)
parser.add_argument(
"--dry-run", action="store_true",
help="Solo diagnóstico, sin aplicar cambios.",
)
def handle(self, *args, **options):
ped_id = options.get("pedimento")
org_id = options.get("organizacion")
dry_run = options["dry_run"]
if dry_run:
self.stdout.write(self.style.WARNING(
"=== MODO PRUEBA (--dry-run): Sin cambios en BD ===\n"
))
qs = Document.objects.all()
if ped_id:
try:
ped = Pedimento.objects.get(id=ped_id)
except Pedimento.DoesNotExist:
raise CommandError(f"Pedimento {ped_id!r} no encontrado.")
qs = qs.filter(pedimento=ped)
self.stdout.write(f"Pedimento: {ped.pedimento_app}\n")
elif org_id:
qs = qs.filter(organizacion_id=org_id)
total = qs.count()
self.stdout.write(f"Documentos a revisar: {total}\n")
ok = mismatch = not_found = 0
for doc in qs.iterator(chunk_size=500):
name = doc.archivo.name if doc.archivo else None
if not name:
continue
if minio_client.file_exists(name):
ok += 1
continue
lower_name = self._lower_filename(name)
if lower_name == name:
not_found += 1
continue
if minio_client.file_exists(lower_name):
mismatch += 1
self.stdout.write(
f" {'[DRY]' if dry_run else '[FIX]'} doc {doc.id}:\n"
f" BD : {name}\n"
f" MinIO : {lower_name}\n"
)
if not dry_run:
doc.archivo.name = lower_name
doc.save(update_fields=["archivo"])
else:
not_found += 1
self.stdout.write(
f"\n{'' * 60}\nRESUMEN\n"
f" Coinciden exacto : {ok}\n"
f" Mismatch de case : {mismatch}\n"
f" No encontrados : {not_found}\n"
)
if dry_run and mismatch:
self.stdout.write(self.style.WARNING(
"\nEjecuta sin --dry-run para aplicar los cambios."
))
elif not dry_run and mismatch:
self.stdout.write(self.style.SUCCESS(
f"\n{mismatch} registros actualizados en BD."
))
def _lower_filename(self, name):
"""Lowercase solo el filename, preserva el path del directorio."""
dir_part = posixpath.dirname(name)
filename = posixpath.basename(name)
return posixpath.join(dir_part, filename.lower())