chore: initial commit del proyecto SCAII_Sync_Client

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 13:38:20 -05:00
commit 10dc063cf1
26 changed files with 2069 additions and 0 deletions

61
registros/scaf/reg_501.py Normal file
View File

@@ -0,0 +1,61 @@
from typing import Dict, Any
class Generador501SCAF:
@staticmethod
def preparar_json(datos_pedi: Dict[str, Any], totales: Dict[str, float],
datos_cliente: Dict[str, Any], total_trailers: int) -> Dict[str, Any]:
"""
Lógica específica para SCAF (Registro 501).
"""
# Lógica de Destino/Zona (SCAF a veces usa códigos distintos, pero el if parece igual)
destino = datos_pedi.get("OPCIONDESTINO", "")
codigo_destino = "9" if "Interior" in destino else "7" if any(x in destino for x in ["Región", "Franja"]) else ""
clave_transfer = datos_cliente.get("CLAVETRANSFER", "").strip() if datos_cliente else ""
# Construcción del Registro 501 SCAF
registro = {
"tipo_operacion_501": datos_pedi.get("Tipo"),
"clave_documento_501": datos_pedi.get("Clave"),
"numero_pedimento_501": (datos_pedi.get("Pedimento") or "")[8:15],
"codigo_importador_exportador_501": clave_transfer if clave_transfer else " ",
"monto_fletes_501": totales["flete"],
"monto_seguros_501": totales["seguros"],
"costo_embalajes_501": totales["embalajes"],
"cargos_incrementables_501": totales["otros_incrementa"],
"cargos_deducibles_501": 0,
"peso_total_bruto_501": totales["peso_bruto"],
"total_bultos_501": totales["bultos"],
"metodo_transporte_entrada_501": datos_pedi.get("TransE"),
"metodo_transporte_arribo_501": datos_pedi.get("TransA"),
"metodo_transporte_salida_501": datos_pedi.get("TransS"),
"codigo_destino_zona_501": codigo_destino,
"numero_referencia_501": "",
"moneda_costos_incrementables_501": "ME",
"codigo_aduana_despacho_501": datos_pedi.get("Aduana"),
"numero_patente_501": (datos_pedi.get("Pedimento") or "")[3:7],
"origenPedimento": 1,
"total_vehiculos_501": total_trailers,
"rfc_importador_exportador_501": " ",
"curp_importador_exportador_501": " ",
"nombre_completo_importador_exportador_501": " ",
"calle_direccion_importador_exportador_501": " ",
"numero_exterior_direccion_importador_exportador_501": " ",
"numero_interior_direccion_importador_exportador_501": " ",
"colonia_direccion_importador_exportador_501": " ",
"localidad_direccion_importador_exportador_501": " ",
"municipio_direccion_importador_exportador_501": " ",
"estado_direccion_importador_exportador_501": " ",
"pais_direccion_importador_exportador_501": " ",
"codigo_postal_direccion_importador_exportador_501": " ",
"monto_fletes_decrementables_501": 0,
"monto_seguros_decrementables_501": 0,
"monto_carga_decrementables_501": 0,
"monto_descarga_decrementables_501": 0,
"monto_otros_decrementables_501": 0,
"MarcaBulto1_501": "S/M", # SCAF usa S/M mayormente
"MarcaBulto2_501": "S/N"
}
return registro

18
registros/scaf/reg_502.py Normal file
View File

@@ -0,0 +1,18 @@
from typing import Dict, Any
class Generador502SCAF:
@staticmethod
def preparar_json(id_pedimento: int, datos_transporte: Dict[str, Any]) -> Dict[str, Any]:
"""
Lógica para Registro 502 (Transporte) - SCAF.
"""
placas = datos_transporte.get("NUMEROPLACAS", " ") if datos_transporte else " "
pais = datos_transporte.get("PAIS", " ") if datos_transporte else " "
registro = {
"id_pedimento": id_pedimento,
"numero_placas_502": placas,
"codigo_pais_transporte_502": pais
}
return registro

21
registros/scaf/reg_503.py Normal file
View File

@@ -0,0 +1,21 @@
from typing import Dict, Any
class Generador503SCAF:
@staticmethod
def preparar_json(id_pedimento: int, factura_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Lógica para Registro 503 (Guías) - SCAF.
"""
num_niu = factura_data.get("NumNIU", " ")
tipo_guia = factura_data.get("TipoGuia", " ")
total_guias = factura_data.get("CantGuias", 0)
registro = {
"id_pedimento": id_pedimento,
"numero_guia_manifiesto_embarque_503": num_niu if num_niu else " ",
"identificador_guia_503": tipo_guia if tipo_guia else " ",
"total_guias_503": total_guias if total_guias else 0
}
return registro

33
registros/scaf/reg_504.py Normal file
View File

@@ -0,0 +1,33 @@
from typing import List, Dict, Any
class Generador504SCAF:
@staticmethod
def preparar_listado_json(id_pedimento: int, contenedores_raw: str) -> List[Dict[str, Any]]:
"""
Procesa el string de contenedores (CONT1|TIPO1,CONT2|TIPO2)
y genera una lista de registros para la API - Versión SCAF.
"""
registros = []
if not contenedores_raw or contenedores_raw.strip() == "":
return registros
bloques = contenedores_raw.split(",")
for bloque in bloques:
if not bloque.strip():
continue
partes = bloque.split("|")
num_contenedor = partes[0].strip() if len(partes) > 0 else ""
tipo_contenedor = partes[1].strip() if len(partes) > 1 else " "
if num_contenedor:
registros.append({
"id_pedimento_501": id_pedimento,
"numero_contenedor_504": num_contenedor,
"tipo_contenedor_504": tipo_contenedor,
"status_504": "1"
})
return registros

104
registros/scaf/reg_505.py Normal file
View File

@@ -0,0 +1,104 @@
from typing import Dict, Any, Optional
class Generador505SCAF:
@staticmethod
def preparar_json(fac: Dict[str, Any], id_pedimento: int, datos_empresa: Dict[str, Any],
datos_prov: Dict[str, Any], datos_trans: Optional[Dict[str, Any]],
tipo_moneda_global: str, tc_global: float) -> Dict[str, Any]:
"""
Lógica para Registro 505 (Facturas) - SCAF.
"""
clave_moneda = fac.get("ClaveMoneda", "")
valor_mc = fac.get("ValorImpoMC", 0)
valor_me = fac.get("ValorImpoME", 0)
if tipo_moneda_global == "ME":
clave_moneda = "USD"
valor_mc = valor_me
elif tipo_moneda_global == "MN":
clave_moneda = "MXP"
valor_mc = fac.get("ValorImpoMN", 0)
# SCAF usa el mismo mapeo de Valor Aduana que SCAII
met_val_raw = fac.get("MetValor", 0)
met_val_map = {1: "VTM", 2: "VMI", 3: "VMS", 4: "VPU", 5: "VR", 6: "A78"}
met_val_str = f"VALADU.{met_val_map.get(met_val_raw, met_val_raw)}"
# 3. Incoterm
incoterm = fac.get("Incoterm", "")
incoterm_full = incoterm if incoterm else ""
# 4. Otros mapeos
obs = fac.get("ObservacionesVU", "").replace("\r", "").replace("\n", "").replace("\t", "")
contenedores = fac.get("ContenedoresTipo", "").replace(",", "|")
registro = {
"numero_factura_505": fac.get("NumFactura"),
"fecha_facturacion_505": str(fac.get("FechaFactura"))[:10],
"termino_facturacion_505": incoterm if incoterm else "",
"moneda_facturacion_505": clave_moneda if clave_moneda else "",
"valor_moneda_facturacion_505": str(float(valor_mc or 0)),
"valor_dolares_505": str(float(valor_me or 0)),
"codigo_proveedor_comprador_505": datos_prov.get("CLAVETRANSFER", ""),
"e_document_505": fac.get("EDocument", ""),
"serie_certificado_cove_505": fac.get("NumeroCertificado", ""),
"firma_electronica_cove_505": fac.get("FirmaElectronica", ""),
"observaciones_factura_505": obs if obs else "",
"numero_economico_505": contenedores if contenedores else "",
"tipo_contenedor_numero_economico_505": str(fac.get("Remesa") or ""),
"numero_contenedor_505": contenedores if contenedores else "",
"tipo_contenedor_505": str(fac.get("Remesa") or ""),
"numero_remesa_505": str(fac.get("Remesa") or ""),
"tipo_operacion_505": "",
"certificado_origen_505": "S" if fac.get("FUNGECOMOCO") else "N",
"numero_certificado_origen_505": "",
"numero_exportador_confiable_505": datos_empresa.get("NUMDEEXPORTADORCONFIABLE") or "",
"incoterm_505": incoterm_full,
"metodo_valoracion_505": met_val_str,
"vinculacion_505": str(datos_prov.get("VINCULACION", 0)),
"subdivision_505": "S" if fac.get("SUBDIVISION") else "N",
"tipo_cambio_usd_505": str(float(fac.get("TipoCambio") or 0)),
"se_genero_en_contingencia_505": "S" if fac.get("MODOCONTINGENCIA") else "N",
"operacion_cove_505": fac.get("NUMOPERACIONVU", ""),
"transportista_clave_505": fac.get("TRANSPORTISTA", ""),
"rfc_505": datos_trans.get("RFC", "") if datos_trans else "",
"caat_505": datos_trans.get("CODIGOCAAT", "") if datos_trans else "",
"id_pedimento": id_pedimento,
"PaisFactura_505": datos_prov.get("PAIS", ""),
"Bultos_cantidad_505": str(fac.get("CANTBULTOS", 0)),
"provcomp_numregidtrib_505": " ",
"provcomp_rfc_505": " ",
"provcomp_curp_505": " ",
"provcomp_nombre_505": " ",
"provcomp_calle_505": " ",
"provcomp_numero_exterior_505": " ",
"provcomp_numero_interior_505": " ",
"provcomp_colonia_505": " ",
"provcomp_localidad_505": " ",
"provcomp_municipio_505": " ",
"provcomp_estado_505": " ",
"provcomp_pais_505": " ",
"provcomp_codigo_postal_505": " ",
"destinatario_clave_505": " ",
"destinatario_numregidtrib_505": " ",
"destinatario_rfc_505": " ",
"destinatario_curp_505": " ",
"destinatario_nombre_505": " ",
"destinatario_calle_505": " ",
"destinatario_numero_exterior_505": " ",
"destinatario_numero_interior_505": " ",
"destinatario_colonia_505": " ",
"destinatario_localidad_505": " ",
"destinatario_referencia_505": " ",
"destinatario_municipio_505": " ",
"destinatario_estado_505": " ",
"destinatario_pais_505": " ",
"destinatario_codigo_postal_505": " ",
"uuid_505": " ",
"path_pdf_cfdi_505": " ",
"path_xml_cfdi_505": " ",
"estatus_cove_505": " "
}
return registro

101
registros/scaf/reg_551.py Normal file
View File

@@ -0,0 +1,101 @@
from typing import Dict, Any, Optional
class Generador551SCAF:
@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) -> Dict[str, Any]:
"""
Lógica para Registro 551 (Partidas) - SCAF.
Homologado con la lógica de pesos y valores de 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 and frac_oct.strip() else frac_raw
# 2. Descripciones
desc_esp = par.get("Descripcion", " ").replace("\r", "").replace("\n", "").replace("\t", "")
# 3. Arancel (Mapping)
tipo_frac = par.get("TipoFracc", "")
arancel_map = {"TLCS": 3, "GENERAL": 0, "PROSEC": 1}
id_arancel = str(arancel_map.get(tipo_frac, 0))
# 4. Unidad de Medida OMA y Comercial
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)
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": float(par.get("ValorDolares") or 0), # SCAF suele usar Dolares para MC
"cantidad_comercial_551": float(par.get("Cantidad") or 0),
"unidad_medida_comercial_551": um_comercial_num,
"cantidad_tarifa_551": float(par.get("Cantidad") or 0),
"umt_551": " ",
"valor_agregado_551": "0",
"vinculacion_551": "0",
"metodo_valoracion_551": str(met_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",
"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": " ",
"cantidad_bultos_551": float(par.get("CantBultos") or 0),
# --- PARIDAD WINDEV ---
"peso_bruto_kg_551": str(float(par.get("PesoNeto") or 0)),
"peso_neto_kg_551": str(float(par.get("ValorAduanasMN") or 0)),
"numero_remesa_551": str(num_remesa or " "),
"numero_partida_remesa_551": str(num_partida_remesa),
"cantidad_alterna_551": float(par.get("CantidadAlterna") or 0),
"unidad_medida_alterna_551": str(par.get("UnidadAlterna", " ")),
"descripcion_ingles_551": " ",
"identificador_sistema_scaii_scaf_551": "SCAF",
"vu_submodelo_551": " ",
"vu_serie_551": " ",
"valor_unitario_aduana_551": str(float(par.get("CostoUnitarioME") or 0)),
"valor_dolares_551": str(float(par.get("ValorDolares") or 0)),
"unidad_medida_oma_551": oma_final,
"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

16
registros/scaf/reg_554.py Normal file
View File

@@ -0,0 +1,16 @@
from typing import Dict, Any
class Generador554SCAF:
@staticmethod
def preparar_json(iden: Dict[str, Any], id_partida: int) -> Dict[str, Any]:
"""
Lógica para Registro 554 (Identificadores de Partidas) - SCAF.
"""
registro = {
"tipo_caso_554": iden.get("identificador", " "),
"complemento_caso_1_554": iden.get("complemento_1", " "),
"complemento_caso_2_554": iden.get("complemento_2", " "),
"complemento_caso_3_554": iden.get("complemento_3", " "),
"id_partida": id_partida
}
return registro

17
registros/scaf/reg_558.py Normal file
View File

@@ -0,0 +1,17 @@
from typing import Dict, Any
class Generador558SCAF:
@staticmethod
def preparar_json(id_partida: int, observaciones: str) -> Dict[str, Any]:
"""
Lógica para Registro 558 (Observaciones de Partidas) - SCAF.
"""
if not observaciones:
observaciones = " "
obs_limpia = observaciones.replace("\r", "").replace("\n", "").replace("\t", "").strip()
return {
"observaciones_558": obs_limpia[:1000],
"id_partida": id_partida
}

View File

@@ -0,0 +1,62 @@
from typing import Dict, Any
class Generador501:
@staticmethod
def preparar_json(datos_pedi: Dict[str, Any], totales: Dict[str, float],
datos_cliente: Dict[str, Any], total_trailers: int) -> Dict[str, Any]:
"""
Unifica la lógica de Get_Regs501, _Def y _Expo en una sola estructura.
"""
# Lógica de Destino/Zona (Opcional)
destino = datos_pedi.get("Destino", "")
codigo_destino = "9" if "Interior" in destino else "7" if any(x in destino for x in ["Región", "Franja"]) else ""
# Clave del Importador/Exportador
clave_transfer = datos_cliente.get("CLAVETRANSFER", "").strip() if datos_cliente else ""
# Construcción del Registro 501
registro = {
"tipo_operacion_501": datos_pedi.get("Tipo") or "",
"clave_documento_501": datos_pedi.get("Clave") or "",
"numero_pedimento_501": (datos_pedi.get("Pedimento") or "")[8:15],
"codigo_importador_exportador_501": clave_transfer if clave_transfer else " ",
"monto_fletes_501": totales["flete"],
"monto_seguros_501": totales["seguros"],
"costo_embalajes_501": totales["embalajes"],
"cargos_incrementables_501": totales["otros_incrementa"],
"cargos_deducibles_501": 0,
"peso_total_bruto_501": totales["peso_bruto"],
"total_bultos_501": totales["bultos"],
"metodo_transporte_entrada_501": str(datos_pedi.get("TransE") or ""),
"metodo_transporte_arribo_501": str(datos_pedi.get("TransA") or ""),
"metodo_transporte_salida_501": str(datos_pedi.get("TransS") or ""),
"codigo_destino_zona_501": codigo_destino,
"numero_referencia_501": "",
"moneda_costos_incrementables_501": "ME", # O tomar de la primera factura
"codigo_aduana_despacho_501": str(datos_pedi.get("Aduana") or ""),
"numero_patente_501": (datos_pedi.get("Pedimento") or "")[3:7],
"origenPedimento": 1,
"total_vehiculos_501": total_trailers,
"rfc_importador_exportador_501": " ",
"curp_importador_exportador_501": " ",
"nombre_completo_importador_exportador_501": " ",
"calle_direccion_importador_exportador_501": " ",
"numero_exterior_direccion_importador_exportador_501": " ",
"numero_interior_direccion_importador_exportador_501": " ",
"colonia_direccion_importador_exportador_501": " ",
"localidad_direccion_importador_exportador_501": " ",
"municipio_direccion_importador_exportador_501": " ",
"estado_direccion_importador_exportador_501": " ",
"pais_direccion_importador_exportador_501": " ",
"codigo_postal_direccion_importador_exportador_501": " ",
"monto_fletes_decrementables_501": 0,
"monto_seguros_decrementables_501": 0,
"monto_carga_decrementables_501": 0,
"monto_descarga_decrementables_501": 0,
"monto_otros_decrementables_501": 0,
"MarcaBulto1_501": "S/N",
"MarcaBulto2_501": "S/N"
}
return registro

View File

@@ -0,0 +1,20 @@
from typing import Dict, Any
class Generador502SCAII:
@staticmethod
def preparar_json(id_pedimento: int, datos_transporte: Dict[str, Any]) -> Dict[str, Any]:
"""
Lógica para Registro 502 (Transporte) - SCAII.
"""
# En WinDev: GTrailers.NUMEROPLACAS si existe, si no " "
placas = datos_transporte.get("NUMEROPLACAS", " ") if datos_transporte else " "
pais = datos_transporte.get("PAIS", " ") if datos_transporte else " "
registro = {
"id_pedimento": id_pedimento,
"numero_placas_502": placas,
"codigo_pais_transporte_502": pais
}
return registro

View File

@@ -0,0 +1,21 @@
from typing import Dict, Any
class Generador503SCAII:
@staticmethod
def preparar_json(id_pedimento: int, factura_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Lógica para Registro 503 (Guías) - SCAII.
"""
num_niu = factura_data.get("num_niu", " ")
tipo_guia = factura_data.get("tipo_guia", " ")
total_guias = factura_data.get("total_guias", 0)
registro = {
"id_pedimento": id_pedimento,
"numero_guia_manifiesto_embarque_503": num_niu if num_niu else " ",
"identificador_guia_503": tipo_guia if tipo_guia else " ",
"total_guias_503": total_guias if total_guias else 0
}
return registro

View File

@@ -0,0 +1,35 @@
from typing import List, Dict, Any
class Generador504SCAII:
@staticmethod
def preparar_listado_json(id_pedimento: int, contenedores_raw: str) -> List[Dict[str, Any]]:
"""
Procesa el string de contenedores (CONT1|TIPO1,CONT2|TIPO2)
y genera una lista de registros para la API.
"""
registros = []
if not contenedores_raw or contenedores_raw.strip() == "":
return registros
# En WinDev: FOR EACH STRING sContenedores OF DS_Facturas.c1 SEPARATED BY ","
bloques = contenedores_raw.split(",")
for bloque in bloques:
if not bloque.strip():
continue
# En WinDev: (sNumeroContenedor,sTipoContenedor) = StringSplit(sContenedores,"|")
partes = bloque.split("|")
num_contenedor = partes[0].strip() if len(partes) > 0 else ""
tipo_contenedor = partes[1].strip() if len(partes) > 1 else " "
if num_contenedor:
registros.append({
"id_pedimento_501": id_pedimento,
"numero_contenedor_504": num_contenedor,
"tipo_contenedor_504": tipo_contenedor,
"status_504": "1"
})
return registros

106
registros/scaii/reg_505.py Normal file
View File

@@ -0,0 +1,106 @@
from typing import Dict, Any, Optional
class Generador505SCAII:
@staticmethod
def preparar_json(fac: Dict[str, Any], id_pedimento: int, datos_empresa: Dict[str, Any],
datos_prov: Dict[str, Any], datos_trans: Optional[Dict[str, Any]],
tipo_moneda_global: str, tc_global: float) -> Dict[str, Any]:
"""
Lógica para Registro 505 (Facturas) - SCAII.
"""
# 1. Lógica de Moneda y Valores
clave_moneda = fac.get("ClaveMoneda", "")
valor_mc = fac.get("ValorMC", 0)
valor_me = fac.get("ValorImpoME", 0)
if tipo_moneda_global == "ME":
clave_moneda = "USD"
valor_mc = valor_me
elif tipo_moneda_global == "MN":
clave_moneda = "MXP"
# Asumiendo lógica estándar si no es FP
valor_mc = fac.get("ValorImpoMN", 0)
# 2. Lógica de Método de Valoración (Mapping)
met_val_raw = fac.get("MetodoValoracion", 0)
met_val_map = {1: "VTM", 2: "VMI", 3: "VMS", 4: "VPU", 5: "VR", 6: "A78"}
met_val_str = f"VALADU.{met_val_map.get(met_val_raw, met_val_raw)}"
# 3. Incoterm
incoterm = fac.get("Incoterm", "")
incoterm_full = incoterm if incoterm else ""
# 4. Otros mapeos
obs = fac.get("Observaciones", "").replace("\r", "").replace("\n", "").replace("\t", "")
contenedores = fac.get("ContenedoresTipo", "").replace(",", "|")
registro = {
"numero_factura_505": fac.get("NumFactura"),
"fecha_facturacion_505": str(fac.get("Fecha"))[:10], # YYYY-MM-DD
"termino_facturacion_505": incoterm if incoterm else "",
"moneda_facturacion_505": clave_moneda if clave_moneda else "",
"valor_moneda_facturacion_505": str(float(valor_mc or 0)),
"valor_dolares_505": str(float(valor_me or 0)),
"codigo_proveedor_comprador_505": datos_prov.get("CLAVETRANSFER", ""),
"e_document_505": fac.get("EDocument", ""),
"serie_certificado_cove_505": fac.get("NumCertificado", ""),
"firma_electronica_cove_505": fac.get("FirmaElectronica", ""),
"observaciones_factura_505": obs if obs else "",
"numero_economico_505": contenedores if contenedores else "",
"tipo_contenedor_numero_economico_505": str(fac.get("Remesa") or ""),
"numero_contenedor_505": contenedores if contenedores else "",
"tipo_contenedor_505": str(fac.get("Remesa") or ""),
"numero_remesa_505": str(fac.get("Remesa") or ""),
"tipo_operacion_505": "", # Se llenará en el motor
"certificado_origen_505": "S" if fac.get("FungeComoCO") else "N",
"numero_certificado_origen_505": "",
"numero_exportador_confiable_505": (datos_empresa.get("NUMDEEXPORTADORCONFIABLE") or "") if datos_empresa else "",
"incoterm_505": incoterm_full,
"metodo_valoracion_505": met_val_str,
"vinculacion_505": str(datos_prov.get("VINCULACION") or "0"),
"subdivision_505": "S" if fac.get("Subdivision") else "N",
"tipo_cambio_usd_505": str(float(fac.get("TipoCambio") or 0)),
"se_genero_en_contingencia_505": "S" if fac.get("ModoContingencia") else "N",
"operacion_cove_505": fac.get("NumOperacionVU") or "",
"transportista_clave_505": fac.get("Transportista") or "",
"rfc_505": (datos_trans.get("RFC") or "") if datos_trans else "",
"caat_505": (datos_trans.get("CODIGOCAAT") or "") if datos_trans else "",
"id_pedimento": id_pedimento,
"PaisFactura_505": datos_prov.get("PAIS", ""),
"Bultos_cantidad_505": str(fac.get("CantBultos", 0)),
"provcomp_numregidtrib_505": " ",
"provcomp_rfc_505": " ",
"provcomp_curp_505": " ",
"provcomp_nombre_505": " ",
"provcomp_calle_505": " ",
"provcomp_numero_exterior_505": " ",
"provcomp_numero_interior_505": " ",
"provcomp_colonia_505": " ",
"provcomp_localidad_505": " ",
"provcomp_municipio_505": " ",
"provcomp_estado_505": " ",
"provcomp_pais_505": " ",
"provcomp_codigo_postal_505": " ",
"destinatario_clave_505": " ",
"destinatario_numregidtrib_505": " ",
"destinatario_rfc_505": " ",
"destinatario_curp_505": " ",
"destinatario_nombre_505": " ",
"destinatario_calle_505": " ",
"destinatario_numero_exterior_505": " ",
"destinatario_numero_interior_505": " ",
"destinatario_colonia_505": " ",
"destinatario_localidad_505": " ",
"destinatario_referencia_505": " ",
"destinatario_municipio_505": " ",
"destinatario_estado_505": " ",
"destinatario_pais_505": " ",
"destinatario_codigo_postal_505": " ",
"uuid_505": " ",
"path_pdf_cfdi_505": " ",
"path_xml_cfdi_505": " ",
"estatus_cove_505": " "
}
return registro

144
registros/scaii/reg_551.py Normal file
View File

@@ -0,0 +1,144 @@
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 ""
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),
"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

View File

@@ -0,0 +1,23 @@
from typing import Dict, Any
class Generador554SCAII:
@staticmethod
def preparar_json(iden: Dict[str, Any], id_partida: int) -> Dict[str, Any]:
"""
Lógica para Registro 554 (Identificadores de Partidas) - SCAII.
"""
registro = {
"tipo_caso_554": iden.get("identificador", " "),
"complemento_caso_1_554": iden.get("complemento_1", " "),
"complemento_caso_2_554": iden.get("complemento_2", " "),
"complemento_caso_3_554": iden.get("complemento_2", " "), # WinDev repite C3 con C3, pero aquí mapeamos C1, C2, C3
"id_partida": id_partida
}
# En el código WinDev: FormURL_Reg554.complemento_caso_3_554 = DS_IntPar.C3
# Pero DS_IntPar.C3 es COMPLEMENTO2. DS_IntPar.C4 es COMPLEMENTO3.
# Ajustaré para que use el complemento_3 real si existe.
if iden.get("complemento_3"):
registro["complemento_caso_3_554"] = iden.get("complemento_3")
return registro

View File

@@ -0,0 +1,18 @@
from typing import Dict, Any
class Generador558:
@staticmethod
def preparar_json(id_partida: int, observaciones: str) -> Dict[str, Any]:
"""
Lógica para Registro 558 (Observaciones de Partidas).
Limpia caracteres especiales como saltos de línea y tabuladores.
"""
if not observaciones:
observaciones = " "
obs_limpia = observaciones.replace("\r", "").replace("\n", "").replace("\t", "").strip()
return {
"observaciones_558": obs_limpia[:1000],
"id_partida": id_partida
}