"""Pestaña de Resultados de la aplicación GUI.""" import os import customtkinter as ctk import tkinter.ttk as ttk from gui.styles import AppStyles class ResultsTab: """Encapsula la UI de la pestaña Resultados.""" def __init__(self, parent_tab): """Inicializa la pestaña de Resultados. Args: parent_tab: El CTkFrame que representa la pestaña. """ self.tab = parent_tab self.fonts = AppStyles.get_fonts() self.colors = AppStyles.get_colors() self.spacing = AppStyles.get_spacing() self._setup_layout() self._create_subtabs() self._setup_treeview_style() self._create_extractor_tab() self._create_preclass_results_tab() def _setup_layout(self): """Configura el layout base de la pestaña.""" self.tab.grid_columnconfigure(0, weight=1) self.tab.grid_rowconfigure(0, weight=1) self.tabs_res = ctk.CTkTabview(self.tab) self.tabs_res.grid(row=0, column=0, sticky="nsew", padx=10, pady=10) def _create_subtabs(self): """Crea las sub-pestañas de Resultados.""" self.tab_res_extractor = self.tabs_res.add("Extractor (Próximamente)") self.tab_res_preclass = self.tabs_res.add("Preclasificación") def _setup_treeview_style(self): """Configura el estilo de los treeviews.""" style = ttk.Style() style.theme_use("clam") style.configure("Treeview", rowheight=25) def _create_extractor_tab(self): """Crea la sub-pestaña del Extractor (en construcción).""" lbl_const = ctk.CTkLabel( self.tab_res_extractor, text="En construcción...", font=ctk.CTkFont(size=20) ) lbl_const.place(relx=0.5, rely=0.5, anchor="center") def _create_preclass_results_tab(self): """Crea la sub-pestaña de resultados de preclasificación.""" self.tab_res_preclass.grid_columnconfigure(0, weight=1) self.tab_res_preclass.grid_rowconfigure(1, weight=1) self._create_filters_section() self._create_tables_section() self._create_actions_section() def _create_filters_section(self): """Crea la sección de filtros.""" self.frame_res_filters = ctk.CTkFrame(self.tab_res_preclass) self.frame_res_filters.grid(row=0, column=0, sticky="ew", padx=10, pady=5) ctk.CTkLabel(self.frame_res_filters, text="Run ID:").grid(row=0, column=0, padx=5, pady=5) self.combo_res_run = ctk.CTkComboBox(self.frame_res_filters, values=["Todos"], width=150) self.combo_res_run.grid(row=0, column=1, padx=5, pady=5) ctk.CTkLabel(self.frame_res_filters, text="Estado:").grid(row=0, column=2, padx=5, pady=5) self.combo_res_status = ctk.CTkComboBox( self.frame_res_filters, values=["Todos", "MATCH", "NO_MATCH", "ERROR", "NO_FILES"], width=120 ) self.combo_res_status.grid(row=0, column=3, padx=5, pady=5) self.entry_res_search = ctk.CTkEntry( self.frame_res_filters, placeholder_text="Buscar radicado/archivo...", width=200 ) self.entry_res_search.grid(row=0, column=4, padx=5, pady=5) self.btn_res_search = ctk.CTkButton(self.frame_res_filters, text="Buscar", width=100) self.btn_res_search.grid(row=0, column=5, padx=10, pady=5) def _create_tables_section(self): """Crea la sección con las tablas (principal y de detalle).""" self.frame_res_table = ctk.CTkFrame(self.tab_res_preclass, fg_color="transparent") self.frame_res_table.grid(row=1, column=0, sticky="nsew", padx=10, pady=5) self.frame_res_table.grid_columnconfigure(0, weight=1) self.frame_res_table.grid_rowconfigure(0, weight=3) self.frame_res_table.grid_rowconfigure(2, weight=2) self._create_main_results_table() self._create_detail_label() self._create_matches_table() def _create_main_results_table(self): """Crea la tabla principal de resultados.""" cols = ("ID", "Run", "Fecha", "Radicado", "Estado", "Archivo") self.tree_results = ttk.Treeview( self.frame_res_table, columns=cols, show="headings", selectmode="browse" ) for c in cols: self.tree_results.heading(c, text=c) self.tree_results.column(c, width=100) self.tree_results.column("Archivo", width=300) self.tree_results.column("ID", width=50) self.tree_results.column("Run", width=50) # Scrollbars sb_y = ttk.Scrollbar(self.frame_res_table, orient="vertical", command=self.tree_results.yview) sb_x = ttk.Scrollbar(self.frame_res_table, orient="horizontal", command=self.tree_results.xview) self.tree_results.configure(yscrollcommand=sb_y.set, xscrollcommand=sb_x.set) self.tree_results.grid(row=0, column=0, sticky="nsew") sb_y.grid(row=0, column=1, sticky="ns") sb_x.grid(row=1, column=0, sticky="ew") def _create_detail_label(self): """Crea la etiqueta de la sección de detalle.""" lbl_detail = ctk.CTkLabel( self.frame_res_table, text="Detalle de Matches (Seleccionar fila arriba)", font=ctk.CTkFont(size=12, weight="bold") ) lbl_detail.grid(row=1, column=0, sticky="w", pady=(10, 5)) def _create_matches_table(self): """Crea la tabla de detalle de matches.""" cols_detail = ("Key", "Valor Encontrado", "Existe", "Valor Esperado") self.tree_matches = ttk.Treeview( self.frame_res_table, columns=cols_detail, show="headings", selectmode="browse", height=5 ) for c in cols_detail: self.tree_matches.heading(c, text=c) self.tree_matches.column(c, width=150) sb_detail_y = ttk.Scrollbar(self.frame_res_table, orient="vertical", command=self.tree_matches.yview) self.tree_matches.configure(yscrollcommand=sb_detail_y.set) self.tree_matches.grid(row=2, column=0, sticky="nsew") sb_detail_y.grid(row=2, column=1, sticky="ns") def _create_actions_section(self): """Crea la sección de acciones.""" self.frame_res_actions = ctk.CTkFrame(self.tab_res_preclass) self.frame_res_actions.grid(row=2, column=0, sticky="ew", padx=10, pady=10) self.btn_res_export = ctk.CTkButton( self.frame_res_actions, text="Exportar Vista a Excel", fg_color=self.colors["success"] ) self.btn_res_export.pack(side="right", padx=10, pady=5) self.btn_res_organize = ctk.CTkButton( self.frame_res_actions, text="Organizar Carpetas (Run)", fg_color=self.colors["warning"], hover_color=self.colors["warning_hover"] ) self.btn_res_organize.pack(side="right", padx=10, pady=5) self.btn_res_refresh = ctk.CTkButton(self.frame_res_actions, text="Refrescar Runs") self.btn_res_refresh.pack(side="left", padx=10, pady=5) # --- API Pública --- def set_on_search(self, callback): """Vincula el callback al botón de búsqueda.""" self.btn_res_search.configure(command=callback) def set_on_export(self, callback): """Vincula el callback al botón de exportación.""" self.btn_res_export.configure(command=callback) def set_on_refresh(self, callback): """Vincula el callback al botón de refrescar.""" self.btn_res_refresh.configure(command=callback) def set_on_organize(self, callback): """Vincula el callback al botón de organizar.""" self.btn_res_organize.configure(command=callback) def set_on_select_result(self, callback): """Vincula el callback al evento de selección de fila.""" self.tree_results.bind("<>", callback) def get_selected_result_values(self) -> tuple | None: """Devuelve los valores de la fila seleccionada o None si no hay selección.""" selection = self.tree_results.selection() if not selection: return None return self.tree_results.item(selection[0], "values") or None def update_matches_table(self, rows): """Actualiza la tabla de matches con los datos proporcionados.""" for item in self.tree_matches.get_children(): self.tree_matches.delete(item) for r in rows: exists_str = "✅" if r.get("exists_bool") else "❌" vals = (r.get("key"), r.get("found_value"), exists_str, r.get("expected_value") or "-") self.tree_matches.insert("", "end", values=vals) def update_runs_combo(self, runs: list): """Actualiza el combo box con los IDs de ejecuciones disponibles.""" vals = ["Todos"] + [str(r) for r in runs] self.combo_res_run.configure(values=vals) self.combo_res_run.set("Todos") def set_run_id(self, run_id: int | str): """Establece el ID de ejecución seleccionado.""" try: self.combo_res_run.set(str(run_id)) except Exception: pass def get_filters(self) -> dict: """Devuelve los filtros actuales.""" return { "run_id": self.combo_res_run.get(), "status": self.combo_res_status.get(), "text": self.entry_res_search.get() } def update_results_table(self, rows): """Actualiza la tabla de resultados con los datos proporcionados.""" for item in self.tree_results.get_children(): self.tree_results.delete(item) for r in rows: vals = ( r.get("id"), r.get("run_id"), r.get("timestamp"), r.get("radicado"), r.get("status"), os.path.basename(r.get("file_path") or "") ) self.tree_results.insert("", "end", values=vals)