feat: Enhance search functionality in the database manager to support relaxed criteria for result retrieval, improving user experience by allowing broader searches when no results are found. Update GUI components to reflect these changes, including new filter notes and options for relaxing search criteria.
This commit is contained in:
+16
-9
@@ -310,7 +310,9 @@ class AppController:
|
||||
|
||||
# Mostrar resumen rápido
|
||||
# Usar tabla results (1 fila = 1 documento), no el join con matches
|
||||
docs = self.db_manager.search_results(run_id=run_id, status="Todos", text=None)
|
||||
docs, _ = self.db_manager.search_results(
|
||||
run_id=run_id, status="Todos", text=None, relax_if_empty=False
|
||||
)
|
||||
match_count = sum(1 for r in docs if r.get("status") == "MATCH")
|
||||
self.view.append_preclass_output_safe(f"Total procesados: {len(docs)}")
|
||||
self.view.append_preclass_output_safe(f"Coincidencias (MATCH): {match_count}")
|
||||
@@ -427,20 +429,24 @@ class AppController:
|
||||
|
||||
def on_res_search(self):
|
||||
"""Busca resultados con los filtros actuales."""
|
||||
self.view.clear_res_filter_note()
|
||||
filters = self.view.get_res_filters()
|
||||
run_id = None
|
||||
if filters["run_id"] != "Todos":
|
||||
try:
|
||||
run_id = int(filters["run_id"])
|
||||
except: pass
|
||||
|
||||
|
||||
try:
|
||||
rows = self.db_manager.search_results(
|
||||
rows, relax_note = self.db_manager.search_results(
|
||||
run_id=run_id,
|
||||
status=filters["status"],
|
||||
text=filters["text"]
|
||||
text=filters["text"],
|
||||
relax_if_empty=bool(filters.get("relax_on_empty", False)),
|
||||
)
|
||||
self.view.update_res_table(rows)
|
||||
if relax_note:
|
||||
self.view.set_res_filter_note(relax_note)
|
||||
except Exception as e:
|
||||
logger.error(f"Error buscando resultados: {e}")
|
||||
mb.showerror("Error", f"Error en búsqueda: {e}")
|
||||
@@ -462,13 +468,14 @@ class AppController:
|
||||
if filters["run_id"] != "Todos":
|
||||
try: run_id = int(filters["run_id"])
|
||||
except: pass
|
||||
|
||||
rows = self.db_manager.search_results(
|
||||
|
||||
rows, _ = self.db_manager.search_results(
|
||||
run_id=run_id,
|
||||
status=filters["status"],
|
||||
text=filters["text"]
|
||||
text=filters["text"],
|
||||
relax_if_empty=bool(filters.get("relax_on_empty", False)),
|
||||
)
|
||||
|
||||
|
||||
if not rows:
|
||||
mb.showwarning("Exportar", "No hay datos para exportar.")
|
||||
return
|
||||
@@ -1292,7 +1299,7 @@ class AppController:
|
||||
if path.lower().endswith(".xlsx"):
|
||||
self._cleaning_result_df.to_excel(path, index=False)
|
||||
else:
|
||||
self._cleaning_result_df.to_csv(path, index=False)
|
||||
self._cleaning_result_df.to_csv(path, index=False, sep="|")
|
||||
mb.showinfo("Guardado", f"Archivo guardado correctamente:\n{path}")
|
||||
except Exception as e:
|
||||
logger.exception("Error guardando CSV limpio")
|
||||
|
||||
+155
-25
@@ -12,7 +12,11 @@ from typing import Optional
|
||||
import customtkinter as ctk
|
||||
import pandas as pd
|
||||
|
||||
from application.csv_cleaning import CLEANING_RULES, ColumnSummary
|
||||
from application.csv_cleaning import (
|
||||
CLEANING_RULES,
|
||||
ColumnSummary,
|
||||
applicable_rule_ids_for_type,
|
||||
)
|
||||
from gui.styles import AppStyles
|
||||
|
||||
|
||||
@@ -43,14 +47,24 @@ _TYPE_COLORS: dict[str, tuple[str, str]] = {
|
||||
"empty": ("#6B7280", "#6B7280"),
|
||||
}
|
||||
|
||||
# Orden fijo en el desplegable de anulación manual
|
||||
_USER_EDITABLE_TYPE_IDS: tuple[str, ...] = (
|
||||
"integer", "decimal", "date", "boolean", "categorical", "text", "mixed", "empty",
|
||||
)
|
||||
_TYPE_MENU_VALUES: list[str] = [
|
||||
_TYPE_LABELS.get(t, t) for t in _USER_EDITABLE_TYPE_IDS
|
||||
]
|
||||
|
||||
_RULE_SHORT_LABELS: dict[str, str] = {
|
||||
"strip_special_spaces": "Sin especiales/espacios",
|
||||
"strip_special_keep_date_seps": "Sin especiales (mantiene / - espacio)",
|
||||
"strip_special_keep_date_seps": "Símbolos: conserva /, -, espacios",
|
||||
"strip_spaces": "Sin espacios",
|
||||
"to_integer": "A entero",
|
||||
"date_numeric_to_ddmmyyyy": "Fecha numérica → DD/MM/AAAA",
|
||||
"date_alpha_to_ddmmyyyy": "Fecha alfanumérica → DD/MM/AAAA",
|
||||
"date_messy_text_to_ddmmyyyy": "Fecha con ruido (texto) → DD/MM/AAAA",
|
||||
"round_decimals_custom": "Aprox. decimales (>0.5 sube)",
|
||||
"doc_id_split_tipo_numero": "Dividir doc (tipo + cifras)",
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +87,10 @@ class _ColumnUI:
|
||||
card: ctk.CTkFrame
|
||||
body: ctk.CTkFrame
|
||||
toggle_btn: ctk.CTkButton
|
||||
type_menu: ctk.CTkOptionMenu
|
||||
count_label: ctk.CTkLabel
|
||||
stats_frame: ctk.CTkFrame
|
||||
rules_frame: ctk.CTkFrame
|
||||
rule_vars: list[tuple[str, ctk.CTkCheckBox, ctk.BooleanVar]]
|
||||
summary: ColumnSummary
|
||||
expanded: bool = True
|
||||
@@ -90,6 +107,8 @@ class CleaningTab:
|
||||
|
||||
self._column_uis: dict[str, _ColumnUI] = {}
|
||||
self._search_var: Optional[ctk.StringVar] = None
|
||||
# Tipo de dato forzado por el usuario (id interno) por columna; vacío = inferido
|
||||
self._type_overrides: dict[str, str] = {}
|
||||
|
||||
self._setup_layout()
|
||||
self._create_file_section()
|
||||
@@ -275,13 +294,14 @@ class CleaningTab:
|
||||
)
|
||||
self.bulk_type_menu.pack(side="left", padx=(0, 6))
|
||||
|
||||
rule_options = [r["name"] for r in CLEANING_RULES]
|
||||
self._bulk_rule_var = ctk.StringVar(value=rule_options[0])
|
||||
self._bulk_rule_var = ctk.StringVar(value=CLEANING_RULES[0]["name"])
|
||||
self.bulk_rule_menu = ctk.CTkOptionMenu(
|
||||
bulk_row, values=rule_options, variable=self._bulk_rule_var,
|
||||
bulk_row, values=[r["name"] for r in CLEANING_RULES], variable=self._bulk_rule_var,
|
||||
width=380, font=self.fonts["ui"],
|
||||
)
|
||||
self.bulk_rule_menu.pack(side="left", padx=(0, 6))
|
||||
self._sync_bulk_rule_menu()
|
||||
self._bulk_type_var.trace_add("write", lambda *_: self._sync_bulk_rule_menu())
|
||||
|
||||
self.btn_bulk_apply = ctk.CTkButton(
|
||||
bulk_row, text="Aplicar a todas", width=140, height=28,
|
||||
@@ -314,8 +334,9 @@ class CleaningTab:
|
||||
)
|
||||
self.lbl_global_count.pack(side="right")
|
||||
|
||||
self.columns_container = ctk.CTkScrollableFrame(
|
||||
frame, fg_color="transparent", height=420,
|
||||
# Frame plano: el scroll único es el de `self.scroll` (evita doble rueda del ratón)
|
||||
self.columns_container = ctk.CTkFrame(
|
||||
frame, fg_color="transparent",
|
||||
)
|
||||
self.columns_container.grid(row=1, column=0, sticky="ew", padx=10, pady=(0, 10))
|
||||
self.columns_container.grid_columnconfigure(0, weight=1)
|
||||
@@ -460,11 +481,33 @@ class CleaningTab:
|
||||
self.btn_expand_all.configure(state=state)
|
||||
self.btn_collapse_all.configure(state=state)
|
||||
|
||||
def _sync_bulk_rule_menu(self) -> None:
|
||||
"""Acota las reglas del desplegable según el tipo (p. ej. entero: sin a entero/approx)."""
|
||||
if not hasattr(self, "bulk_rule_menu") or not hasattr(
|
||||
self, "_bulk_type_var",
|
||||
):
|
||||
return
|
||||
type_label = self._bulk_type_var.get()
|
||||
type_id = next(
|
||||
(k for k, v in _TYPE_LABELS.items() if v == type_label), "text"
|
||||
)
|
||||
want = set(applicable_rule_ids_for_type(type_id))
|
||||
names = [r["name"] for r in CLEANING_RULES if r["id"] in want]
|
||||
if not names:
|
||||
return
|
||||
cur = self._bulk_rule_var.get()
|
||||
self.bulk_rule_menu.configure(values=names)
|
||||
if cur in names:
|
||||
self._bulk_rule_var.set(cur)
|
||||
else:
|
||||
self._bulk_rule_var.set(names[0])
|
||||
|
||||
def populate_columns(self, summaries: list[ColumnSummary]):
|
||||
"""Renderiza la lista de columnas con sus reglas aplicables."""
|
||||
for child in list(self.columns_container.winfo_children()):
|
||||
child.destroy()
|
||||
self._column_uis.clear()
|
||||
self._type_overrides.clear()
|
||||
if self._search_var is not None:
|
||||
try:
|
||||
self._search_var.set("")
|
||||
@@ -523,15 +566,28 @@ class CleaningTab:
|
||||
)
|
||||
toggle_btn.grid(row=0, column=0, padx=(0, 6), sticky="w")
|
||||
|
||||
type_color = _TYPE_COLORS.get(summary.inferred_type, ("gray60", "gray40"))
|
||||
type_label = _TYPE_LABELS.get(summary.inferred_type, summary.inferred_type)
|
||||
badge = ctk.CTkLabel(
|
||||
header, text=type_label.upper(),
|
||||
font=ctk.CTkFont(size=10, weight="bold"),
|
||||
fg_color=type_color, text_color="white",
|
||||
corner_radius=10, padx=10, pady=2,
|
||||
eff0 = self._type_overrides.get(summary.name) or summary.inferred_type
|
||||
if eff0 not in _TYPE_LABELS:
|
||||
eff0 = "text"
|
||||
tcol = _TYPE_COLORS.get(eff0, ("#64748B", "#64748B"))
|
||||
tmenu = ctk.CTkOptionMenu(
|
||||
header,
|
||||
values=_TYPE_MENU_VALUES,
|
||||
width=150,
|
||||
height=28,
|
||||
font=self.fonts["ui"],
|
||||
corner_radius=10,
|
||||
button_color=tcol[0] if tcol[0] else tcol[1],
|
||||
button_hover_color=tcol[0],
|
||||
fg_color=tcol[0] if tcol[0] else tcol[1],
|
||||
)
|
||||
tmenu.set(_TYPE_LABELS.get(eff0, _TYPE_LABELS["text"]))
|
||||
tmenu.grid(row=0, column=1, padx=(0, 6), sticky="w")
|
||||
tmenu.configure(
|
||||
command=lambda v, c=summary.name, m=tmenu: self._on_user_column_type_change(
|
||||
c, m, v
|
||||
)
|
||||
)
|
||||
badge.grid(row=0, column=1, padx=(0, 10), sticky="w")
|
||||
|
||||
name_lbl = ctk.CTkLabel(
|
||||
header, text=summary.name, font=self.fonts["bold_large"], anchor="w",
|
||||
@@ -559,7 +615,8 @@ class CleaningTab:
|
||||
)
|
||||
lbl_example.grid(row=0, column=0, sticky="ew", padx=4, pady=(0, 4))
|
||||
|
||||
self._build_stats_chips(body, summary).grid(
|
||||
stats_frame = self._build_stats_chips(body, summary, eff0)
|
||||
stats_frame.grid(
|
||||
row=1, column=0, sticky="ew", padx=4, pady=(0, 4),
|
||||
)
|
||||
|
||||
@@ -572,8 +629,11 @@ class CleaningTab:
|
||||
rules_frame.grid(row=3, column=0, sticky="ew", padx=0, pady=(2, 0))
|
||||
rules_frame.grid_columnconfigure((0, 1), weight=1, uniform="rules")
|
||||
|
||||
applicable_ids = summary.applicable_rules or [r["id"] for r in CLEANING_RULES]
|
||||
ordered_ids = [r["id"] for r in CLEANING_RULES if r["id"] in applicable_ids]
|
||||
eff_rules = self._type_overrides.get(summary.name) or summary.inferred_type
|
||||
if eff_rules not in _TYPE_LABELS:
|
||||
eff_rules = "text"
|
||||
applic = set(applicable_rule_ids_for_type(eff_rules))
|
||||
ordered_ids = [r["id"] for r in CLEANING_RULES if r["id"] in applic]
|
||||
|
||||
vars_list: list[tuple[str, ctk.CTkCheckBox, ctk.BooleanVar]] = []
|
||||
col_in_row = 0
|
||||
@@ -597,8 +657,15 @@ class CleaningTab:
|
||||
row_in_rules += 1
|
||||
|
||||
ui = _ColumnUI(
|
||||
card=card, body=body, toggle_btn=toggle_btn,
|
||||
count_label=count_lbl, rule_vars=vars_list, summary=summary,
|
||||
card=card,
|
||||
body=body,
|
||||
toggle_btn=toggle_btn,
|
||||
type_menu=tmenu,
|
||||
count_label=count_lbl,
|
||||
stats_frame=stats_frame,
|
||||
rules_frame=rules_frame,
|
||||
rule_vars=vars_list,
|
||||
summary=summary,
|
||||
)
|
||||
toggle_btn.configure(command=lambda c=summary.name: self._toggle_column(c))
|
||||
return ui
|
||||
@@ -607,9 +674,14 @@ class CleaningTab:
|
||||
# Stats visuales por columna
|
||||
# ===========================================================================
|
||||
|
||||
def _build_stats_chips(self, parent, summary: ColumnSummary) -> ctk.CTkFrame:
|
||||
def _build_stats_chips(
|
||||
self, parent, summary: ColumnSummary, display_type: Optional[str] = None
|
||||
) -> ctk.CTkFrame:
|
||||
"""Construye una fila de chips con la información clave de la columna."""
|
||||
row = ctk.CTkFrame(parent, fg_color="transparent")
|
||||
dtype = (display_type or summary.inferred_type) or "text"
|
||||
if dtype not in _TYPE_LABELS:
|
||||
dtype = "text"
|
||||
|
||||
# Sin datos
|
||||
nulls_color = (
|
||||
@@ -638,7 +710,7 @@ class CleaningTab:
|
||||
).pack(side="left", padx=(0, 6), pady=2)
|
||||
|
||||
# Stats por tipo
|
||||
if summary.inferred_type in ("integer", "decimal"):
|
||||
if dtype in ("integer", "decimal"):
|
||||
if summary.min is not None:
|
||||
self._make_stat_chip(
|
||||
row, label="Mín", value=_fmt_number(summary.min),
|
||||
@@ -654,7 +726,7 @@ class CleaningTab:
|
||||
row, label="Promedio", value=_fmt_number(summary.mean),
|
||||
bg=("#DCFCE7", "#14532D"), text_color=("#166534", "#BBF7D0"),
|
||||
).pack(side="left", padx=(0, 6), pady=2)
|
||||
elif summary.inferred_type == "date":
|
||||
elif dtype == "date":
|
||||
if summary.detected_date_format:
|
||||
self._make_stat_chip(
|
||||
row, label="Formato detectado",
|
||||
@@ -662,7 +734,7 @@ class CleaningTab:
|
||||
bg=("#FFEDD5", "#7C2D12"),
|
||||
text_color=("#9A3412", "#FED7AA"),
|
||||
).pack(side="left", padx=(0, 6), pady=2)
|
||||
elif summary.inferred_type in ("text", "categorical"):
|
||||
elif dtype in ("text", "categorical"):
|
||||
if summary.avg_length is not None:
|
||||
self._make_stat_chip(
|
||||
row, label="Longitud promedio",
|
||||
@@ -736,6 +808,64 @@ class CleaningTab:
|
||||
# Lógica interna
|
||||
# ===========================================================================
|
||||
|
||||
def _effective_inferred_type(self, ui: _ColumnUI) -> str:
|
||||
"""Tipo mostrado para la columna: anulación del usuario o inferido."""
|
||||
t = self._type_overrides.get(ui.summary.name) or ui.summary.inferred_type
|
||||
return t if t in _TYPE_LABELS else "text"
|
||||
|
||||
def _on_user_column_type_change(
|
||||
self, col_name: str, type_menu: ctk.CTkOptionMenu, spanish_label: str
|
||||
) -> None:
|
||||
type_id = next(
|
||||
(k for k, v in _TYPE_LABELS.items() if v == spanish_label), "text"
|
||||
)
|
||||
ui = self._column_uis.get(col_name)
|
||||
if ui is None:
|
||||
return
|
||||
s = ui.summary
|
||||
if type_id == s.inferred_type:
|
||||
self._type_overrides.pop(col_name, None)
|
||||
else:
|
||||
self._type_overrides[col_name] = type_id
|
||||
eff = self._effective_inferred_type(ui)
|
||||
tcol = _TYPE_COLORS.get(eff, ("#64748B", "#64748B"))
|
||||
type_menu.configure(
|
||||
button_color=tcol[0] if tcol[0] else tcol[1],
|
||||
button_hover_color=tcol[0],
|
||||
fg_color=tcol[0] if tcol[0] else tcol[1],
|
||||
)
|
||||
ui.stats_frame.destroy()
|
||||
new_stats = self._build_stats_chips(ui.body, s, eff)
|
||||
new_stats.grid(row=1, column=0, sticky="ew", padx=4, pady=(0, 4))
|
||||
ui.stats_frame = new_stats
|
||||
for w in list(ui.rules_frame.winfo_children()):
|
||||
w.destroy()
|
||||
applic = set(applicable_rule_ids_for_type(eff))
|
||||
new_vars: list[tuple[str, ctk.CTkCheckBox, ctk.BooleanVar]] = []
|
||||
col_in_row = 0
|
||||
row_in_rules = 0
|
||||
rmap = {r["id"]: r for r in CLEANING_RULES}
|
||||
for rid in (r["id"] for r in CLEANING_RULES if r["id"] in applic):
|
||||
rule = rmap.get(rid)
|
||||
if rule is None:
|
||||
continue
|
||||
var = ctk.BooleanVar(value=False)
|
||||
var.trace_add("write", lambda *_a, c=col_name: self._on_rule_toggle(c))
|
||||
label = _RULE_SHORT_LABELS.get(rid, rule["name"])
|
||||
chk = ctk.CTkCheckBox(
|
||||
ui.rules_frame, text=label, variable=var,
|
||||
font=self.fonts["ui"], checkbox_width=18, checkbox_height=18,
|
||||
)
|
||||
chk.grid(row=row_in_rules, column=col_in_row, sticky="w", padx=10, pady=4)
|
||||
new_vars.append((rid, chk, var))
|
||||
col_in_row += 1
|
||||
if col_in_row >= 2:
|
||||
col_in_row = 0
|
||||
row_in_rules += 1
|
||||
ui.rule_vars = new_vars
|
||||
self.set_status(f"Columna «{col_name}»: tipo «{_TYPE_LABELS.get(eff, eff)}».")
|
||||
self._on_rule_toggle(col_name)
|
||||
|
||||
def _toggle_column(self, col_name: str):
|
||||
ui = self._column_uis.get(col_name)
|
||||
if ui is None:
|
||||
@@ -804,7 +934,7 @@ class CleaningTab:
|
||||
|
||||
matched = 0
|
||||
for ui in self._column_uis.values():
|
||||
if ui.summary.inferred_type != type_id:
|
||||
if self._effective_inferred_type(ui) != type_id:
|
||||
continue
|
||||
for rid, _chk, var in ui.rule_vars:
|
||||
if rid == rule_id:
|
||||
|
||||
+47
-1
@@ -1,5 +1,6 @@
|
||||
"""Pestaña de Resultados de la aplicación GUI."""
|
||||
import os
|
||||
import tkinter as tk
|
||||
import customtkinter as ctk
|
||||
import tkinter.ttk as ttk
|
||||
from gui.styles import AppStyles
|
||||
@@ -88,6 +89,43 @@ class ResultsTab:
|
||||
|
||||
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)
|
||||
|
||||
self.lbl_res_priority = ctk.CTkLabel(
|
||||
self.frame_res_filters,
|
||||
text=(
|
||||
"Filtros estrictos: se combinan (AND) con importancia: "
|
||||
"Run > estado (MATCH, etc.) > criterio de búsqueda. "
|
||||
"Puede ampliar la búsqueda (sin tocar el run) abajo."
|
||||
),
|
||||
text_color=("#5C6370", "#9CA3AF"),
|
||||
font=ctk.CTkFont(size=12),
|
||||
anchor="w",
|
||||
wraplength=920,
|
||||
)
|
||||
self.lbl_res_priority.grid(
|
||||
row=1, column=0, columnspan=6, padx=5, pady=(4, 0), sticky="w"
|
||||
)
|
||||
self._res_relax_var = tk.BooleanVar(value=False)
|
||||
self.chk_res_relax = ctk.CTkCheckBox(
|
||||
self.frame_res_filters,
|
||||
text="Si 0 resultados, ampliar: quitar criterio de búsqueda, luego estado (el run fijado no se descarta).",
|
||||
font=ctk.CTkFont(size=12),
|
||||
variable=self._res_relax_var,
|
||||
)
|
||||
self.chk_res_relax.grid(
|
||||
row=2, column=0, columnspan=6, padx=5, pady=4, sticky="w"
|
||||
)
|
||||
self.lbl_res_filter_note = ctk.CTkLabel(
|
||||
self.frame_res_filters,
|
||||
text="",
|
||||
text_color=("#1D4ED8", "#60A5FA"),
|
||||
font=ctk.CTkFont(size=12),
|
||||
anchor="w",
|
||||
wraplength=920,
|
||||
)
|
||||
self.lbl_res_filter_note.grid(
|
||||
row=3, column=0, columnspan=6, padx=5, pady=(0, 4), sticky="w"
|
||||
)
|
||||
|
||||
def _create_tables_section(self):
|
||||
"""Crea la sección con las tablas (principal y de detalle)."""
|
||||
@@ -238,9 +276,17 @@ class ResultsTab:
|
||||
return {
|
||||
"run_id": self.combo_res_run.get(),
|
||||
"status": self.combo_res_status.get(),
|
||||
"text": self.entry_res_search.get()
|
||||
"text": self.entry_res_search.get(),
|
||||
"relax_on_empty": bool(self._res_relax_var.get()),
|
||||
}
|
||||
|
||||
def set_filter_note(self, text: str) -> None:
|
||||
"""Mensaje bajo ampliar (p. ej. qué criterio se relajó). Texto vacío la limpia."""
|
||||
self.lbl_res_filter_note.configure(text=(text or "").strip())
|
||||
|
||||
def clear_filter_note(self) -> None:
|
||||
self.set_filter_note("")
|
||||
|
||||
def update_results_table(self, rows):
|
||||
"""Actualiza la tabla de resultados con los datos proporcionados."""
|
||||
for item in self.tree_results.get_children():
|
||||
|
||||
+18
-14
@@ -39,12 +39,12 @@ class InvoiceAppView(ctk.CTk):
|
||||
self.tabs.grid(row=0, column=0, sticky="nsew", padx=20, pady=20)
|
||||
|
||||
self.tab_main = self.tabs.add("Procesar Documentos")
|
||||
self.tab_cleaning = self.tabs.add("Proceso de Limpieza")
|
||||
self.tab_preclass = self.tabs.add("Preclasificación")
|
||||
self.tab_results = self.tabs.add("Resultados")
|
||||
self.tab_settings = self.tabs.add("Configuración")
|
||||
self.tab_usage = self.tabs.add("Dashboard & Métricas")
|
||||
self.tab_postprocess = self.tabs.add("Post-Procesamiento")
|
||||
self.tab_cleaning = self.tabs.add("Proceso de Limpieza")
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: PROCESAR DOCUMENTOS ---
|
||||
@@ -52,21 +52,24 @@ class InvoiceAppView(ctk.CTk):
|
||||
self.main_tab = MainProcessingTab(self.tab_main)
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: RESULTADOS ---
|
||||
# --- TAB: PROCESO DE LIMPIEZA ---
|
||||
# =========================================================================
|
||||
self.results_tab = ResultsTab(self.tab_results)
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: CONFIGURACIÓN ---
|
||||
# =========================================================================
|
||||
self.settings_tab = SettingsTab(self.tab_settings)
|
||||
self.cleaning_tab = CleaningTab(self.tab_cleaning)
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: PRECLASIFICACIÓN ---
|
||||
# =========================================================================
|
||||
self.preclass_tab = PreclassificationTab(self.tab_preclass)
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: RESULTADOS ---
|
||||
# =========================================================================
|
||||
self.results_tab = ResultsTab(self.tab_results)
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: CONFIGURACIÓN ---
|
||||
# =========================================================================
|
||||
self.settings_tab = SettingsTab(self.tab_settings)
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: DASHBOARD & MÉTRICAS ---
|
||||
@@ -78,11 +81,6 @@ class InvoiceAppView(ctk.CTk):
|
||||
# =========================================================================
|
||||
self.postprocess_tab = PostProcessTab(self.tab_postprocess)
|
||||
|
||||
# =========================================================================
|
||||
# --- TAB: PROCESO DE LIMPIEZA ---
|
||||
# =========================================================================
|
||||
self.cleaning_tab = CleaningTab(self.tab_cleaning)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MÉTODOS PÚBLICOS (API COMPATIBLE)
|
||||
@@ -161,6 +159,12 @@ class InvoiceAppView(ctk.CTk):
|
||||
def get_res_filters(self):
|
||||
return self.results_tab.get_filters()
|
||||
|
||||
def set_res_filter_note(self, text: str) -> None:
|
||||
self.results_tab.set_filter_note(text)
|
||||
|
||||
def clear_res_filter_note(self) -> None:
|
||||
self.results_tab.clear_filter_note()
|
||||
|
||||
def update_res_table(self, rows):
|
||||
self.results_tab.update_results_table(rows)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user