import flet as ft import threading from typing import Optional class InterfazFlet: def __init__(self, cliente_sinc=None): self.cliente = cliente_sinc self.page: Optional[ft.Page] = None self.COLOR_TEXTO_TITULO = "#0066CC" self.COLOR_FRANJA_CELESTE = "#7CE0F6" self.lista_resultados = ft.Column( expand=True, spacing=2, scroll=ft.ScrollMode.ALWAYS, auto_scroll=True ) self.cabecera_lista = ft.Container( content=ft.Row([ ft.Text("Pedimento", width=150, color=ft.Colors.WHITE, weight="bold", size=12), ft.Text("Factura", width=120, color=ft.Colors.WHITE, weight="bold", size=12), ft.Text("Remesa", width=80, color=ft.Colors.WHITE, weight="bold", size=12), ft.Text("Estatus de Sincronización", expand=True, color=ft.Colors.WHITE, weight="bold", size=12), ]), bgcolor=ft.Colors.BLUE_GREY_900, padding=ft.padding.only(left=20, top=10, right=20, bottom=10), border_radius=ft.border_radius.only(top_left=5, top_right=5) ) self.barra_progreso = ft.ProgressBar(value=0, color=ft.Colors.BLUE_600, bgcolor=ft.Colors.GREY_300) self.status_label = ft.Text("Listo", color=ft.Colors.BLACK, size=13, weight="w500") self.porcentaje_label = ft.Text("0%", color=ft.Colors.BLUE_700, weight="bold", size=14) def main(self, page: ft.Page): self.page = page page.title = "Sistema de Control de Aduanas e Inventarios" page.theme_mode = ft.ThemeMode.LIGHT page.window_width = 1000 page.window_height = 650 page.padding = 20 page.bgcolor = ft.Colors.GREY_50 titulo = ft.Text("Comunicación SCAII - WINSAAI.", size=24, color=self.COLOR_TEXTO_TITULO, weight="bold") franjas_decorativas = ft.Column([ ft.Container(height=3, bgcolor=ft.Colors.BLACK), ft.Container(height=15, bgcolor=self.COLOR_FRANJA_CELESTE) ], spacing=0) lista_container = ft.Container( content=ft.Column([self.cabecera_lista, self.lista_resultados], spacing=0), expand=True, bgcolor=ft.Colors.WHITE, border=ft.border.all(1, ft.Colors.GREY_300), border_radius=8 ) footer = ft.Container( content=ft.Row([ ft.Column([ ft.Row([self.status_label, ft.Container(expand=True), self.porcentaje_label]), self.barra_progreso ], expand=True, spacing=5) ], alignment=ft.MainAxisAlignment.START, vertical_alignment=ft.CrossAxisAlignment.CENTER), padding=15, bgcolor=ft.Colors.WHITE, border_radius=8, border=ft.border.all(1, ft.Colors.GREY_200), shadow=ft.BoxShadow(blur_radius=5, color=ft.Colors.GREY_200, offset=ft.Offset(0, 2)) ) page.add(titulo, franjas_decorativas, lista_container, ft.Divider(height=10, color=ft.Colors.TRANSPARENT), footer) if self.cliente: threading.Thread(target=self._hilo_sincronizacion, daemon=True).start() # ------------------------------------------------------------------ # def agregar_fila(self, pedimento, factura, remesa, datos_completos, estatus): 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" } 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 ), ) 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 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() if self.page: self.page.run_task(_do) # ------------------------------------------------------------------ # def actualizar_estado_fila(self, indice, estatus): async def _do(): if indice < len(self.lista_resultados.controls): # controls[indice] = Column → controls[0] = header Container → Row → controls[3] = Text estatus fila_header = self.lista_resultados.controls[indice].controls[0] fila_header.content.controls[3].value = estatus self.page.update() if self.page: self.page.run_task(_do) def actualizar_progreso(self, valor): async def _do(): self.barra_progreso.value = valor self.porcentaje_label.value = f"{int(valor * 100)}%" self.page.update() if self.page: self.page.run_task(_do) def actualizar_estado_texto(self, texto): async def _do(): self.status_label.value = texto self.page.update() if self.page: self.page.run_task(_do) # ------------------------------------------------------------------ # def _hilo_sincronizacion(self): try: if self.cliente: self.cliente.proceso_principal() async def _done(): self.status_label.value = "Listo" self.barra_progreso.value = 1.0 self.porcentaje_label.value = "100%" self.page.update() if self.page: self.page.run_task(_done) except Exception as ex: print(f"[UI ERROR] {ex}") async def _err(): self.status_label.value = f"Error: {ex}" self.page.update() if self.page: self.page.run_task(_err) def lanzar_interfaz(cliente=None): app = InterfazFlet(cliente) if cliente: cliente.gui = app ft.app(target=app.main) return app if __name__ == "__main__": ft.app(target=InterfazFlet().main)