"""Tab de configuraci贸n.""" from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout, QLineEdit, QPushButton, QSpinBox, QCheckBox, QFileDialog, QMessageBox, QScrollArea ) from pathlib import Path from ..utils.crypto import encrypt_password, decrypt_password from ..extract.seven_zip import SevenZipExtractor from ..sql.sql_manager import SQLServerManager class ConfigTab(QWidget): """Tab de configuraci贸n de la aplicaci贸n.""" def __init__(self, engine): """Inicializa el tab.""" super().__init__() self.engine = engine self.config = {} self._setup_ui() def _setup_ui(self): """Configura la interfaz.""" # Scroll area para la configuraci贸n scroll = QScrollArea() scroll.setWidgetResizable(True) container = QWidget() layout = QVBoxLayout(container) layout.setSpacing(15) # Grupo: Carpetas paths_group = QGroupBox("Carpetas") paths_layout = QFormLayout() self.input_folder_input = QLineEdit() input_browse_btn = QPushButton("馃搧") input_browse_btn.clicked.connect(lambda: self._browse_folder(self.input_folder_input)) input_layout = QHBoxLayout() input_layout.addWidget(self.input_folder_input) input_layout.addWidget(input_browse_btn) paths_layout.addRow("Carpeta de Entrada:", input_layout) self.processed_folder_input = QLineEdit() processed_browse_btn = QPushButton("馃搧") processed_browse_btn.clicked.connect(lambda: self._browse_folder(self.processed_folder_input)) processed_layout = QHBoxLayout() processed_layout.addWidget(self.processed_folder_input) processed_layout.addWidget(processed_browse_btn) paths_layout.addRow("Carpeta Procesados:", processed_layout) self.failed_folder_input = QLineEdit() failed_browse_btn = QPushButton("馃搧") failed_browse_btn.clicked.connect(lambda: self._browse_folder(self.failed_folder_input)) failed_layout = QHBoxLayout() failed_layout.addWidget(self.failed_folder_input) failed_layout.addWidget(failed_browse_btn) paths_layout.addRow("Carpeta Fallados:", failed_layout) self.extract_folder_input = QLineEdit() extract_browse_btn = QPushButton("馃搧") extract_browse_btn.clicked.connect(lambda: self._browse_folder(self.extract_folder_input)) extract_layout = QHBoxLayout() extract_layout.addWidget(self.extract_folder_input) extract_layout.addWidget(extract_browse_btn) paths_layout.addRow("Carpeta Extracci贸n:", extract_layout) self.data_sql_folder_input = QLineEdit() data_browse_btn = QPushButton("馃搧") data_browse_btn.clicked.connect(lambda: self._browse_folder(self.data_sql_folder_input)) data_layout = QHBoxLayout() data_layout.addWidget(self.data_sql_folder_input) data_layout.addWidget(data_browse_btn) paths_layout.addRow("Carpeta Data SQL:", data_layout) self.seven_zip_input = QLineEdit() seven_zip_browse_btn = QPushButton("馃搧") seven_zip_browse_btn.clicked.connect(self._browse_seven_zip) seven_zip_detect_btn = QPushButton("馃攳 Detectar") seven_zip_detect_btn.clicked.connect(self._detect_seven_zip) seven_zip_layout = QHBoxLayout() seven_zip_layout.addWidget(self.seven_zip_input) seven_zip_layout.addWidget(seven_zip_browse_btn) seven_zip_layout.addWidget(seven_zip_detect_btn) paths_layout.addRow("7-Zip (7z.exe):", seven_zip_layout) paths_group.setLayout(paths_layout) layout.addWidget(paths_group) # Grupo: SQL Server sql_group = QGroupBox("SQL Server") sql_layout = QFormLayout() self.sql_server_input = QLineEdit() self.sql_server_input.setPlaceholderText("localhost o IP") sql_layout.addRow("Servidor:", self.sql_server_input) self.sql_windows_auth_checkbox = QCheckBox() self.sql_windows_auth_checkbox.setChecked(True) self.sql_windows_auth_checkbox.toggled.connect(self._on_auth_changed) sql_layout.addRow("Usar Windows Auth:", self.sql_windows_auth_checkbox) self.sql_username_input = QLineEdit() self.sql_username_input.setEnabled(False) sql_layout.addRow("Usuario SQL:", self.sql_username_input) self.sql_password_input = QLineEdit() self.sql_password_input.setEchoMode(QLineEdit.EchoMode.Password) self.sql_password_input.setEnabled(False) sql_layout.addRow("Contrase帽a SQL:", self.sql_password_input) test_sql_btn = QPushButton("馃攲 Probar Conexi贸n") test_sql_btn.clicked.connect(self._test_sql_connection) sql_layout.addRow("", test_sql_btn) sql_group.setLayout(sql_layout) layout.addWidget(sql_group) # Grupo: Concurrencia concurrency_group = QGroupBox("Concurrencia") concurrency_layout = QFormLayout() self.extract_workers_spinbox = QSpinBox() self.extract_workers_spinbox.setMinimum(1) self.extract_workers_spinbox.setMaximum(10) concurrency_layout.addRow("Workers de Extracci贸n:", self.extract_workers_spinbox) self.restore_workers_spinbox = QSpinBox() self.restore_workers_spinbox.setMinimum(1) self.restore_workers_spinbox.setMaximum(5) concurrency_layout.addRow("Workers de Restore:", self.restore_workers_spinbox) concurrency_group.setLayout(concurrency_layout) layout.addWidget(concurrency_group) # Grupo: Estabilidad de Archivos stability_group = QGroupBox("Estabilidad de Archivos") stability_layout = QFormLayout() self.stability_enabled_checkbox = QCheckBox() self.stability_enabled_checkbox.setChecked(True) stability_layout.addRow("Verificar Estabilidad:", self.stability_enabled_checkbox) self.stability_interval_spinbox = QSpinBox() self.stability_interval_spinbox.setMinimum(1) self.stability_interval_spinbox.setMaximum(60) self.stability_interval_spinbox.setSuffix(" s") stability_layout.addRow("Intervalo de Verificaci贸n:", self.stability_interval_spinbox) self.stable_duration_spinbox = QSpinBox() self.stable_duration_spinbox.setMinimum(1) self.stable_duration_spinbox.setMaximum(300) self.stable_duration_spinbox.setSuffix(" s") stability_layout.addRow("Duraci贸n Estable:", self.stable_duration_spinbox) self.ready_marker_checkbox = QCheckBox() stability_layout.addRow("Usar Marcador .ready:", self.ready_marker_checkbox) stability_group.setLayout(stability_layout) layout.addWidget(stability_group) # Grupo: Timeouts timeouts_group = QGroupBox("Timeouts") timeouts_layout = QFormLayout() self.extract_timeout_spinbox = QSpinBox() self.extract_timeout_spinbox.setMinimum(1) self.extract_timeout_spinbox.setMaximum(240) self.extract_timeout_spinbox.setSuffix(" min") timeouts_layout.addRow("Extracci贸n:", self.extract_timeout_spinbox) self.restore_timeout_spinbox = QSpinBox() self.restore_timeout_spinbox.setMinimum(1) self.restore_timeout_spinbox.setMaximum(480) self.restore_timeout_spinbox.setSuffix(" min") timeouts_layout.addRow("Restauraci贸n:", self.restore_timeout_spinbox) timeouts_group.setLayout(timeouts_layout) layout.addWidget(timeouts_group) # Grupo: Caracter铆sticas features_group = QGroupBox("Caracter铆sticas") features_layout = QFormLayout() self.dry_run_checkbox = QCheckBox() features_layout.addRow("Modo Dry Run:", self.dry_run_checkbox) self.auto_scan_checkbox = QCheckBox() self.auto_scan_checkbox.setChecked(True) features_layout.addRow("Escaneo Autom谩tico:", self.auto_scan_checkbox) self.scan_interval_spinbox = QSpinBox() self.scan_interval_spinbox.setMinimum(10) self.scan_interval_spinbox.setMaximum(600) self.scan_interval_spinbox.setSuffix(" s") features_layout.addRow("Intervalo de Escaneo:", self.scan_interval_spinbox) features_group.setLayout(features_layout) layout.addWidget(features_group) # Botones de acci贸n actions_layout = QHBoxLayout() save_btn = QPushButton("馃捑 Guardar Configuraci贸n") save_btn.clicked.connect(self._save_config) actions_layout.addWidget(save_btn) actions_layout.addStretch() layout.addLayout(actions_layout) layout.addStretch() scroll.setWidget(container) # Layout principal main_layout = QVBoxLayout(self) main_layout.addWidget(scroll) def load_config(self, config: dict): """Carga la configuraci贸n en la UI.""" self.config = config # Carpetas paths = config.get("paths", {}) self.input_folder_input.setText(paths.get("input_folder", "")) self.processed_folder_input.setText(paths.get("processed_folder", "")) self.failed_folder_input.setText(paths.get("failed_folder", "")) self.extract_folder_input.setText(paths.get("extract_folder", "")) self.data_sql_folder_input.setText(paths.get("data_sql_folder", "")) self.seven_zip_input.setText(paths.get("seven_zip_exe", "")) # SQL sql = config.get("sql", {}) self.sql_server_input.setText(sql.get("server", "localhost")) self.sql_windows_auth_checkbox.setChecked(sql.get("use_windows_auth", True)) self.sql_username_input.setText(sql.get("username", "")) # Descifrar password si existe encrypted_pass = sql.get("password_encrypted", "") if encrypted_pass: try: decrypted = decrypt_password(encrypted_pass) self.sql_password_input.setText(decrypted) except: pass # Concurrencia concurrency = config.get("concurrency", {}) self.extract_workers_spinbox.setValue(concurrency.get("extract_workers", 1)) self.restore_workers_spinbox.setValue(concurrency.get("restore_workers", 1)) # Estabilidad stability = config.get("stability", {}) self.stability_enabled_checkbox.setChecked(stability.get("check_enabled", True)) self.stability_interval_spinbox.setValue(stability.get("check_interval_seconds", 5)) self.stable_duration_spinbox.setValue(stability.get("stable_duration_seconds", 10)) self.ready_marker_checkbox.setChecked(stability.get("use_ready_marker", False)) # Timeouts timeouts = config.get("timeouts", {}) self.extract_timeout_spinbox.setValue(timeouts.get("extract_minutes", 30)) self.restore_timeout_spinbox.setValue(timeouts.get("restore_minutes", 60)) # Caracter铆sticas features = config.get("features", {}) self.dry_run_checkbox.setChecked(features.get("dry_run_mode", False)) self.auto_scan_checkbox.setChecked(features.get("auto_scan_enabled", True)) self.scan_interval_spinbox.setValue(features.get("scan_interval_seconds", 30)) def _save_config(self): """Guarda la configuraci贸n.""" # Construir config config = { "paths": { "input_folder": self.input_folder_input.text(), "processed_folder": self.processed_folder_input.text(), "failed_folder": self.failed_folder_input.text(), "extract_folder": self.extract_folder_input.text(), "data_sql_folder": self.data_sql_folder_input.text(), "seven_zip_exe": self.seven_zip_input.text() }, "sql": { "server": self.sql_server_input.text(), "use_windows_auth": self.sql_windows_auth_checkbox.isChecked(), "username": self.sql_username_input.text(), "password_encrypted": "" }, "concurrency": { "extract_workers": self.extract_workers_spinbox.value(), "restore_workers": self.restore_workers_spinbox.value() }, "stability": { "check_enabled": self.stability_enabled_checkbox.isChecked(), "check_interval_seconds": self.stability_interval_spinbox.value(), "stable_duration_seconds": self.stable_duration_spinbox.value(), "use_ready_marker": self.ready_marker_checkbox.isChecked() }, "timeouts": { "extract_minutes": self.extract_timeout_spinbox.value(), "restore_minutes": self.restore_timeout_spinbox.value() }, "retries": self.config.get("retries", {"max_attempts": 3, "retry_delay_seconds": 60}), "features": { "dry_run_mode": self.dry_run_checkbox.isChecked(), "auto_scan_enabled": self.auto_scan_checkbox.isChecked(), "scan_interval_seconds": self.scan_interval_spinbox.value() } } # Cifrar password si no usa Windows Auth if not self.sql_windows_auth_checkbox.isChecked(): password = self.sql_password_input.text() if password: try: config["sql"]["password_encrypted"] = encrypt_password(password) except Exception as e: QMessageBox.warning(self, "Advertencia", f"Error cifrando contrase帽a: {e}") # Guardar try: self.engine.save_config(config) QMessageBox.information(self, "脡xito", "Configuraci贸n guardada exitosamente.") except Exception as e: QMessageBox.critical(self, "Error", f"Error guardando configuraci贸n: {e}") def _browse_folder(self, line_edit: QLineEdit): """Abre el di谩logo para seleccionar carpeta.""" folder = QFileDialog.getExistingDirectory(self, "Seleccionar Carpeta") if folder: line_edit.setText(folder) def _browse_seven_zip(self): """Abre el di谩logo para seleccionar 7z.exe.""" file_path, _ = QFileDialog.getOpenFileName( self, "Seleccionar 7z.exe", "C:\\Program Files\\7-Zip", "Ejecutables (*.exe)" ) if file_path: self.seven_zip_input.setText(file_path) def _detect_seven_zip(self): """Auto-detecta 7-Zip.""" path = SevenZipExtractor._auto_detect_7zip() if path: self.seven_zip_input.setText(path) QMessageBox.information(self, "脡xito", f"7-Zip detectado en:\n{path}") else: QMessageBox.warning( self, "No Encontrado", "No se pudo auto-detectar 7-Zip. Por favor, selecciona manualmente." ) def _on_auth_changed(self, checked: bool): """Maneja el cambio en el tipo de autenticaci贸n.""" self.sql_username_input.setEnabled(not checked) self.sql_password_input.setEnabled(not checked) def _test_sql_connection(self): """Prueba la conexi贸n a SQL Server.""" server = self.sql_server_input.text() use_windows_auth = self.sql_windows_auth_checkbox.isChecked() username = self.sql_username_input.text() password = self.sql_password_input.text() if not server: QMessageBox.warning(self, "Error", "Debe especificar un servidor.") return try: manager = SQLServerManager( server=server, use_windows_auth=use_windows_auth, username=username if not use_windows_auth else None, password=password if not use_windows_auth else None ) success, error = manager.test_connection() if success: QMessageBox.information( self, "脡xito", "Conexi贸n a SQL Server exitosa." ) else: QMessageBox.critical( self, "Error", f"Error conectando a SQL Server:\n{error}" ) except Exception as e: QMessageBox.critical(self, "Error", f"Error: {e}")