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

@@ -16,6 +16,7 @@ from .tasks.auditoria import (
)
from .tasks.internal_services import auditar_pedimentos
from .tasks.microservice_v2 import procesar_pedimentos_completos, procesar_pedimento_completo_individual
from .tasks.auto_corregir import auto_corregir_pedamentos_task, auditar_pedamentos_incompletos_task
from api.customs.models import Pedimento
from api.organization.models import Organizacion
from api.record.models import Document
@@ -98,7 +99,7 @@ def crear_partidas_organizacion(request):
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
return Response({'error': 'No tiene permisos para esta organización'}, status=status.HTTP_403_FORBIDDEN)
task = crear_partidas.delay(organizacion_id)
task = crear_partidas.delay(organizacion_id, user_id=str(user.id))
return Response({
'organizacion_id': organizacion_id,
@@ -228,7 +229,7 @@ def auditar_pedimentos_endpoint(request):
)
# Ejecutar la tarea de auditoría
task = auditar_pedimentos.delay(organizacion_id)
task = auditar_pedimentos.delay(organizacion_id, user_id=str(user.id))
message = f"Auditoría iniciada para la organización {organizacion_id}"
return Response({
@@ -309,7 +310,7 @@ def auditar_procesamiento_remesa_pedimento_endpoint(request):
def _lanzar_auditoria_organizacion(request, task_fn, label):
"""Helper compartido para los 4 endpoints de auditoría masiva por organización."""
"""Helper compartido para los endpoints de auditoría masiva por organización."""
organizacion_id = request.data.get('organizacion_id')
if not organizacion_id:
return Response({'error': 'Debe proporcionar organizacion_id'}, status=status.HTTP_400_BAD_REQUEST)
@@ -318,12 +319,12 @@ def _lanzar_auditoria_organizacion(request, task_fn, label):
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
return Response({'error': 'No tiene permisos para esta organización'}, status=status.HTTP_403_FORBIDDEN)
task = task_fn.delay(organizacion_id)
task = task_fn.delay(organizacion_id, user_id=str(user.id))
return Response({
'organizacion_id': organizacion_id,
'auditoria': label,
'task_id': task.id,
'mensaje': f'Auditoría de {label} iniciada. Consulta el resultado en GET /api/tasks/status/{task.id}/',
'mensaje': f'Auditoría de {label} iniciada. Usa el stream SSE para seguimiento en tiempo real.',
}, status=status.HTTP_202_ACCEPTED)
@@ -1973,12 +1974,14 @@ def procesar_pedimento_completo_endpoint(request):
'pc_descargado': pc_descargado,
}
# Ya descargado — devolver diagnóstico sin encolar
if pc_descargado:
force = bool(request.data.get('force', False))
# Ya descargado — solo bloquear si no es forzado
if pc_descargado and not force:
return Response({
**base_response,
'estado': 'ya_descargado',
'mensaje': 'El pedimento completo ya fue descargado',
'mensaje': 'El pedimento completo ya fue descargado. Usa force=true para reprocesar remesas, partidas y documentos derivados.',
}, status=status.HTTP_200_OK)
# No puede procesar — devolver diagnóstico con razones
@@ -1990,7 +1993,7 @@ def procesar_pedimento_completo_endpoint(request):
}, status=status.HTTP_200_OK)
# Todo en orden — encolar
task = procesar_pedimento_completo_individual.delay(str(pedimento_id))
task = procesar_pedimento_completo_individual.delay(str(pedimento_id), force=force)
logger.info(f"Procesamiento PC encolado: {pedimento.pedimento} (task={task.id})")
return Response({
@@ -2091,8 +2094,226 @@ def actualizar_info_pedimento(pedimento, info_xml):
if actualizado:
pedimento.save()
return True
return False
except Exception:
return False
return False
# ──────────────────────────────────────────────────────────────────────────────
# Auto-corrección de pedimentos incompletos
# ──────────────────────────────────────────────────────────────────────────────
@swagger_auto_schema(
method='post',
operation_description=(
"Encola una tarea Celery que analiza los XMLs de pedimentos con "
"consultar_vucem=False, extrae datos del pedimento completo VUCEM y "
"auto-corrige los campos faltantes (numero_operacion, aduana, "
"clave_pedimento, regimen, contribuyente). El documento se reclasifica "
"a tipo 2 (Pedimento Completo) y se activa consultar_vucem=True."
),
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['organizacion_id'],
properties={
'organizacion_id': openapi.Schema(
type=openapi.TYPE_STRING,
description='UUID de la organización a procesar',
),
},
),
responses={
202: openapi.Response('Tarea encolada correctamente'),
400: openapi.Response('organizacion_id faltante'),
404: openapi.Response('Organización no encontrada'),
},
)
@api_view(['POST'])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auto_corregir_pedamentos_endpoint(request):
organizacion_id = request.data.get('organizacion_id')
if not organizacion_id:
return Response(
{'error': 'organizacion_id es requerido'},
status=status.HTTP_400_BAD_REQUEST,
)
try:
Organizacion.objects.get(id=organizacion_id)
except Organizacion.DoesNotExist:
return Response(
{'error': 'Organización no encontrada'},
status=status.HTTP_404_NOT_FOUND,
)
task = auto_corregir_pedamentos_task.delay(str(organizacion_id))
logger.info(
f"[auto_corregir] tarea encolada — org={organizacion_id} task={task.id}"
)
return Response(
{
'task_id': task.id,
'organizacion': str(organizacion_id),
'mensaje': (
'Tarea encolada. Se analizarán los pedimentos con '
'consultar_vucem=False de la organización.'
),
},
status=status.HTTP_202_ACCEPTED,
)
@swagger_auto_schema(
method='post',
operation_description=(
"Análisis de solo lectura: detecta pedimentos con consultar_vucem=False "
"que podrían corregirse automáticamente. No modifica BD ni storage. "
"Retorna el listado de pedimentos corregibles, los campos que cambiarían "
"y el nuevo nombre de documento que se asignaría."
),
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['organizacion_id'],
properties={
'organizacion_id': openapi.Schema(
type=openapi.TYPE_STRING,
description='UUID de la organización a analizar',
),
},
),
responses={
202: openapi.Response('Tarea de análisis encolada correctamente'),
400: openapi.Response('organizacion_id faltante'),
404: openapi.Response('Organización no encontrada'),
},
)
@api_view(['POST'])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_pedamentos_incompletos_endpoint(request):
organizacion_id = request.data.get('organizacion_id')
if not organizacion_id:
return Response(
{'error': 'organizacion_id es requerido'},
status=status.HTTP_400_BAD_REQUEST,
)
try:
Organizacion.objects.get(id=organizacion_id)
except Organizacion.DoesNotExist:
return Response(
{'error': 'Organización no encontrada'},
status=status.HTTP_404_NOT_FOUND,
)
task = auditar_pedamentos_incompletos_task.delay(str(organizacion_id))
logger.info(
f"[auditar_incompletos] tarea encolada — org={organizacion_id} task={task.id}"
)
return Response(
{
'task_id': task.id,
'organizacion': str(organizacion_id),
'mensaje': (
'Tarea de análisis encolada. Se reportarán los pedimentos con '
'consultar_vucem=False que podrían corregirse automáticamente, '
'sin modificar nada.'
),
},
status=status.HTTP_202_ACCEPTED,
)
@swagger_auto_schema(
method='post',
operation_description="Analiza un pedimento específico para auto-corrección (solo lectura, sin modificar BD). Acepta pedimento_id (UUID) o pedimento_app.",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'pedimento_id': openapi.Schema(type=openapi.TYPE_STRING, description='UUID del pedimento'),
'pedimento_app': openapi.Schema(type=openapi.TYPE_STRING, description='Número de pedimento (ej: 21-80-3452-1004463)'),
},
),
responses={
202: openapi.Response('Tarea de análisis encolada'),
400: openapi.Response('Parámetro faltante'),
404: openapi.Response('Pedimento no encontrado'),
},
)
@api_view(['POST'])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auditar_pedamento_incompleto_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
pedimento_app = request.data.get('pedimento_app')
if not pedimento_id and not pedimento_app:
return Response({'error': 'pedimento_id o pedimento_app es requerido'}, status=status.HTTP_400_BAD_REQUEST)
try:
if pedimento_id:
pedimento = Pedimento.objects.get(id=pedimento_id)
else:
pedimento = Pedimento.objects.get(pedimento_app=pedimento_app)
except Pedimento.DoesNotExist:
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
task = auditar_pedamentos_incompletos_task.delay(str(pedimento.organizacion_id), str(pedimento.id))
logger.info(f"[auditar_incompletos] individual — ped={pedimento.pedimento_app} task={task.id}")
return Response(
{
'task_id': task.id,
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento_app,
'mensaje': 'Análisis individual encolado. Sin modificar nada.',
},
status=status.HTTP_202_ACCEPTED,
)
@swagger_auto_schema(
method='post',
operation_description="Auto-corrige un pedimento específico: extrae campos del XML y actualiza BD + storage. Acepta pedimento_id (UUID) o pedimento_app.",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'pedimento_id': openapi.Schema(type=openapi.TYPE_STRING, description='UUID del pedimento'),
'pedimento_app': openapi.Schema(type=openapi.TYPE_STRING, description='Número de pedimento (ej: 21-80-3452-1004463)'),
},
),
responses={
202: openapi.Response('Tarea de corrección encolada'),
400: openapi.Response('Parámetro faltante'),
404: openapi.Response('Pedimento no encontrado'),
},
)
@api_view(['POST'])
@permission_classes([IsAuthenticated, require_permission('auditoria.view')])
def auto_corregir_pedamento_endpoint(request):
pedimento_id = request.data.get('pedimento_id')
pedimento_app = request.data.get('pedimento_app')
if not pedimento_id and not pedimento_app:
return Response({'error': 'pedimento_id o pedimento_app es requerido'}, status=status.HTTP_400_BAD_REQUEST)
try:
if pedimento_id:
pedimento = Pedimento.objects.get(id=pedimento_id)
else:
pedimento = Pedimento.objects.get(pedimento_app=pedimento_app)
except Pedimento.DoesNotExist:
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
task = auto_corregir_pedamentos_task.delay(str(pedimento.organizacion_id), str(pedimento.id))
logger.info(f"[auto_corregir] individual — ped={pedimento.pedimento_app} task={task.id}")
return Response(
{
'task_id': task.id,
'pedimento_id': str(pedimento.id),
'pedimento': pedimento.pedimento_app,
'mensaje': 'Corrección individual encolada.',
},
status=status.HTTP_202_ACCEPTED,
)