El API valida linea_scaii_551 como Optional[int] y rechazaba la partida con 422 (int_parsing) porque se enviaba un espacio " " cuando el LOTE estaba vacio. Ahora se convierte el LOTE a int; si viene vacio o no es numerico se envia null, que el API acepta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
153 lines
7.7 KiB
Python
153 lines
7.7 KiB
Python
from typing import Dict, Any, Optional
|
|
|
|
class Generador551SCAII:
|
|
@staticmethod
|
|
def preparar_json(par: Dict[str, Any], id_factura: int, datos_parte: Dict[str, Any],
|
|
um_data: Optional[Dict[str, Any]], met_val: str, cla_mon: str,
|
|
num_remesa: int, num_partida_remesa: int, tipo_moneda_fac: str) -> Dict[str, Any]:
|
|
"""
|
|
Lógica para Registro 551 (Partidas) - SCAII.
|
|
Implementa paridad total con la lógica de Clarion/WinDev.
|
|
"""
|
|
|
|
# 1. Lógica de Fracción (Regla Octava)
|
|
frac_raw = par.get("Fraccion", "")
|
|
frac_oct = par.get("FraccionOctava", "")
|
|
frac_final = frac_oct if frac_oct else frac_raw
|
|
|
|
# 2. Descripciones (Limpieza de CR/LF como en WinDev)
|
|
desc_esp = par.get("Descripcion", " ").replace("\r", "").replace("\n", "").replace("\t", "")
|
|
desc_ing = " " # Por ahora
|
|
|
|
# 3. Arancel (Mapping de Clarion)
|
|
tipo_frac = par.get("TipoFracc", "")
|
|
arancel_map = {"TLCS": 3, "GENERAL": 0, "PROSEC": 1}
|
|
id_arancel = str(arancel_map.get(tipo_frac, 0))
|
|
|
|
# 4. Lógica de Valor Comercial (Loc_ValorExpoMC)
|
|
# ME = ValorDolares, MN = ValorPesos, else ValorComercial
|
|
if tipo_moneda_fac == "ME":
|
|
valor_comercial = float(par.get("ValorDolares") or 0)
|
|
elif tipo_moneda_fac == "MN":
|
|
valor_comercial = float(par.get("ValorPesos") or 0)
|
|
else:
|
|
valor_comercial = float(par.get("ValorComercial") or 0)
|
|
|
|
# 5. Unidad de Medida OMA y Comercial (Mapeo reverso para WinDev)
|
|
oma_final = str(par.get("ClaveOMA") or " ").strip()
|
|
oma_raw = str(par.get("UnidadMedida", "")).strip().upper()
|
|
|
|
if not oma_final or oma_final == "None":
|
|
oma_final = "C62_1" if oma_raw in ["6", "PZA"] else oma_raw
|
|
|
|
map_um_num = {
|
|
"PZA": "6", "PIEZA": "6", "PZ": "6", "PC": "6", "PIEZAS": "6",
|
|
"KGS": "1", "KGM": "1", "KG": "1", "KILOS": "1", "KILO": "1",
|
|
"LTS": "5", "L": "5", "LITROS": "5", "LITRO": "5",
|
|
"MTR": "2", "M": "2", "METROS": "2", "METRO": "2",
|
|
"PAR": "7", "PARES": "7",
|
|
"CJ": "3", "CAJA": "3", "CAJAS": "3",
|
|
"JGO": "12", "JUEGO": "12", "SET": "12", "KIT": "12"
|
|
}
|
|
|
|
# Priorizar la clave que venga de la base de datos (CLAVEOMA)
|
|
if um_data and um_data.get("CLAVEOMA"):
|
|
um_comercial_num = str(um_data["CLAVEOMA"]).strip()
|
|
else:
|
|
um_comercial_num = map_um_num.get(oma_raw, oma_raw)
|
|
|
|
# 6. Cálculo del Valor Agregado
|
|
valor_agregado_me = float(par.get("ValorAgregadoME") or 0)
|
|
valor_aduanas_me = float(par.get("ValorAduanasME") or 0)
|
|
valor_dolares = float(par.get("ValorDolares") or 0)
|
|
valor_aduanas_mn = float(par.get("ValorAduanasMN") or 0)
|
|
valor_pesos = float(par.get("ValorPesos") or 0)
|
|
costo_auxiliar_me = float(par.get("CostoAuxiliarME") or 0)
|
|
cantidad = float(par.get("Cantidad") or 0)
|
|
|
|
# 1ra Opción (EXPO): Si es Exportación, el valor ya viene calculado en ValorAgregadoME
|
|
if valor_agregado_me > 0:
|
|
valor_agregado = valor_agregado_me
|
|
# 2da Opción (IMPO - Exactitud WinDev): Costo Unitario Auxiliar * Cantidad
|
|
elif costo_auxiliar_me > 0:
|
|
valor_agregado = costo_auxiliar_me * cantidad
|
|
# 3ra Opción: Si Valor Aduanas ME existe
|
|
elif valor_aduanas_me > 0 and valor_aduanas_me > valor_dolares:
|
|
valor_agregado = valor_aduanas_me - valor_dolares
|
|
# 4ta Opción: Calcular mediante el tipo de cambio implícito
|
|
elif valor_aduanas_mn > 0 and valor_aduanas_mn > valor_pesos and valor_dolares > 0:
|
|
tc_implicito = valor_pesos / valor_dolares if valor_dolares else 1
|
|
valor_agregado = (valor_aduanas_mn - valor_pesos) / tc_implicito
|
|
else:
|
|
valor_agregado = 0
|
|
|
|
valor_agregado = round(max(0, valor_agregado), 2)
|
|
|
|
# 7. Método de Valoración específico por partida
|
|
metodo_val = str(par.get("MetodoValoracion") or "").strip()
|
|
if not metodo_val:
|
|
metodo_val = str(met_val) if met_val else ""
|
|
|
|
# linea_scaii_551 es entero opcional (llave de cruce con WINSAAI); sale del LOTE.
|
|
# Si el LOTE viene vacio o no es numerico -> None (el API lo acepta como null).
|
|
try:
|
|
linea_scaii = int(str(par.get("Lote")).strip())
|
|
except (ValueError, TypeError):
|
|
linea_scaii = None
|
|
|
|
registro = {
|
|
"id_factura": id_factura,
|
|
"fraccion_551": str(frac_final),
|
|
"descripcion_mercancia_551": desc_esp[:250],
|
|
"numero_parte_551": str(par.get("NumParte") or " "),
|
|
"valor_mercancia_551": valor_comercial,
|
|
"cantidad_comercial_551": float(par.get("Cantidad") or 0),
|
|
"unidad_medida_comercial_551": um_comercial_num, # Ej. "6"
|
|
"cantidad_tarifa_551": float(par.get("Cantidad") or 0), # Se ajusta en el motor
|
|
"umt_551": " ", # WinDev envía un espacio vacío aquí
|
|
"valor_agregado_551": str(valor_agregado),
|
|
"vinculacion_551": "0",
|
|
"metodo_valoracion_551": metodo_val,
|
|
"marca_551": str(par.get("Marca") or " ").strip() or " ",
|
|
"modelo_551": str(par.get("Modelo") or " ").strip() or " ",
|
|
"pais_origen_destino_551": str(par.get("PaisOrigen") or " "),
|
|
"pais_comprador_vendedor_551": "MEX", # Generalmente MEX en Impo
|
|
"entidad_federativa_origen_551": " ",
|
|
"entidad_federativa_destino_551": " ",
|
|
"entidad_federativa_comprador_551": " ",
|
|
"entidad_federativa_vendedor_551": " ",
|
|
"identificador_arancel_aplicar_551": id_arancel,
|
|
"clave_industria_551": str(par.get("Sector") or " "),
|
|
"peso_bruto_551": float(par.get("PesoBruto") or 0),
|
|
"uso_mercancia_551": " ",
|
|
"estado_mercancia_551": " ",
|
|
"moneda_partida_551": str(cla_mon),
|
|
"numero_factura_551": str(par.get("NumFactura") or " "),
|
|
"tipo_bulto_551": " ", # Se llena en motor si aplica
|
|
"cantidad_bultos_551": float(par.get("CantBultos") or 0),
|
|
|
|
# --- PARIDAD WINDEV ---
|
|
"peso_bruto_kg_551": float(par.get("PesoNeto") or 0), # WinDev envía Peso Neto aquí
|
|
"peso_neto_kg_551": float(par.get("ValorAduanasMN") or 0), # HACK WinDev: Envía ValorAduanasMN aquí
|
|
|
|
"numero_remesa_551": str(num_remesa or " "),
|
|
"numero_partida_remesa_551": str(par.get("Linea") or num_partida_remesa),
|
|
"linea_scaii_551": linea_scaii, # Entero (o null) desde LOTE de la tabla de partidas
|
|
"cantidad_alterna_551": float(par.get("CantidadAlterna") or 0),
|
|
"unidad_medida_alterna_551": str(par.get("UnidadAlterna") or " "),
|
|
"descripcion_ingles_551": str(par.get("DescripcionIngles") or " ").strip()[:80],
|
|
"identificador_sistema_scaii_scaf_551": "SCAII",
|
|
"vu_submodelo_551": " ",
|
|
"vu_serie_551": " ",
|
|
"valor_unitario_aduana_551": float(par.get("CostoUnitarioME") or 0),
|
|
"valor_dolares_551": float(par.get("ValorDolares") or 0),
|
|
"unidad_medida_oma_551": oma_final, # Código de la fracción (ej. '6')
|
|
"valor_dolares_cove_551": "0",
|
|
"subdivision_fraccion_551": str(frac_final)[-2:] if len(str(frac_final)) >= 2 else "00",
|
|
"codigo_carta_porte_551": " ",
|
|
"nodo_551": "85858585",
|
|
"CantidadComercialOMA_551": str(float(par.get("Cantidad") or 0))
|
|
}
|
|
|
|
return registro
|