From e9152a8cd1c764426293a8d220ebfc24802e8a34 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Thu, 9 Jul 2026 14:15:06 -0500 Subject: [PATCH 1/6] fix: enviar linea_scaii_551 en partidas desde campo LOTE Las partidas (registro551) no incluian linea_scaii_551. Se agrega el campo al payload de ambos generadores (SCAII y SCAF) tomando su valor del campo LOTE de las tablas de partidas, y se selecciona LOTE en los querys de partidas para impo/expo, definitiva y temporal. Co-Authored-By: Claude Opus 4.8 (1M context) --- gestor_base_datos.py | 12 ++++++------ registros/scaf/reg_551.py | 1 + registros/scaii/reg_551.py | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gestor_base_datos.py b/gestor_base_datos.py index 240c838..20fc4ad 100644 --- a/gestor_base_datos.py +++ b/gestor_base_datos.py @@ -174,8 +174,8 @@ class GestorBaseDatos: col_fac = "FACCMIMPODEF" if regimen == "IMD" else "FACTURAIMPO" query = f""" - SELECT p.Consecutivo AS Consecutivo, p.{col_linea} AS Linea, p.NumParte AS NumParte, - p.CantImpo AS Cantidad, p.ValorImpoMC AS ValorComercial, p.UniMed AS UnidadMedida, + SELECT p.Consecutivo AS Consecutivo, p.{col_linea} AS Linea, p.LOTE AS Lote, p.NumParte AS NumParte, + p.CantImpo AS Cantidad, p.ValorImpoMC AS ValorComercial, p.UniMed AS UnidadMedida, p.FraccionImpo AS Fraccion, p.PesoNetoKGS AS PesoNeto, p.TipoFraccImpo AS TipoFracc, p.CostoUnitarioME AS CostoUnitarioME, p.ValorImpoME AS ValorDolares, p.ValorImpoMN AS ValorPesos, p.PaisOrigen AS PaisOrigen, p.Sector AS Sector, @@ -196,7 +196,7 @@ class GestorBaseDatos: """ else: # EXPO SCAII query = f""" - SELECT p.FacturaExpo AS Consecutivo, p.Linea AS Linea, p.NumParte AS NumParte, + SELECT p.FacturaExpo AS Consecutivo, p.Linea AS Linea, p.LOTE AS Lote, p.NumParte AS NumParte, p.CantExpo AS Cantidad, p.ValorTotalMN AS ValorComercial, p.UniMed AS UnidadMedida, p.FraccionExpo AS Fraccion, p.PesoNetoKGS AS PesoNeto, p.TipoFracExpo AS TipoFracc, p.CostoUnitarioME AS CostoUnitarioME, p.ValorTotalME AS ValorDolares, @@ -224,8 +224,8 @@ class GestorBaseDatos: col_vmn = "ValorMN" if regimen == "IMD" else "ValorImpoMN" query = f""" - SELECT p.Consecutivo AS Consecutivo, p.{col_linea} AS Linea, p.NumParte AS NumParte, - p.{col_cant} AS Cantidad, p.ValorImpoMC AS ValorComercial, p.UnidadMedida AS UnidadMedida, + SELECT p.Consecutivo AS Consecutivo, p.{col_linea} AS Linea, p.LOTE AS Lote, p.NumParte AS NumParte, + p.{col_cant} AS Cantidad, p.ValorImpoMC AS ValorComercial, p.UnidadMedida AS UnidadMedida, p.Fraccion AS Fraccion, p.PesoNetoKGS AS PesoNeto, p.TipoFraccion AS TipoFracc, p.CostoUnitarioDlls AS CostoUnitarioME, p.{col_vme} AS ValorDolares, p.{col_vmn} AS ValorPesos, p.PaisOrigen AS PaisOrigen, p.Sector AS Sector, @@ -245,7 +245,7 @@ class GestorBaseDatos: """ else: # EXPO SCAF query = """ - SELECT p.Consecutivo AS Consecutivo, p.LineaExpo AS Linea, p.FacturaImpo AS NumParte, + SELECT p.Consecutivo AS Consecutivo, p.LineaExpo AS Linea, p.LOTE AS Lote, p.FacturaImpo AS NumParte, p.CantExpo AS Cantidad, 0 AS ValorComercial, p.UnidadMedida AS UnidadMedida, p.FraccionExpo AS Fraccion, p.PesoNetoKGS AS PesoNeto, p.TipoFraccion AS TipoFracc, p.CostoUnitarioDlls AS CostoUnitarioME, p.ValorExpoME AS ValorDolares, diff --git a/registros/scaf/reg_551.py b/registros/scaf/reg_551.py index 262d1d0..a2bc788 100644 --- a/registros/scaf/reg_551.py +++ b/registros/scaf/reg_551.py @@ -82,6 +82,7 @@ class Generador551SCAF: "numero_remesa_551": str(num_remesa or " "), "numero_partida_remesa_551": str(num_partida_remesa), + "linea_scaii_551": str(par.get("Lote") or " "), # Lote de la tabla de partidas (impo/expo/def/temp) "cantidad_alterna_551": float(par.get("CantidadAlterna") or 0), "unidad_medida_alterna_551": str(par.get("UnidadAlterna", " ")), "descripcion_ingles_551": " ", diff --git a/registros/scaii/reg_551.py b/registros/scaii/reg_551.py index e650bbf..e42cc38 100644 --- a/registros/scaii/reg_551.py +++ b/registros/scaii/reg_551.py @@ -125,6 +125,7 @@ class Generador551SCAII: "numero_remesa_551": str(num_remesa or " "), "numero_partida_remesa_551": str(par.get("Linea") or num_partida_remesa), + "linea_scaii_551": str(par.get("Lote") or " "), # Lote de la tabla de partidas (impo/expo/def/temp) "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], -- 2.49.1 From 797572d3d794981c7f9d088c51942461866e3115 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Fri, 10 Jul 2026 10:25:53 -0500 Subject: [PATCH 2/6] chore: ignorar venv32 y archivos de log Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 82d037e..30e920d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ venv/ +venv32/ build/ dist/ __pycache__/ *.pyc *.spec.bak +*.log -- 2.49.1 From 4347925cc357d42bef5e5fd231fba616a4f0f1ae Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Fri, 10 Jul 2026 10:25:53 -0500 Subject: [PATCH 3/6] fix: actualizar URL del API a anexo46 /api/v1 El endpoint viejo (104.192.7.152:3529) dejo de responder. La API se movio a https://anexo46.aduanasoft.com/api/v1/. Todas las rutas que usa el cliente (login, registro501..registro558) existen bajo ese prefijo. Co-Authored-By: Claude Opus 4.8 (1M context) --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index a75f835..e41b2eb 100644 --- a/config.py +++ b/config.py @@ -60,7 +60,7 @@ class ConfiguracionApp: log.error("FALTA PARÁMETRO: No se recibió DBServer. El proceso no puede continuar.") # 4. Lógica de URL Dinámica (Solo SCAII o SCAF) - self.datos["url_api"] = "https://104.192.7.152:3529/" + self.datos["url_api"] = "https://anexo46.aduanasoft.com/api/v1/" # 5. Valores por defecto finales if not self.datos["puerto"] or self.datos["puerto"] == 0: -- 2.49.1 From a8db143339047e7b357996278b4bf4cb5d9e200c Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Fri, 10 Jul 2026 10:25:53 -0500 Subject: [PATCH 4/6] fix: empaquetar datos de flet en el exe (icons.json) PyInstaller no incluia flet/controls/material/icons.json, por lo que al crear iconos en la GUI se lanzaba FileNotFoundError y las filas no se pintaban. Se recolectan flet y flet_desktop con collect_all. Tambien se elimina la ruta version= que apuntaba a un temporal inexistente. Co-Authored-By: Claude Opus 4.8 (1M context) --- SCAII_Sincronizador_32bit.spec | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/SCAII_Sincronizador_32bit.spec b/SCAII_Sincronizador_32bit.spec index b08ff29..104ca3c 100644 --- a/SCAII_Sincronizador_32bit.spec +++ b/SCAII_Sincronizador_32bit.spec @@ -1,12 +1,19 @@ # -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_all + +# flet necesita sus archivos de datos (icons.json, etc.); el hook por defecto no los trae completos +datas, binaries, hiddenimports = [], [], [] +for pkg in ('flet', 'flet_desktop'): + d, b, h = collect_all(pkg) + datas += d; binaries += b; hiddenimports += h a = Analysis( ['main.py'], pathex=[], - binaries=[], - datas=[], - hiddenimports=[], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], @@ -35,5 +42,4 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, - version='C:\\Users\\PC\\AppData\\Local\\Temp\\6dd1fff1-457d-40b5-9d54-2ef8d8c29463', ) -- 2.49.1 From db75d2b97ab9bbbfd5a26fa8051c9d6e1d466a43 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Fri, 10 Jul 2026 10:25:53 -0500 Subject: [PATCH 5/6] fix: registrar errores al pintar filas en la GUI agregar_fila envolvia la construccion de la fila sin manejo de error, y un fallo (p.ej. icons.json ausente) se perdia en silencio dejando la tabla vacia. Se agrega try/except con log y traza de la fila agregada. Co-Authored-By: Claude Opus 4.8 (1M context) --- interfaz.py | 112 +++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/interfaz.py b/interfaz.py index 353ad57..f301667 100644 --- a/interfaz.py +++ b/interfaz.py @@ -1,6 +1,7 @@ import flet as ft import threading from typing import Optional +from logger_utils import log class InterfazFlet: def __init__(self, cliente_sinc=None): @@ -72,66 +73,71 @@ class InterfazFlet: # ------------------------------------------------------------------ # def agregar_fila(self, pedimento, factura, remesa, datos_completos, estatus): + log.info(f"[UI] agregar_fila llamado (page={'OK' if self.page else 'None'}): pedi={pedimento} fac={factura}") async def _do(): - # Panel de detalles (oculto inicialmente) - campos_relevantes = { - "NumFactura": "Factura", - "Remesa": "Remesa", - "IdProveedor": "ID Proveedor", - "ValorDolares": "Valor Dólares", - "PesoBruto": "Peso Bruto", - "Transportista": "Transportista", - "NumTrailer": "Caja/Trailer", - "Incoterm": "Incoterm", - "Consecutivo": "Folio Interno" - } + try: + # Panel de detalles (oculto inicialmente) + campos_relevantes = { + "NumFactura": "Factura", + "Remesa": "Remesa", + "IdProveedor": "ID Proveedor", + "ValorDolares": "Valor Dólares", + "PesoBruto": "Peso Bruto", + "Transportista": "Transportista", + "NumTrailer": "Caja/Trailer", + "Incoterm": "Incoterm", + "Consecutivo": "Folio Interno" + } - filas_detalles = [ - ft.Row([ - ft.Text(f"{etiqueta}:", weight="bold", width=120, size=12, color=ft.Colors.GREY_700), - ft.Text(str(datos_completos.get(clave, "N/A")), size=12, color=ft.Colors.BLUE_700), - ], spacing=10) - for clave, etiqueta in campos_relevantes.items() - ] + filas_detalles = [ + ft.Row([ + ft.Text(f"{etiqueta}:", weight="bold", width=120, size=12, color=ft.Colors.GREY_700), + ft.Text(str(datos_completos.get(clave, "N/A")), size=12, color=ft.Colors.BLUE_700), + ], spacing=10) + for clave, etiqueta in campos_relevantes.items() + ] - panel_detalles = ft.Container( - content=ft.Column(filas_detalles, spacing=6), - visible=False, # <-- oculto por defecto - bgcolor=ft.Colors.BLUE_50, - padding=ft.padding.only(left=30, top=10, right=20, bottom=10), - border=ft.border.only( - bottom=ft.BorderSide(1, ft.Colors.BLUE_100), - left=ft.BorderSide(3, ft.Colors.BLUE_400), # acento visual izquierdo - ), - ) + panel_detalles = ft.Container( + content=ft.Column(filas_detalles, spacing=6), + visible=False, # <-- oculto por defecto + bgcolor=ft.Colors.BLUE_50, + padding=ft.padding.only(left=30, top=10, right=20, bottom=10), + border=ft.border.only( + bottom=ft.BorderSide(1, ft.Colors.BLUE_100), + left=ft.BorderSide(3, ft.Colors.BLUE_400), # acento visual izquierdo + ), + ) - icono_expand = ft.Icon(ft.Icons.KEYBOARD_ARROW_DOWN, size=16, color=ft.Colors.GREY_400) + icono_expand = ft.Icon(ft.Icons.KEYBOARD_ARROW_DOWN, size=16, color=ft.Colors.GREY_400) - def toggle_detalles(e, p=panel_detalles, i=icono_expand): - p.visible = not p.visible - i.name = ft.Icons.KEYBOARD_ARROW_UP if p.visible else ft.Icons.KEYBOARD_ARROW_DOWN + def toggle_detalles(e, p=panel_detalles, i=icono_expand): + p.visible = not p.visible + i.name = ft.Icons.KEYBOARD_ARROW_UP if p.visible else ft.Icons.KEYBOARD_ARROW_DOWN + self.page.update() + + fila_header = ft.Container( + content=ft.Row([ + ft.Text(pedimento, width=150, size=12, color=ft.Colors.BLACK), + ft.Text(factura, width=120, size=12, color=ft.Colors.BLACK), + ft.Text(remesa, width=80, size=12, color=ft.Colors.BLACK), + ft.Text(estatus, expand=True, size=12, color=ft.Colors.BLUE_700, weight="bold"), + icono_expand, + ]), + padding=ft.padding.symmetric(horizontal=20, vertical=8), + border=ft.border.only(bottom=ft.BorderSide(1, ft.Colors.GREY_100)), + on_click=toggle_detalles, + ink=True, + tooltip="Clic para ver detalles" + ) + + # Agrupamos header + panel en una Column + fila_completa = ft.Column([fila_header, panel_detalles], spacing=0) + self.lista_resultados.controls.append(fila_completa) self.page.update() + log.info(f"[UI] fila agregada OK. Total filas en tabla: {len(self.lista_resultados.controls)}") + except Exception as ex: + log.error(f"[UI] Error en agregar_fila._do: {ex}") - fila_header = ft.Container( - content=ft.Row([ - ft.Text(pedimento, width=150, size=12, color=ft.Colors.BLACK), - ft.Text(factura, width=120, size=12, color=ft.Colors.BLACK), - ft.Text(remesa, width=80, size=12, color=ft.Colors.BLACK), - ft.Text(estatus, expand=True, size=12, color=ft.Colors.BLUE_700, weight="bold"), - icono_expand, - ]), - padding=ft.padding.symmetric(horizontal=20, vertical=8), - border=ft.border.only(bottom=ft.BorderSide(1, ft.Colors.GREY_100)), - on_click=toggle_detalles, - ink=True, - tooltip="Clic para ver detalles" - ) - - # Agrupamos header + panel en una Column - fila_completa = ft.Column([fila_header, panel_detalles], spacing=0) - self.lista_resultados.controls.append(fila_completa) - self.page.update() - if self.page: self.page.run_task(_do) -- 2.49.1 From 12f050d1cd851213af7194e96e6ea73b616280f0 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Mon, 13 Jul 2026 14:27:36 -0500 Subject: [PATCH 6/6] fix: enviar linea_scaii_551 como entero o null, no string 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) --- registros/scaf/reg_551.py | 9 ++++++++- registros/scaii/reg_551.py | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/registros/scaf/reg_551.py b/registros/scaf/reg_551.py index a2bc788..57b9491 100644 --- a/registros/scaf/reg_551.py +++ b/registros/scaf/reg_551.py @@ -45,6 +45,13 @@ class Generador551SCAF: else: um_comercial_num = map_um_num.get(oma_raw, oma_raw) + # 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), @@ -82,7 +89,7 @@ class Generador551SCAF: "numero_remesa_551": str(num_remesa or " "), "numero_partida_remesa_551": str(num_partida_remesa), - "linea_scaii_551": str(par.get("Lote") or " "), # Lote de la tabla de partidas (impo/expo/def/temp) + "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", " ")), "descripcion_ingles_551": " ", diff --git a/registros/scaii/reg_551.py b/registros/scaii/reg_551.py index e42cc38..e67a1fc 100644 --- a/registros/scaii/reg_551.py +++ b/registros/scaii/reg_551.py @@ -88,6 +88,13 @@ class Generador551SCAII: 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), @@ -125,7 +132,7 @@ class Generador551SCAII: "numero_remesa_551": str(num_remesa or " "), "numero_partida_remesa_551": str(par.get("Linea") or num_partida_remesa), - "linea_scaii_551": str(par.get("Lote") or " "), # Lote de la tabla de partidas (impo/expo/def/temp) + "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], -- 2.49.1