8 Commits

Author SHA1 Message Date
fd31777337 fix: reg_501 tolera NULL en Destino y CLAVETRANSFER (#2)
preparar_json del 501 reventaba (TypeError/AttributeError) cuando
OPCIONDESTINO o CLAVETRANSFER venian NULL: get(k, "") no protege porque
un NULL de BD llega como None y el default no aplica. Pasa con pedimentos
que baja SCAII_Sync (dejan esos campos en NULL); WinDev los llenaba. Se
usa "or ''" en SCAII y SCAF.

Ademas, el hilo de sincronizacion tragaba la excepcion con print (invisible
en el exe windowed), por lo que parecia un cuelgue. Ahora se registra en
el log con traza (log.error exc_info).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Reviewed-on: #2
Co-authored-by: Kevin_Ramirez <kramirez@aduanasoft.com.mx>
Co-committed-by: Kevin_Ramirez <kramirez@aduanasoft.com.mx>
2026-08-03 16:54:59 +00:00
6db39444f4 Merge pull request 'fix/linea-scaii-551-partidas' (#1) from fix/linea-scaii-551-partidas into development
Reviewed-on: #1
2026-07-13 20:49:31 +00:00
12f050d1cd 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) <noreply@anthropic.com>
2026-07-13 14:27:36 -05:00
db75d2b97a 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) <noreply@anthropic.com>
2026-07-10 10:25:53 -05:00
a8db143339 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) <noreply@anthropic.com>
2026-07-10 10:25:53 -05:00
4347925cc3 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) <noreply@anthropic.com>
2026-07-10 10:25:53 -05:00
797572d3d7 chore: ignorar venv32 y archivos de log
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:25:53 -05:00
e9152a8cd1 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) <noreply@anthropic.com>
2026-07-09 14:15:06 -05:00
9 changed files with 103 additions and 71 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
venv/
venv32/
build/
dist/
__pycache__/
*.pyc
*.spec.bak
*.log

View File

@@ -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',
)

View File

@@ -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:

View File

@@ -174,7 +174,7 @@ 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,
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,
@@ -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,7 +224,7 @@ 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,
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,
@@ -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,

View File

@@ -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,7 +73,9 @@ 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():
try:
# Panel de detalles (oculto inicialmente)
campos_relevantes = {
"NumFactura": "Factura",
@@ -131,6 +134,9 @@ class InterfazFlet:
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}")
if self.page:
self.page.run_task(_do)
@@ -178,7 +184,7 @@ class InterfazFlet:
self.page.run_task(_done)
except Exception as ex:
print(f"[UI ERROR] {ex}")
log.error(f"[UI ERROR] Fallo en el hilo de sincronizacion: {ex}", exc_info=True)
async def _err():
self.status_label.value = f"Error: {ex}"
self.page.update()

View File

@@ -9,10 +9,11 @@ class Generador501SCAF:
"""
# Lógica de Destino/Zona (SCAF a veces usa códigos distintos, pero el if parece igual)
destino = datos_pedi.get("OPCIONDESTINO", "")
# OJO: usar "or ''" y no get(k, ''), porque un NULL de BD llega como None y el default no aplica
destino = datos_pedi.get("OPCIONDESTINO") or ""
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 ""
clave_transfer = (datos_cliente.get("CLAVETRANSFER") or "").strip() if datos_cliente else ""
# Construcción del Registro 501 SCAF
registro = {

View File

@@ -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,6 +89,7 @@ class Generador551SCAF:
"numero_remesa_551": str(num_remesa or " "),
"numero_partida_remesa_551": str(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", " ")),
"descripcion_ingles_551": " ",

View File

@@ -9,11 +9,12 @@ class Generador501:
"""
# Lógica de Destino/Zona (Opcional)
destino = datos_pedi.get("Destino", "")
# OJO: usar "or ''" y no get(k, ''), porque un NULL de BD llega como None y el default no aplica
destino = datos_pedi.get("Destino") or ""
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 ""
clave_transfer = (datos_cliente.get("CLAVETRANSFER") or "").strip() if datos_cliente else ""
# Construcción del Registro 501
registro = {

View File

@@ -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,6 +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": 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],