fix: filtrado de partidas por nomenclatura de documento (core/partida_docs)

Frontera (_|.|$) tras vu_PT_{app}_{numero} para cubrir los 3 formatos sin
confundir partida 1 con 11/100. Fuente unica en core/partida_docs.py, reusada
por get_documentos, handlers de borrado/descarga y fix_partidas_error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 07:31:07 -06:00
parent d732602775
commit 244bbcb21c
6 changed files with 316 additions and 43 deletions

View File

@@ -12,8 +12,12 @@ from api.customs.models import (
from django.db import models
from django.db.models import Q
from api.record.models import Document # Asegúrate de importar el modelo Documento
from api.record.serializers import DocumentSerializer
from api.record.serializers import DocumentSerializer
from api.vucem.serializers import VucemSerializer
from core.partida_docs import es_doc_de_partida
import logging
logger = logging.getLogger(__name__)
class PedimentoSerializer(serializers.ModelSerializer):
documentos_count = serializers.SerializerMethodField()
@@ -48,31 +52,34 @@ class PartidaSerializer(serializers.ModelSerializer):
documentos = serializers.SerializerMethodField()
def get_documentos(self, obj):
if not obj or not getattr(obj, 'pedimento', None):
return []
if not obj or not getattr(obj, 'numero_partida', None):
if not obj or not getattr(obj, 'pedimento', None) or not getattr(obj, 'numero_partida', None):
return []
try:
pedimento_app = str(obj.pedimento.pedimento_app).strip()
numero = str(obj.numero_partida).strip()
# Incluir pedimento_app en el patrón para evitar falsos positivos
# entre partidas con números cortos (1 matchearía 10, 100, etc.)
patron = f"vu_PT_{pedimento_app}_{numero}_"
# El matching documento→partida se hace por nombre de archivo con
# frontera real (core.partida_docs); document_type_id=1 son los
# documentos de respuesta de partida (excluye REQUEST/ERROR 17/18).
mapa = self.context.get('docs_por_partida')
if mapa is not None:
# Camino optimizado: la vista precargó el mapa de la página.
docs = mapa.get((obj.pedimento_id, obj.numero_partida), [])
else:
# Fallback (retrieve u otros callers): una consulta por partida.
qs = Document.objects.filter(
pedimento=obj.pedimento,
document_type_id=1,
).select_related('pedimento') # evita N+1 en DocumentSerializer.get_pedimento_numero
app = obj.pedimento.pedimento_app
docs = [d for d in qs if es_doc_de_partida(d.archivo.name, app, obj.numero_partida)]
# 17 = REQUEST partida, 18 = ERROR partida
qs = Document.objects.filter(
pedimento=obj.pedimento,
archivo__icontains=patron,
).exclude(document_type_id__in=[17, 18])
org_id = getattr(obj, 'organizacion_id', None)
if org_id:
docs = [d for d in docs if d.organizacion_id == org_id]
if hasattr(obj, 'organizacion') and obj.organizacion:
qs = qs.filter(organizacion=obj.organizacion)
return DocumentSerializer(docs, many=True, context=self.context).data
serializer = DocumentSerializer(qs, many=True, context=self.context)
return serializer.data
except Exception:
except Exception as e:
logger.warning("get_documentos partida %s: %s", getattr(obj, 'id', '?'), e)
return []
class Meta:
model = Partida