import time import os from typing import List, Dict, Any, Tuple, Optional from registros.scaii.reg_501 import Generador501 as Gen501SCAII from registros.scaf.reg_501 import Generador501SCAF as Gen501SCAF from registros.scaii.reg_502 import Generador502SCAII as Gen502SCAII from registros.scaf.reg_502 import Generador502SCAF as Gen502SCAF from registros.scaii.reg_503 import Generador503SCAII as Gen503SCAII from registros.scaf.reg_503 import Generador503SCAF as Gen503SCAF from registros.scaii.reg_504 import Generador504SCAII as Gen504SCAII from registros.scaf.reg_504 import Generador504SCAF as Gen504SCAF from registros.scaii.reg_505 import Generador505SCAII as Gen505SCAII from registros.scaf.reg_505 import Generador505SCAF as Gen505SCAF from registros.scaii.reg_551 import Generador551SCAII as Gen551SCAII from registros.scaf.reg_551 import Generador551SCAF as Gen551SCAF from registros.scaii.reg_554 import Generador554SCAII as Gen554SCAII from registros.scaf.reg_554 import Generador554SCAF as Gen554SCAF from registros.scaii.reg_558 import Generador558 as Gen558SCAII from registros.scaf.reg_558 import Generador558SCAF as Gen558SCAF from logger_utils import log class MotorSincronizacion: def __init__(self, gestor_db, cliente_api): self.db = gestor_db self.api = cliente_api self.tipo_moneda_global = "ME" self.callback_progreso = None self.callback_fila = None self.callback_estado_texto = None self.callback_actualizar_fila = None def proceso_principal(self): """Metodo de entrada robusto para la Interfaz""" config_datos = self.db.config pedimento = config_datos.get("pedimento") sistema = config_datos.get("sistema", "SCAII") log.info(f"Iniciando proceso principal para Pedimento: {pedimento}, Sistema: {sistema}") if getattr(self, "callback_estado_texto", None): self.callback_estado_texto("Iniciando conexiones...") # 1. Conectar a la Base de Datos if not self.db.conectar(): if getattr(self, "callback_estado_texto", None): self.callback_estado_texto("Error: Fallo conexión SQL Server.") return False # 1b. Leer credenciales RPA de GEmpresa (igual que WinDev en producción) # Si vienen por parámetro (/RPAUser), se usan esas. Si no, se leen de la BD. if not self.api.usuario or not self.api.password: rpa_user, rpa_pass = self.db.obtener_credenciales_rpa() if rpa_user and rpa_pass: self.api.usuario = rpa_user self.api.password = rpa_pass else: if getattr(self, "callback_estado_texto", None): self.callback_estado_texto("Error: No hay credenciales RPA en GEmpresa.") log.error("No se encontraron credenciales RPA en GEmpresa. Configura NOMBREBDINTER y CLAVEPREVALIDADOR.") return False # 2. Obtener Token de la API if not self.api.obtener_token(): if getattr(self, "callback_estado_texto", None): self.callback_estado_texto("Error: Fallo de autenticación API.") return False if not pedimento: return False return self.ejecutar_logica_generacion(pedimento, sistema) def ejecutar_logica_generacion(self, pedimento: str, sistema: str) -> bool: """Orquestador Gen_Regs""" # 1. Buscar pedimento (Intento 1: Tal cual viene) datos_pedi = self.db.obtener_datos_pedimento(pedimento, sistema) # 2. Buscar pedimento (Intento 2: Sin guiones) if not datos_pedi and "-" in pedimento: log.info("Intento 2: Buscando pedimento sin guiones...") datos_pedi = self.db.obtener_datos_pedimento(pedimento.replace("-", ""), sistema) # 3. Buscar pedimento (Intento 3: Solo los últimos 7 dígitos - El número consecutivo) if not datos_pedi and len(pedimento) >= 7: solo_consecutivo = pedimento.replace("-", "").replace(" ", "")[-7:] log.info(f"Intento 3: Buscando solo por consecutivo final: {solo_consecutivo}...") datos_pedi = self.db.obtener_datos_pedimento(solo_consecutivo, sistema) if not datos_pedi: if getattr(self, "callback_estado_texto", None): self.callback_estado_texto(f"Error: Pedimento {pedimento} no encontrado.") return False tipo_op = datos_pedi.get("Tipo") regimen = datos_pedi.get("Regimen", "") if getattr(self, "callback_estado_texto", None): self.callback_estado_texto(f"Pedimento {tipo_op}/{regimen} encontrado. Buscando facturas...") log.info(f"Pedimento encontrado: {tipo_op}/{regimen}. Buscando facturas...") datos_empresa = self.db.obtener_datos_empresa(sistema) facturas = self.db.obtener_detalle_facturas(pedimento, sistema, regimen, tipo_op) if not facturas: if getattr(self, "callback_estado_texto", None): self.callback_estado_texto(f"Aviso: 0 facturas para {pedimento}") return False self.facturas_cache = facturas totales = self.calcular_totales(facturas, self.tipo_moneda_global) id_proveedor = facturas[0].get("IdProveedor") datos_cliente = self.db.obtener_datos_proveedor(id_proveedor, sistema) total_trailers = self.db.obtener_total_vehiculos(pedimento, sistema) gen_501 = Gen501SCAII if sistema == "SCAII" else Gen501SCAF json_501 = gen_501.preparar_json(datos_pedi, totales, datos_cliente, total_trailers) id_pedimento_api = self.api.enviar_registro_api("registro501", json_501) if not id_pedimento_api: return False num_trailer = facturas[0].get("NumTrailer") datos_trailer = self.db.obtener_datos_trailer(num_trailer, sistema) gen_502 = Gen502SCAII if sistema == "SCAII" else Gen502SCAF self.api.enviar_registro_api("registro502", gen_502.preparar_json(id_pedimento_api, datos_trailer)) gen_503 = Gen503SCAII if sistema == "SCAII" else Gen503SCAF self.api.enviar_registro_api("registro503", gen_503.preparar_json(id_pedimento_api, facturas[0])) log.info(f"Procesando {len(facturas)} facturas...") for idx, fac in enumerate(facturas): self.idx_actual = idx if self.callback_fila: self.callback_fila(pedimento, str(fac.get("NumFactura")), str(fac.get("Remesa")), fac, "Procesando...") time.sleep(0.02) gen_504 = Gen504SCAII if sistema == "SCAII" else Gen504SCAF for j504 in gen_504.preparar_listado_json(id_pedimento_api, fac.get("ContenedoresTipo")): self.api.enviar_registro_api("registro504", j504) id_prov = fac.get("IdProveedor") prov_data = self.db.obtener_datos_proveedor(id_prov, sistema) trans_data = self.db.obtener_datos_transporte(fac.get("Transportista"), sistema) gen_505 = Gen505SCAII if sistema == "SCAII" else Gen505SCAF json_505 = gen_505.preparar_json(fac, id_pedimento_api, datos_empresa, prov_data, trans_data, self.tipo_moneda_global, 1.0) json_505["tipo_operacion_505"] = tipo_op id_factura_api = self.api.enviar_registro_api("registro505", json_505) if id_factura_api: self.ejecutar_generacion_partidas(fac, id_factura_api, sistema, tipo_op, regimen, json_505) if getattr(self, "callback_actualizar_fila", None): self.callback_actualizar_fila(idx, "Enviada correctamente") else: if getattr(self, "callback_actualizar_fila", None): self.callback_actualizar_fila(idx, "Error enviando") if getattr(self, "callback_progreso", None): self.callback_progreso((idx + 1) / len(facturas)) time.sleep(0.05) log.info("Sincronización finalizada con éxito.") return True def calcular_totales(self, facturas: List[Dict[str, Any]], moneda_objetivo: str) -> Dict[str, float]: totales = {"peso_bruto": 0.0, "bultos": 0.0, "flete": 0.0, "seguros": 0.0, "embalajes": 0.0, "otros_incrementa": 0.0} for fac in facturas: totales["peso_bruto"] += float(fac.get("PesoBruto") or 0) totales["bultos"] += float(fac.get("CANTBULTOS") or 0) tc = float(fac.get("TipoCambio") or 1.0) m_fac = fac.get("ClaveMoneda") def conv(v): val = float(v or 0) if moneda_objetivo == "ME": return val if m_fac == "ME" else val / tc return val * tc if m_fac == "MN" else val totales["flete"] += conv(fac.get("Flete")) totales["seguros"] += conv(fac.get("Seguros")) totales["embalajes"] += conv(fac.get("Embalajes")) totales["otros_incrementa"] += conv(fac.get("OtrosIncrementa")) return totales def realizar_conversion_tarifa(self, cant_c: float, um_c: str, frac: str, peso: float, sist: str) -> Tuple[float, str]: cl_um = self.db.obtener_um_tarifa_fraccion(frac, sist) if not cl_um: return cant_c, "" if str(cl_um) == "1": return peso, "1" um_s = self.db.obtener_unidad_aduana_scaii(clave_aduana=cl_um) if not um_s or um_s == um_c: return cant_c, str(cl_um) t, f = self.db.obtener_conversion_unidades(um_c, um_s) cant_t = cant_c * f if t == "M" else (cant_c / f if f != 0 else cant_c) return cant_t, str(cl_um) def ejecutar_generacion_partidas(self, fac: Dict[str, Any], id_f: int, sist: str, t_op: str, reg: str, j505: Dict[str, Any]): partidas = self.db.obtener_partidas_factura(fac.get("Consecutivo"), sist, t_op, reg) if not partidas: return for i, par in enumerate(partidas, start=1): if getattr(self, "callback_estado_texto", None): self.callback_estado_texto(f"Enviando Partida {i}/{len(partidas)}...") # Obtener datos de la unidad de medida para el mapeo numérico um_raw = par.get("UnidadMedida", "") um_data = self.db.obtener_datos_unidad_medida(um_raw, sist) if um_raw else None ct, ut = self.realizar_conversion_tarifa(float(par.get("Cantidad") or 0), par.get("UnidadMedida", ""), par.get("Fraccion", ""), float(par.get("PesoNeto") or 0), sist) if sist == "SCAII": j551 = Gen551SCAII.preparar_json(par, id_f, {}, um_data, str(fac.get("MetodoValoracion") or ""), j505["moneda_facturacion_505"], fac.get("Remesa"), i, fac.get("TipoMoneda", "ME")) else: j551 = Gen551SCAF.preparar_json(par, id_f, {}, um_data, str(fac.get("MetodoValoracion") or ""), j505["moneda_facturacion_505"], fac.get("Remesa"), i) j551["cantidad_tarifa_551"] = ct if ut: j551["unidad_medida_tarifa_551"] = ut j551["numero_partida_remesa_551"] = str(par.get("Linea", i)) id_p = self.api.enviar_registro_api("registro551", j551) if id_p: mod = ("SCAII" if sist == "SCAII" else "SCAF") + ("-IT" if t_op == "I" and reg != "IMD" else ("-ID" if t_op == "I" else ("-E" if sist == "SCAII" else "-ER"))) for iden in self.db.obtener_identificadores_partida(fac.get("Consecutivo"), par.get("Linea"), mod): self.api.enviar_registro_api("registro554", (Gen554SCAII if sist == "SCAII" else Gen554SCAF).preparar_json(iden, id_p)) if par.get("InfoAdic", "").strip(): self.api.enviar_registro_api("registro558", (Gen558SCAII if sist == "SCAII" else Gen558SCAF).preparar_json(id_p, par.get("InfoAdic"))) time.sleep(0.01)