feat: Introduce build and installation scripts for IPSExtractor, including a batch file for PyInstaller and Inno Setup integration, and add application preferences management for document types and retry logic in the GUI.
This commit is contained in:
+124
-2
@@ -1,6 +1,7 @@
|
||||
"""Pestaña Procesar Documentos de la aplicación GUI."""
|
||||
import customtkinter as ctk
|
||||
from gui.styles import AppStyles
|
||||
from application.app_prefs import get_allowed_document_types
|
||||
|
||||
|
||||
class MainProcessingTab:
|
||||
@@ -112,7 +113,47 @@ class MainProcessingTab:
|
||||
font=self.fonts["ui"]
|
||||
)
|
||||
self.radio_general_main.pack(side="left")
|
||||
|
||||
|
||||
# Filtro por Radicados
|
||||
self.filter_enabled_var = ctk.BooleanVar(value=False)
|
||||
self.filter_check = ctk.CTkCheckBox(
|
||||
self.frame_paths,
|
||||
text="Procesar solo radicados seleccionados",
|
||||
variable=self.filter_enabled_var,
|
||||
font=self.fonts["ui"],
|
||||
command=self._on_filter_toggle
|
||||
)
|
||||
self.filter_check.grid(row=4, column=0, columnspan=3, padx=15, pady=(4, 0), sticky="w")
|
||||
|
||||
self.filter_frame = ctk.CTkFrame(self.frame_paths, fg_color="transparent")
|
||||
self.filter_frame.grid(row=5, column=0, columnspan=3, padx=15, pady=(2, 6), sticky="ew")
|
||||
self.filter_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
filter_hint = ctk.CTkLabel(
|
||||
self.filter_frame,
|
||||
text="Ingresa los radicados a procesar (uno por línea o separados por coma):",
|
||||
font=self.fonts["ui"],
|
||||
text_color=self.colors["text_muted"]
|
||||
)
|
||||
filter_hint.grid(row=0, column=0, sticky="w", pady=(0, 2))
|
||||
|
||||
self.filter_textbox = ctk.CTkTextbox(
|
||||
self.filter_frame,
|
||||
height=80,
|
||||
font=self.fonts["mono"],
|
||||
)
|
||||
self.filter_textbox.grid(row=1, column=0, sticky="ew")
|
||||
|
||||
# Ocultar hasta que se active el checkbox
|
||||
self.filter_frame.grid_remove()
|
||||
|
||||
def _on_filter_toggle(self):
|
||||
"""Muestra u oculta el área de texto del filtro."""
|
||||
if self.filter_enabled_var.get():
|
||||
self.filter_frame.grid()
|
||||
else:
|
||||
self.filter_frame.grid_remove()
|
||||
|
||||
def _create_config_section(self):
|
||||
"""Crea la sección de configuración de tipos de documento y TIFF."""
|
||||
self.frame_config = ctk.CTkFrame(self.scroll_container)
|
||||
@@ -139,6 +180,15 @@ class MainProcessingTab:
|
||||
font=self.fonts["section"]
|
||||
)
|
||||
self.doc_types_label.grid(row=0, column=0, sticky="w")
|
||||
|
||||
allowed_types_text = ", ".join(get_allowed_document_types())
|
||||
self.doc_types_hint = ctk.CTkLabel(
|
||||
self.doc_types_header,
|
||||
text=f"Tipos válidos para documento_forzate: {allowed_types_text}. NIT no aplica.",
|
||||
font=self.fonts["ui"],
|
||||
text_color=self.colors["text_muted"],
|
||||
)
|
||||
self.doc_types_hint.grid(row=1, column=0, sticky="w", pady=(2, 0))
|
||||
|
||||
self.add_doc_type_btn = ctk.CTkButton(
|
||||
self.doc_types_header,
|
||||
@@ -147,7 +197,7 @@ class MainProcessingTab:
|
||||
width=120,
|
||||
height=28
|
||||
)
|
||||
self.add_doc_type_btn.grid(row=0, column=1, sticky="e")
|
||||
self.add_doc_type_btn.grid(row=0, column=1, rowspan=2, sticky="e")
|
||||
self.add_doc_type_btn.configure(command=self._add_doc_type_row)
|
||||
|
||||
# Lista de Tipos
|
||||
@@ -161,6 +211,16 @@ class MainProcessingTab:
|
||||
# Configuración Avanzada (TIFF Range)
|
||||
self.frame_advanced = ctk.CTkFrame(self.frame_config, fg_color="transparent")
|
||||
self.frame_advanced.grid(row=1, column=0, sticky="ew", padx=10, pady=(0, 2))
|
||||
self.frame_advanced.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.retry_empty_var = ctk.BooleanVar(value=False)
|
||||
self.retry_empty_check = ctk.CTkCheckBox(
|
||||
self.frame_advanced,
|
||||
text="Reintentar si la fila sale vacía (máx. 2 intentos)",
|
||||
variable=self.retry_empty_var,
|
||||
font=self.fonts["ui"]
|
||||
)
|
||||
self.retry_empty_check.grid(row=0, column=0, columnspan=4, padx=5, pady=(0, 6), sticky="w")
|
||||
|
||||
self.tiff_start_label = ctk.CTkLabel(
|
||||
self.frame_advanced,
|
||||
@@ -323,6 +383,16 @@ class MainProcessingTab:
|
||||
return v if v in ("radicados", "general") else "radicados"
|
||||
except Exception:
|
||||
return "radicados"
|
||||
|
||||
def set_process_mode(self, mode: str) -> None:
|
||||
"""Establece el modo de procesamiento y refresca la UI."""
|
||||
try:
|
||||
normalized = (mode or "").strip().lower()
|
||||
self.mode_var.set(normalized if normalized in ("radicados", "general") else "radicados")
|
||||
self.update_ui_for_mode()
|
||||
except Exception:
|
||||
self.mode_var.set("radicados")
|
||||
self.update_ui_for_mode()
|
||||
|
||||
def get_keywords(self) -> str:
|
||||
"""Devuelve los keywords."""
|
||||
@@ -347,6 +417,50 @@ class MainProcessingTab:
|
||||
return int(value) if value else 0
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def get_retry_empty_enabled(self) -> bool:
|
||||
"""Indica si se debe reintentar cuando la fila solo trae el radicado."""
|
||||
try:
|
||||
return bool(self.retry_empty_var.get())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def set_retry_empty_enabled(self, enabled: bool) -> None:
|
||||
"""Establece el reintento automático por fila vacía."""
|
||||
self.retry_empty_var.set(bool(enabled))
|
||||
|
||||
def get_radicado_filter_enabled(self) -> bool:
|
||||
"""Indica si el filtro de radicados está activo."""
|
||||
try:
|
||||
return bool(self.filter_enabled_var.get())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_radicado_filter_list(self) -> list[str]:
|
||||
"""Devuelve la lista de radicados del filtro (sin blancos ni duplicados)."""
|
||||
try:
|
||||
raw = self.filter_textbox.get("1.0", "end")
|
||||
items = []
|
||||
for part in raw.replace(",", "\n").splitlines():
|
||||
s = part.strip()
|
||||
if s:
|
||||
items.append(s)
|
||||
return list(dict.fromkeys(items)) # preserva orden, elimina duplicados
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def set_radicado_filter_enabled(self, enabled: bool) -> None:
|
||||
"""Activa o desactiva el filtro y refresca la UI."""
|
||||
self.filter_enabled_var.set(bool(enabled))
|
||||
self._on_filter_toggle()
|
||||
|
||||
def set_radicado_filter_list(self, radicados: list[str]) -> None:
|
||||
"""Carga una lista de radicados en el área de texto del filtro."""
|
||||
try:
|
||||
self.filter_textbox.delete("1.0", "end")
|
||||
self.filter_textbox.insert("1.0", "\n".join(radicados))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def show_tiff_range_controls(self, show: bool) -> None:
|
||||
"""Muestra u oculta los controles de rango TIFF."""
|
||||
@@ -398,6 +512,11 @@ class MainProcessingTab:
|
||||
if not seen:
|
||||
return False, "Debe haber al menos un tipo de documento."
|
||||
return True, None
|
||||
|
||||
def set_allowed_document_types_hint(self, allowed_types: list[str]) -> None:
|
||||
"""Actualiza el texto de ayuda de tipos válidos."""
|
||||
text = ", ".join(allowed_types)
|
||||
self.doc_types_hint.configure(text=f"Tipos válidos para documento_forzate: {text}. NIT no aplica.")
|
||||
|
||||
def update_ui_for_mode(self):
|
||||
"""Actualiza la visibilidad de componentes según el modo seleccionado."""
|
||||
@@ -445,6 +564,9 @@ class MainProcessingTab:
|
||||
self.csv_button.configure(state=state)
|
||||
self.csv_entry.configure(state=state)
|
||||
self.add_doc_type_btn.configure(state=state)
|
||||
self.retry_empty_check.configure(state=state)
|
||||
self.filter_check.configure(state=state)
|
||||
self.filter_textbox.configure(state=state)
|
||||
|
||||
for row in self._doc_type_rows:
|
||||
row["name_entry"].configure(state=state)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Pestaña de Configuración de la aplicación GUI."""
|
||||
import customtkinter as ctk
|
||||
from gui.styles import AppStyles
|
||||
from application.app_prefs import get_allowed_document_types, normalize_document_types
|
||||
|
||||
|
||||
class SettingsTab:
|
||||
@@ -20,6 +21,7 @@ class SettingsTab:
|
||||
self._setup_layout()
|
||||
self._create_api_section()
|
||||
self._create_processing_section()
|
||||
self._create_document_types_section()
|
||||
self._create_save_button()
|
||||
|
||||
def _setup_layout(self):
|
||||
@@ -55,18 +57,18 @@ class SettingsTab:
|
||||
|
||||
self.combo_model = ctk.CTkComboBox(
|
||||
self.frame_api,
|
||||
values=["5.1", "4.1", "4.1 mini", "4.1 nano"],
|
||||
values=["5.4", "5.4-mini", "5.4-nano", "5.2", "4.1-mini"],
|
||||
width=250,
|
||||
font=self.fonts["ui"]
|
||||
)
|
||||
self.combo_model.grid(row=1, column=1, padx=15, pady=10, sticky="w")
|
||||
self.combo_model.set("4.1")
|
||||
self.combo_model.set("5.4")
|
||||
|
||||
# Texto de ayuda
|
||||
help_text = (
|
||||
"ℹ 4.1 mini: Muy rápido (recomendado)\n"
|
||||
"ℹ 4.1: Estándar (balanceado)\n"
|
||||
"ℹ 5.1: Lento pero mayor razonamiento"
|
||||
"ℹ 5.4: Modelo predeterminado\n"
|
||||
"ℹ 5.4-mini / 4.1-mini: opciones más rápidas\n"
|
||||
"ℹ 5.4-nano: opción más económica"
|
||||
)
|
||||
lbl_help = ctk.CTkLabel(
|
||||
self.frame_api,
|
||||
@@ -119,6 +121,36 @@ class SettingsTab:
|
||||
text_color="orange"
|
||||
)
|
||||
lbl_conc_warn.grid(row=2, column=0, columnspan=2, padx=15, pady=(0, 10), sticky="w")
|
||||
|
||||
def _create_document_types_section(self):
|
||||
"""Crea la sección editable de tipos documentales permitidos."""
|
||||
self.frame_doc_types = ctk.CTkFrame(self.settings_container)
|
||||
self.frame_doc_types.grid(row=2, column=0, sticky="ew", padx=5, pady=5)
|
||||
self.frame_doc_types.grid_columnconfigure(0, weight=1)
|
||||
|
||||
lbl_title = ctk.CTkLabel(
|
||||
self.frame_doc_types,
|
||||
text="Tipos de Documento Permitidos",
|
||||
font=self.fonts["section"]
|
||||
)
|
||||
lbl_title.grid(row=0, column=0, sticky="w", padx=15, pady=(10, 4))
|
||||
|
||||
lbl_help = ctk.CTkLabel(
|
||||
self.frame_doc_types,
|
||||
text="Escríbelos separados por comas. Se usarán en documento_forzate. NIT se ignorará automáticamente.",
|
||||
font=self.fonts["italic_small"],
|
||||
text_color=self.colors["gray_light"],
|
||||
justify="left"
|
||||
)
|
||||
lbl_help.grid(row=1, column=0, sticky="w", padx=15, pady=(0, 6))
|
||||
|
||||
self.allowed_doc_types_entry = ctk.CTkEntry(
|
||||
self.frame_doc_types,
|
||||
font=self.fonts["ui"],
|
||||
placeholder_text="CC, TI, CE, RC, PA, PPT, PEP, EPT, NUIP"
|
||||
)
|
||||
self.allowed_doc_types_entry.grid(row=2, column=0, sticky="ew", padx=15, pady=(0, 10))
|
||||
self.set_allowed_document_types(list(get_allowed_document_types()))
|
||||
|
||||
def _create_save_button(self):
|
||||
"""Crea el botón de guardar configuración."""
|
||||
@@ -128,7 +160,7 @@ class SettingsTab:
|
||||
font=self.fonts["ui"],
|
||||
height=35
|
||||
)
|
||||
self.btn_save_settings.grid(row=2, column=0, padx=15, pady=15, sticky="ew")
|
||||
self.btn_save_settings.grid(row=3, column=0, padx=15, pady=15, sticky="ew")
|
||||
|
||||
# --- API Pública ---
|
||||
|
||||
@@ -154,10 +186,22 @@ class SettingsTab:
|
||||
def set_concurrency_limit(self, limit: int) -> None:
|
||||
"""Establece el límite de concurrencia."""
|
||||
self.concurrency_var.set(str(limit))
|
||||
|
||||
def get_allowed_document_types(self) -> list[str]:
|
||||
"""Devuelve la lista editable de tipos documentales válidos."""
|
||||
raw = self.allowed_doc_types_entry.get().strip()
|
||||
values = [part.strip() for part in raw.split(",")]
|
||||
return normalize_document_types(values)
|
||||
|
||||
def set_allowed_document_types(self, values: list[str]) -> None:
|
||||
"""Establece la lista de tipos documentales válidos."""
|
||||
self.allowed_doc_types_entry.delete(0, "end")
|
||||
self.allowed_doc_types_entry.insert(0, ", ".join(normalize_document_types(values)))
|
||||
|
||||
def enable_controls(self, enabled: bool):
|
||||
"""Habilita o deshabilita los controles."""
|
||||
state = "normal" if enabled else "disabled"
|
||||
self.combo_model.configure(state=state)
|
||||
self.concurrency_entry.configure(state=state)
|
||||
self.allowed_doc_types_entry.configure(state=state)
|
||||
self.btn_save_settings.configure(state=state)
|
||||
|
||||
Reference in New Issue
Block a user