feat: Enhance CSV cleaning and management functionalities by adding new date normalization rules, improving document type homologation, and implementing robust sanitization for CSV cell values. Update GUI components to support loading indicators and status messages during CSV processing, ensuring a smoother user experience.
This commit is contained in:
@@ -56,15 +56,17 @@ _TYPE_MENU_VALUES: list[str] = [
|
||||
]
|
||||
|
||||
_RULE_SHORT_LABELS: dict[str, str] = {
|
||||
"strip_special_spaces": "Sin especiales/espacios",
|
||||
"strip_special_keep_date_seps": "Símbolos: conserva /, -, espacios",
|
||||
"strip_special_spaces": "Sin especiales/espacios (no antes de fecha+hora)",
|
||||
"strip_special_keep_date_seps": "Símbolos: conserva /, -, :, espacios",
|
||||
"strip_spaces": "Sin espacios",
|
||||
"to_integer": "A entero",
|
||||
"date_to_ddmmyyyy": "Normalizar fecha → DD/MM/AAAA",
|
||||
"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)",
|
||||
"doc_type_homologate": "Homologar tipo doc",
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +111,7 @@ class CleaningTab:
|
||||
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._syncing_rule_vars = False
|
||||
|
||||
self._setup_layout()
|
||||
self._create_file_section()
|
||||
@@ -163,6 +166,27 @@ class CleaningTab:
|
||||
self.chip_delim = self._make_chip(chips_frame, "Delim: —")
|
||||
self.chip_delim.pack(side="left")
|
||||
|
||||
self.loading_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
||||
self.loading_frame.grid(
|
||||
row=2, column=0, columnspan=4, sticky="ew", padx=15, pady=(0, 12)
|
||||
)
|
||||
self.loading_frame.grid_columnconfigure(1, weight=1)
|
||||
self.loading_label = ctk.CTkLabel(
|
||||
self.loading_frame,
|
||||
text="",
|
||||
font=self.fonts["italic_small"],
|
||||
text_color=self.colors["gray_light"],
|
||||
anchor="w",
|
||||
)
|
||||
self.loading_label.grid(row=0, column=0, sticky="w", padx=(0, 10))
|
||||
self.loading_progress = ctk.CTkProgressBar(
|
||||
self.loading_frame,
|
||||
mode="indeterminate",
|
||||
height=8,
|
||||
)
|
||||
self.loading_progress.grid(row=0, column=1, sticky="ew")
|
||||
self.loading_frame.grid_remove()
|
||||
|
||||
def _make_chip(self, parent, text: str) -> ctk.CTkLabel:
|
||||
return ctk.CTkLabel(
|
||||
parent, text=text, font=self.fonts["italic_small"],
|
||||
@@ -429,6 +453,23 @@ class CleaningTab:
|
||||
def set_status(self, text: str):
|
||||
self.lbl_status.configure(text=text)
|
||||
|
||||
def set_loading(self, loading: bool, text: str = ""):
|
||||
"""Muestra u oculta una barra indeterminada para operaciones de CSV."""
|
||||
if loading:
|
||||
self.loading_label.configure(text=text or "Procesando CSV...")
|
||||
self.loading_frame.grid()
|
||||
try:
|
||||
self.loading_progress.start()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
self.loading_progress.stop()
|
||||
except Exception:
|
||||
pass
|
||||
self.loading_label.configure(text="")
|
||||
self.loading_frame.grid_remove()
|
||||
|
||||
def set_preview(self, df: Optional[pd.DataFrame]):
|
||||
"""Renderiza el DataFrame original en la tabla de vista previa."""
|
||||
if df is None or len(df) == 0:
|
||||
@@ -643,7 +684,10 @@ class CleaningTab:
|
||||
if rule is None:
|
||||
continue
|
||||
var = ctk.BooleanVar(value=False)
|
||||
var.trace_add("write", lambda *_a, c=summary.name: self._on_rule_toggle(c))
|
||||
var.trace_add(
|
||||
"write",
|
||||
lambda *_a, c=summary.name, r=rid: self._on_rule_toggle(c, r),
|
||||
)
|
||||
label = _RULE_SHORT_LABELS.get(rid, rule["name"])
|
||||
chk = ctk.CTkCheckBox(
|
||||
rules_frame, text=label, variable=var,
|
||||
@@ -850,7 +894,10 @@ class CleaningTab:
|
||||
if rule is None:
|
||||
continue
|
||||
var = ctk.BooleanVar(value=False)
|
||||
var.trace_add("write", lambda *_a, c=col_name: self._on_rule_toggle(c))
|
||||
var.trace_add(
|
||||
"write",
|
||||
lambda *_a, c=col_name, r=rid: self._on_rule_toggle(c, r),
|
||||
)
|
||||
label = _RULE_SHORT_LABELS.get(rid, rule["name"])
|
||||
chk = ctk.CTkCheckBox(
|
||||
ui.rules_frame, text=label, variable=var,
|
||||
@@ -883,10 +930,30 @@ class CleaningTab:
|
||||
if ui.expanded != expanded:
|
||||
self._toggle_column(col_name)
|
||||
|
||||
def _on_rule_toggle(self, col_name: str):
|
||||
def _on_rule_toggle(self, col_name: str, changed_rule_id: Optional[str] = None):
|
||||
ui = self._column_uis.get(col_name)
|
||||
if ui is None:
|
||||
return
|
||||
if (
|
||||
changed_rule_id == "doc_id_split_tipo_numero"
|
||||
and not self._syncing_rule_vars
|
||||
):
|
||||
split_selected = any(
|
||||
rid == "doc_id_split_tipo_numero" and bool(var.get())
|
||||
for rid, _chk, var in ui.rule_vars
|
||||
)
|
||||
if split_selected:
|
||||
for rid, _chk, var in ui.rule_vars:
|
||||
if rid == "doc_type_homologate" and not bool(var.get()):
|
||||
self._syncing_rule_vars = True
|
||||
try:
|
||||
var.set(True)
|
||||
finally:
|
||||
self._syncing_rule_vars = False
|
||||
self.set_status(
|
||||
f"Columna «{col_name}»: se activó homologación de tipo de documento."
|
||||
)
|
||||
break
|
||||
selected = sum(1 for _rid, _chk, var in ui.rule_vars if bool(var.get()))
|
||||
text = f"{selected} regla{'s' if selected != 1 else ''}"
|
||||
color = self.colors["success"] if selected else self.colors["gray_light"]
|
||||
|
||||
Reference in New Issue
Block a user