feature/logica-pedimento-clarion

This commit is contained in:
2026-04-27 12:01:55 -06:00
parent 2f4b9b3a4d
commit eb5df06d24
5 changed files with 106 additions and 18 deletions

View File

@@ -2,7 +2,6 @@
Mapeo fila CSV → datos para PedimentosCreate.
Soporta layout Clarion (PEDIMENTO, TIPO_OPERACION, CLAVE_PEDIMENTO, etc.) y legacy (AÑO, ADUANA, PATENTE, NUMERO).
"""
from datetime import datetime
from decimal import Decimal, InvalidOperation
from typing import Dict, Any, Optional
@@ -71,8 +70,11 @@ def _row_to_pedimento_data_clarion(
elif tipo == "E":
data["operation_type"] = "exp"
ind_con = (row_norm.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper() or "CON"
data["pedimento_type"] = "normal" if ind_con == "IND" else "consolidated"
ind_con = (row_norm.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper()
if ind_con == "IND":
data["pedimento_type"] = "normal"
elif ind_con == "CON":
data["pedimento_type"] = "consolidated"
status = (row_norm.get("ESTATUS") or "").strip()
if status:
@@ -87,19 +89,20 @@ def _row_to_pedimento_data_clarion(
data["observations"] = obs
# Fechas E, F, G → pedimento_dates
# Paridad legacy: Col.G es la fecha de referencia operativa (pago/entrada).
start_str = (row_norm.get("FECHA_INICIO") or "").strip()
end_str = (row_norm.get("FECHA_FINAL") or "").strip()
payment_str = (row_norm.get("FECHA_PAGO") or "").strip()
base = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_dt = parse_date(start_str, date_format_preference) if start_str else base
end_dt = parse_date(end_str, date_format_preference) if end_str else base
payment_dt = parse_date(payment_str, date_format_preference) if payment_str else base
start_dt = parse_date(start_str, date_format_preference) if start_str else None
end_dt = parse_date(end_str, date_format_preference) if end_str else None
payment_dt = parse_date(payment_str, date_format_preference) if payment_str else None
if start_dt and end_dt:
entry_dt = payment_dt or start_dt
data["pedimento_dates"] = {
"entry_date": start_dt,
"entry_date": entry_dt,
"end_date": end_dt,
"start_date": start_dt,
"payment_date": payment_dt,
"payment_date": payment_dt or entry_dt,
}
return data

View File

@@ -34,7 +34,17 @@ TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = {
# Col E, F, G
{"canonical": "FECHA_INICIO", "aliases": ["FECHA INICIO", "FECHA INICIAL"]},
{"canonical": "FECHA_FINAL", "aliases": ["FECHA FINAL", "FECHA FIN"]},
{"canonical": "FECHA_PAGO", "aliases": ["FECHA DE PAGO", "FECHA PAGO"]},
{
"canonical": "FECHA_PAGO",
"aliases": [
"FECHA DE PAGO",
"FECHA PAGO",
"FECHA_ENTRADA",
"FECHA ENTRADA",
"ENTRY_DATE",
"ENTRY DATE",
],
},
# Col H
{"canonical": "ADUANA_SECCION_CRUCE", "aliases": ["ADUANA Y SECCION DE CRUCE", "ADUANA Y SECCION CRUCE", "ADUANA", "CUSTOMS_OFFICE", "CUSTOMS OFFICE"]},
# Col I

View File

@@ -180,7 +180,7 @@ def validate_row_patente(
def validate_row_pedimento_required_full(
row: Dict[str, Any], line_num: int
) -> Optional[Dict[str, Any]]:
"""Obligatorios cuando no es actualizar: TIPO (B), CLAVE_PEDIMENTO (C), REGIMEN (D), FECHA_INICIO (E), FECHA_FINAL (F), FECHA_PAGO (G), ADUANA_SECCION_CRUCE (H)."""
"""Obligatorios cuando no es actualizar: TIPO (B), CLAVE_PEDIMENTO (C), REGIMEN (D), FECHA_INICIO (E), FECHA_FINAL (F), FECHA_PAGO/ENTRADA (G), ADUANA_SECCION_CRUCE (H), IND/CON (J)."""
cols_missing = []
col_labels = [
("TIPO_OPERACION", "Col.B) Tipo Operación"),
@@ -188,8 +188,9 @@ def validate_row_pedimento_required_full(
("REGIMEN", "Col.D) Clave Régimen"),
("FECHA_INICIO", "Col.E) Fecha Inicio"),
("FECHA_FINAL", "Col.F) Fecha Final"),
("FECHA_PAGO", "Col.G) Fecha de Pago"),
("FECHA_PAGO", "Col.G) Fecha de Entrada/Referencia"),
("ADUANA_SECCION_CRUCE", "Col.H) Aduana y Sección de Cruce"),
("INDIVIDUAL_CONSOLIDADO", "Col.J) Tipo de Pedimento (IND/CON)"),
]
for key, label in col_labels:
if not (row.get(key) or "").strip():
@@ -375,9 +376,77 @@ def validaciones_pedimento(
if err:
errors.append(err)
err = validate_row_fechas_coherencia_clarion(row, line_num, date_format_preference)
if err:
errors.append(err)
return errors
def validate_row_fechas_coherencia_clarion(
row: Dict[str, Any],
line_num: int,
date_format_preference: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
Reglas legacy Clarion:
- CON: inicio <= final <= fecha de referencia (pago/entrada).
- IND: si las fechas difieren, advertencia no bloqueante.
En este layout CSV la referencia operativa está en FECHA_PAGO (Col G).
"""
start_raw = (row.get("FECHA_INICIO") or "").strip()
end_raw = (row.get("FECHA_FINAL") or "").strip()
ref_raw = (row.get("FECHA_PAGO") or "").strip()
if not start_raw or not end_raw or not ref_raw:
return None
start_dt = parse_date(start_raw, date_format_preference)
end_dt = parse_date(end_raw, date_format_preference)
ref_dt = parse_date(ref_raw, date_format_preference)
if not start_dt or not end_dt or not ref_dt:
return None
start_date = start_dt.date()
end_date = end_dt.date()
ref_date = ref_dt.date()
ind_con = (row.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper()
# Compatibilidad legacy: vacío se trata como consolidado.
if not ind_con:
ind_con = "CON"
if ind_con == "CON":
if start_date > end_date:
return {
"line": line_num,
"col": "FECHA_FINAL",
"msg": "Error: La fecha inicio no puede ser mayor que la fecha final.",
}
if start_date > ref_date:
return {
"line": line_num,
"col": "FECHA_PAGO",
"msg": "Error: La fecha inicio no puede ser mayor que la fecha de referencia (Col.G).",
}
if end_date > ref_date:
return {
"line": line_num,
"col": "FECHA_PAGO",
"msg": "Error: La fecha final no puede ser mayor que la fecha de referencia (Col.G).",
}
return None
if ind_con == "IND":
if not (start_date == end_date == ref_date):
return {
"line": line_num,
"col": "FECHA_INICIO",
"msg": "Advertencia: En pedimento individual las fechas inicio/final/referencia son distintas.",
"warning": True,
}
return None
# --- Legacy (layout sin PEDIMENTO único): mantener para compatibilidad ---
def validate_row_pedimento_required_legacy(
row: Dict[str, Any], line_num: int

View File

@@ -382,6 +382,11 @@
return 'Inicio';
}
function isFutureEffectiveDate(effectiveDate: string | null): boolean {
if (!effectiveDate) return false;
return effectiveDate > getCurrentLocalDate();
}
let lastTrigger = 0;
// Obtener automáticamente el tipo de cambio cuando cambie la fecha efectiva
@@ -414,9 +419,11 @@
console.warn('⚠️ [TIPO CAMBIO] No encontrado para fecha:', effectiveDate);
if (formData) {
formData.exchange_rate = null;
// Mostrar el diálogo automáticamente si no existe el tipo de cambio
missingExchangeRateDate = effectiveDate;
showExchangeRateDialog = true;
// Para fecha futura no forzamos captura de TC/DOF.
if (!isFutureEffectiveDate(effectiveDate)) {
missingExchangeRateDate = effectiveDate;
showExchangeRateDialog = true;
}
}
}
})
@@ -447,6 +454,7 @@
export async function checkPaymentDateRate(date: string): Promise<boolean> {
const effectiveDate = date || getEffectiveExchangeDate();
if (!effectiveDate || !companyStore.activeCompany?.id) return true;
if (isFutureEffectiveDate(effectiveDate)) return true;
try {
const rate = await getExchangeRateByDate(effectiveDate, companyStore.activeCompany.id);

View File

@@ -1035,10 +1035,8 @@
errorStr.includes('No existe un Tipo de Cambio registrado') ||
errorStr.includes('financials.exchange_rate')
) {
// Evitamos bloquear por validaciones locales de tipo de cambio, pero
// si backend lo rechaza informamos el motivo y mantenemos al usuario en General.
toast.error(
'El backend rechazó el guardado por tipo de cambio faltante o inválido. Revisa la fecha de inicio/pago y registra el tipo de cambio requerido.'
'No hay tipo de cambio para la fecha seleccionada. Si es una fecha futura, puedes guardar y capturarlo después; si no es futura, registra el tipo de cambio.'
);
activeTab = 'general';
return;