import tkinter as tk from tkinter import Menu, ttk, messagebox, filedialog from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import os import threading # Importaciones de módulos de lógica import utility_thread as ut import monitor_logic as ml import alarm_logic as al import backup_logic as bl import notepad_logic as nl import network_monitor as nm import camel_game_logic as cgl import audio_player_logic as apl import external_launcher as el import scraping_logic as sl # --- CLASE ALARMLISTFRAME --- class AlarmListFrame(tk.Frame): def __init__(self, master, root_app, **kwargs): super().__init__(master, **kwargs) self.root_app = root_app self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.tree = ttk.Treeview(self, columns=('Time', 'Message', 'Status'), show='headings') self.tree.heading('Time', text='Hora') self.tree.heading('Message', text='Mensaje') self.tree.heading('Status', text='Estado') self.tree.column('Time', width=80, anchor=tk.CENTER) self.tree.column('Status', width=80, anchor=tk.CENTER) self.tree.grid(row=0, column=0, sticky='nsew', padx=5, pady=5) vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview) vsb.grid(row=0, column=1, sticky='ns') self.tree.configure(yscrollcommand=vsb.set) self.tree.bind('<>', self.on_select) self.update_list() def update_list(self, alarms=None): if alarms is None: alarms = al.load_alarms() for item in self.tree.get_children(): self.tree.delete(item) for alarm in alarms: time_str = f"{alarm['hour']:02d}:{alarm['minute']:02d}" status_str = "ACTIVA" if alarm['active'] else "INACTIVA" tag = 'active' if alarm['active'] else 'inactive' self.tree.insert('', tk.END, iid=str(alarm['id']), text='', values=(time_str, alarm['message'], status_str), tags=(tag,)) self.tree.tag_configure('active', background='#D4EDDA', foreground='#155724') self.tree.tag_configure('inactive', background='#F8D7DA', foreground='#721C24') def on_select(self, event): selected_items = self.tree.selection() if not selected_items: self.root_app.tree_deselect_and_disable_buttons() return alarm_id = int(selected_items[0]) alarms = al.load_alarms() selected_alarm = next((a for a in alarms if a['id'] == alarm_id), None) if selected_alarm: self.root_app.set_selected_alarm(alarm_id, selected_alarm['active']) # --- CLASE MAINAPPLICATION --- class MainApplication(tk.Tk): def __init__(self): super().__init__() self.title("Ventana Responsive Modular") self.geometry("1000x700") self.root = self self.monitor_elements = ml.initialize_monitor_figures() self.selected_alarm_id = None self.selected_alarm_is_active = False self.camel_threads = [] self.winner_name = None # Variables para el scraping self.scraping_search_term_var = None self.scraping_results_text = None self.DEBUG_HTML_FILE = "amazon_debugging_output.html" self.configure_layout() self.create_widgets() ut.start_time_thread(self.label_fecha_hora) self.start_monitor() self.start_alarm_checker() nm.start_network_monitoring_thread(self) # INICIALIZAR EL REPRODUCTOR DE MÚSICA self.music_player = apl.MusicPlayer(self) self.protocol("WM_DELETE_WINDOW", self.on_closing) def on_closing(self): """Detiene la música y destruye la ventana al cerrar la app.""" self.music_player.stop_music() self.destroy() def configure_layout(self): self.columnconfigure(0, weight=0) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=0) self.rowconfigure(0, weight=1) self.rowconfigure(1, weight=0) def create_widgets(self): self.create_menu() self.create_frames() self.create_left_panel_controls() self.create_central_subframes() self.create_status_bar() self.create_notebook() self.create_monitor_tab() self.create_alarm_tab() self.create_notepad_tab() self.create_camel_game_tab() self.create_audio_player_tab() self.create_external_launcher_tab() self.create_scraping_tab() self.notebook.bind('<>', self.on_tab_change) self.update_activity_status("Inicio de la aplicación") def create_menu(self): menu_bar = Menu(self) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label="Salir", command=self.on_closing) menu_bar.add_cascade(label="Archivo", menu=file_menu) self.config(menu=menu_bar) def create_frames(self): self.frame_izquierdo = tk.Frame(self, bg="lightblue", width=200) self.frame_central = tk.Frame(self, bg="white") self.frame_derecho = tk.Frame(self, bg="lightgreen", width=200) self.frame_izquierdo.grid(row=0, column=0, sticky="ns") self.frame_central.grid(row=0, column=1, sticky="nsew") self.frame_derecho.grid(row=0, column=2, sticky="ns") self.frame_izquierdo.grid_propagate(False) self.frame_derecho.grid_propagate(False) def create_left_panel_controls(self): self.frame_izquierdo.columnconfigure(0, weight=1) tk.Label(self.frame_izquierdo, text="Procesos batch", font=("Arial", 12, "bold"), bg="lightblue").grid(row=0, column=0, padx=10, pady=(10, 5), sticky="w") btn_backup = tk.Button( self.frame_izquierdo, text="Copias de seguridad", command=self.backup_app_state, bg="#D4EDDA", fg="#155724", font=("Arial", 10, "bold"), relief=tk.RAISED, bd=3 ) btn_backup.grid(row=1, column=0, padx=10, pady=5, sticky="ew") def create_central_subframes(self): self.frame_central.rowconfigure(0, weight=1) self.frame_central.rowconfigure(1, weight=0) self.frame_central.columnconfigure(0, weight=1) self.frame_superior = tk.Frame(self.frame_central, bg="lightyellow") self.frame_inferior = tk.Frame(self.frame_central, bg="lightgray", height=100) self.frame_superior.grid(row=0, column=0, sticky="nsew") self.frame_inferior.grid(row=1, column=0, sticky="ew") self.frame_inferior.grid_propagate(False) def create_status_bar(self): self.barra_estado = tk.Label(self, bg="lightgray", anchor="w") self.barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") self.label_status_1 = tk.Label(self.barra_estado, text="Listo", bg="green", anchor="w", fg='white') self.label_status_1.pack(side="left", fill="x", expand=True) self.label_net_status = tk.Label(self.barra_estado, text="Red: 0.00/0.00 KB", bg="blue", anchor="w", fg='white') self.label_net_status.pack(side="left", fill="x", expand=True) self.label_audio_status = tk.Label(self.barra_estado, text="Música: Ninguno", bg="cyan", anchor="w") self.label_audio_status.pack(side="left", fill="x", expand=True) self.label_fecha_hora = tk.Label(self.barra_estado, text="Hilo fecha-hora", font=("Helvetica", 14), bd=1, fg="blue", relief="sunken", anchor="w", width=20, padx=10) self.label_fecha_hora.pack(side="right", fill="x", expand=True) def create_notebook(self): style = ttk.Style() style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold")) self.notebook = ttk.Notebook(self.frame_superior, style="CustomNotebook.TNotebook") self.notebook.pack(fill="both", expand=True) def create_monitor_tab(self): tab_monitor = ttk.Frame(self.notebook) self.notebook.add(tab_monitor, text="Monitor de Sistema 📈", padding=4) tab_monitor.columnconfigure(0, weight=1); tab_monitor.columnconfigure(1, weight=1) tab_monitor.rowconfigure(0, weight=1); tab_monitor.rowconfigure(1, weight=0) self.canvas_usage = FigureCanvasTkAgg(self.monitor_elements['fig_usage'], master=tab_monitor) self.canvas_widget_usage = self.canvas_usage.get_tk_widget() self.canvas_widget_usage.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) self.canvas_net = FigureCanvasTkAgg(self.monitor_elements['fig_net'], master=tab_monitor) self.canvas_widget_net = self.canvas_net.get_tk_widget() self.canvas_widget_net.grid(row=0, column=1, sticky="nsew", padx=5, pady=5) frame_metrics = ttk.Frame(tab_monitor, padding=10, relief="groove") frame_metrics.grid(row=1, column=0, columnspan=2, sticky="ew", padx=5, pady=5) frame_metrics.columnconfigure(0, weight=1); frame_metrics.columnconfigure(1, weight=1) self.label_load = tk.Label(frame_metrics, text="Carga: -", anchor="w", font=("Arial", 10, "bold")) self.label_load.grid(row=0, column=0, sticky="ew", pady=2) self.label_threads = tk.Label(frame_metrics, text="Hilos: -", anchor="w", font=("Arial", 10, "bold")) self.label_threads.grid(row=0, column=1, sticky="ew", pady=2) def create_alarm_tab(self): tab_alarm = ttk.Frame(self.notebook) self.notebook.add(tab_alarm, text="Gestor de Alarmas ⏰", padding=10) tab_alarm.columnconfigure(0, weight=1) tab_alarm.columnconfigure(1, weight=0) tab_alarm.rowconfigure(0, weight=1) frame_lista = ttk.Frame(tab_alarm) frame_lista.grid(row=0, column=0, sticky='nsew', padx=10, pady=10) frame_lista.columnconfigure(0, weight=1) frame_lista.rowconfigure(0, weight=1) frame_lista.rowconfigure(1, weight=0) self.alarm_list_widget = AlarmListFrame(frame_lista, self, relief='sunken', borderwidth=2) self.alarm_list_widget.grid(row=0, column=0, sticky='nsew') self.alarm_notification_label = tk.Label(frame_lista, text="Sistema de Alarmas Activo", fg='green', font=("Arial", 14, "bold")) self.alarm_notification_label.grid(row=1, column=0, sticky='ew', pady=5) frame_control = ttk.Frame(tab_alarm, padding=15, relief='groove') frame_control.grid(row=0, column=1, sticky='ns', padx=10, pady=10) ttk.Label(frame_control, text="Añadir Nueva Alarma", font=("Arial", 12, "bold")).grid(row=0, column=0, columnspan=2, pady=(0, 10)) ttk.Label(frame_control, text="Hora (HH):").grid(row=1, column=0, sticky='w', pady=5) self.alarm_hour_var = tk.StringVar(value="08") ttk.Entry(frame_control, textvariable=self.alarm_hour_var, width=5).grid(row=1, column=1, sticky='e', pady=5) ttk.Label(frame_control, text="Minuto (MM):").grid(row=2, column=0, sticky='w', pady=5) self.alarm_minute_var = tk.StringVar(value="00") ttk.Entry(frame_control, textvariable=self.alarm_minute_var, width=5).grid(row=2, column=1, sticky='e', pady=5) ttk.Label(frame_control, text="Mensaje:").grid(row=3, column=0, sticky='w', pady=5) self.alarm_message_var = tk.StringVar(value="Recordatorio") ttk.Entry(frame_control, textvariable=self.alarm_message_var, width=15).grid(row=3, column=1, sticky='e', pady=5) ttk.Button(frame_control, text="Programar Alarma", command=self.add_alarm_callback).grid(row=4, column=0, columnspan=2, pady=(10, 20), sticky='ew') ttk.Label(frame_control, text="Control de Alarma Seleccionada", font=("Arial", 12, "bold")).grid(row=5, column=0, columnspan=2, pady=(10, 10)) self.btn_toggle = ttk.Button(frame_control, text="Activar / Desactivar", command=self.toggle_alarm_callback, state=tk.DISABLED) self.btn_toggle.grid(row=6, column=0, columnspan=2, pady=5, sticky='ew') self.btn_delete = ttk.Button(frame_control, text="Eliminar Alarma", command=self.delete_alarm_callback, state=tk.DISABLED) self.btn_delete.grid(row=7, column=0, columnspan=2, pady=5, sticky='ew') def create_notepad_tab(self): tab_notepad = ttk.Frame(self.notebook) self.notebook.add(tab_notepad, text="Notas 📝", padding=10) tab_notepad.columnconfigure(0, weight=1) tab_notepad.rowconfigure(0, weight=1) tab_notepad.rowconfigure(1, weight=0) frame_editor = ttk.Frame(tab_notepad) frame_editor.grid(row=0, column=0, sticky='nsew', padx=5, pady=5) frame_editor.columnconfigure(0, weight=1) frame_editor.rowconfigure(0, weight=1) self.text_editor = tk.Text( frame_editor, wrap=tk.WORD, font=("Consolas", 10), padx=10, pady=10 ) self.text_editor.grid(row=0, column=0, sticky='nsew') vsb = ttk.Scrollbar(frame_editor, orient="vertical", command=self.text_editor.yview) vsb.grid(row=0, column=1, sticky='ns') self.text_editor.configure(yscrollcommand=vsb.set) self.load_notes_callback(startup=True) frame_buttons = ttk.Frame(tab_notepad) frame_buttons.grid(row=1, column=0, sticky='ew', padx=5, pady=(0, 5)) frame_buttons.columnconfigure(0, weight=1) frame_buttons.columnconfigure(1, weight=1) ttk.Button(frame_buttons, text="Guardar Notas 💾", command=self.save_notes_callback).grid(row=0, column=0, sticky='ew', padx=5, pady=5) ttk.Button(frame_buttons, text="Recargar (Deshacer) 🔄", command=self.load_notes_callback).grid(row=0, column=1, sticky='ew', padx=5, pady=5) def create_camel_game_tab(self): tab_game = ttk.Frame(self.notebook) self.notebook.add(tab_game, text="Carrera de Camellos 🏁", padding=10) tab_game.columnconfigure(0, weight=1) tab_game.rowconfigure(0, weight=0) tab_game.rowconfigure(1, weight=1) frame_control = ttk.Frame(tab_game, padding=5) frame_control.grid(row=0, column=0, sticky='ew') ttk.Style().configure("Accent.TButton", font=("Arial", 12, "bold"), foreground="blue") ttk.Button(frame_control, text="¡INICIAR CARRERA!", command=self.start_game_callback, style="Accent.TButton").pack(pady=10) self.frame_game = ttk.Frame(tab_game, padding=10, relief='groove', borderwidth=2) self.frame_game.grid(row=1, column=0, sticky='nsew', padx=5, pady=5) self.frame_game.columnconfigure(0, weight=1) tk.Label(self.frame_game, text="Pulsa 'INICIAR CARRERA' para empezar...", fg='gray', font=("Arial", 14)).pack(pady=20) def create_audio_player_tab(self): tab_audio = ttk.Frame(self.notebook) self.notebook.add(tab_audio, text="Música 🎧", padding=20) ttk.Label(tab_audio, text="Reproductor de Música de Fondo (Pygame)", font=("Arial", 16, "bold")).pack(pady=10) self.audio_filepath_var = tk.StringVar(value="Ningún archivo cargado.") ttk.Label(tab_audio, textvariable=self.audio_filepath_var, wraplength=500).pack(pady=(10, 5)) ttk.Button(tab_audio, text="Seleccionar Archivo MP3/OGG", command=self.select_audio_file).pack(pady=10) frame_controls = ttk.Frame(tab_audio) frame_controls.pack(pady=20) ttk.Button(frame_controls, text="⏸️ Pausa", command=self.pause_audio).pack(side=tk.LEFT, padx=10) ttk.Button(frame_controls, text="▶️ Reanudar", command=self.unpause_audio).pack(side=tk.LEFT, padx=10) ttk.Button(frame_controls, text="⏹️ Detener", command=self.stop_audio).pack(side=tk.LEFT, padx=10) def create_external_launcher_tab(self): tab_external = ttk.Frame(self.notebook) self.notebook.add(tab_external, text="Lanzador 🌐", padding=20) tab_external.columnconfigure(0, weight=1) tab_external.rowconfigure(0, weight=0) ttk.Label(tab_external, text="Lanzar Aplicaciones Externas", font=("Arial", 16, "bold")).pack(pady=10) frame_browser = ttk.LabelFrame(tab_external, text="Abrir Navegador", padding=15) frame_browser.pack(fill='x', pady=10, padx=50) ttk.Label(frame_browser, text="URL (ej: google.com):").pack(pady=5) self.url_var = tk.StringVar(value="www.google.com") ttk.Entry(frame_browser, textvariable=self.url_var, width=50).pack(pady=5) ttk.Button(frame_browser, text="Abrir URL en Navegador Predeterminado", command=self.launch_browser_callback).pack(pady=10) frame_custom = ttk.LabelFrame(tab_external, text="Abrir Aplicación (Ej: Bloc de Notas)", padding=15) frame_custom.pack(fill='x', pady=20, padx=50) ttk.Label(frame_custom, text="Comando (ej: notepad.exe):").pack(pady=5) self.app_command_var = tk.StringVar(value="notepad.exe") ttk.Entry(frame_custom, textvariable=self.app_command_var, width=50).pack(pady=5) ttk.Button(frame_custom, text="Lanzar Aplicación Externa", command=self.launch_custom_app_callback).pack(pady=10) # --- MÉTODO PARA LA SOLAPA WEB SCRAPING --- def create_scraping_tab(self): tab_scraping = ttk.Frame(self.notebook) self.notebook.add(tab_scraping, text="Web Scraper 🔎", padding=15) tab_scraping.columnconfigure(0, weight=1) tab_scraping.rowconfigure(1, weight=1) # 1. Controles Superiores (Término y Botón) frame_controls = ttk.Frame(tab_scraping, padding=10) frame_controls.grid(row=0, column=0, sticky='ew', pady=(0, 10)) frame_controls.columnconfigure(1, weight=1) ttk.Label(frame_controls, text="Término de Búsqueda (Ej: Portátil, Auriculares):").grid(row=0, column=0, padx=5, sticky='w') self.scraping_search_term_var = tk.StringVar(value="auriculares bluetooth") ttk.Entry(frame_controls, textvariable=self.scraping_search_term_var, width=40).grid(row=0, column=1, padx=5, sticky='ew') ttk.Button(frame_controls, text="START SCRAPING", command=self.start_scraping_callback).grid(row=0, column=2, padx=10, sticky='e') # 2. Área de Resultados (Texto) frame_results = ttk.Frame(tab_scraping, relief=tk.SUNKEN, borderwidth=2) frame_results.grid(row=1, column=0, sticky='nsew', padx=5, pady=5) frame_results.columnconfigure(0, weight=1) frame_results.rowconfigure(0, weight=1) self.scraping_results_text = tk.Text( frame_results, wrap=tk.WORD, font=("Consolas", 10), padx=10, pady=10 ) self.scraping_results_text.grid(row=0, column=0, sticky='nsew') vsb = ttk.Scrollbar(frame_results, orient="vertical", command=self.scraping_results_text.yview) vsb.grid(row=0, column=1, sticky='ns') self.scraping_results_text.configure(yscrollcommand=vsb.set) # --- MÉTODOS DE CALLBACKS DE LÓGICA --- # ------------------------------------------------------------------ # GESTIÓN DEL SCRAPING DE AMAZON (Llamada al hilo) # ------------------------------------------------------------------ def start_scraping_callback(self): """Inicia el proceso de scraping en un hilo separado (llama a Playwright).""" search_term = self.scraping_search_term_var.get() if not search_term: messagebox.showwarning("Advertencia", "Por favor, introduce un término de búsqueda.") return self.update_activity_status(f"Iniciando scraping (Playwright) para: {search_term}...") self.scraping_results_text.delete("1.0", tk.END) self.scraping_results_text.insert("1.0", f"Buscando productos en Amazon para '{search_term}' usando Playwright. Esto puede tardar unos segundos...\n") # LLAMADA CLAVE: Inicia el proceso asíncrono en un hilo de trabajo sl.start_playwright_scraper(search_term, self) def _display_scraping_results(self, results, search_term): """ Recibe los resultados del hilo de Playwright (llamado vía self.after) y los muestra en la interfaz. """ self.scraping_results_text.delete("1.0", tk.END) if results and "error" in results[0]: output = f"--- ERROR en Scraping para {search_term} ---\n\n{results[0]['error']}\n\nSi obtienes un Timeout (30s), Amazon te ha bloqueado." self.scraping_results_text.insert("1.0", output) self.update_activity_status(f"Scraping fallido para {search_term}.") return output = f"--- PRODUCTOS de Amazon para '{search_term}' ({len(results)} encontrados) ---\n\n" for i, product in enumerate(results): output += f"[{i+1}] Producto: {product['nombre']}\n" output += f" Precio: {product['precio']}\n" output += f" Vendedor/Marca: {product['vendedor']}\n" output += f" URL Imagen: {product['imagen_url']}\n" output += "-" * 50 + "\n" output += f"\nDatos guardados en {sl.SCRAPING_FILE}." self.scraping_results_text.insert("1.0", output) self.update_activity_status(f"Scraping completado para '{search_term}'. Resultados guardados.") # ------------------------------------------------------------------ # --- GESTIÓN DE AUDIO --- def select_audio_file(self): filepath = filedialog.askopenfilename( defaultextension=".mp3", filetypes=[("Archivos de Audio", "*.mp3 *.ogg"), ("Todos los archivos", "*.*")] ) if filepath: self.audio_filepath_var.set(filepath) self.update_activity_status(f"Archivo seleccionado: {os.path.basename(filepath)}") self.music_player.load_and_play(filepath) def pause_audio(self): self.music_player.pause_music() self.update_activity_status("Música pausada.") def unpause_audio(self): self.music_player.unpause_music() self.update_activity_status("Música reanudada.") def stop_audio(self): self.music_player.stop_music() self.audio_filepath_var.set("Ningún archivo cargado.") self.update_activity_status("Música detenida.") def update_audio_status(self, message): self.label_audio_status.config(text=f"{message}") # --- GESTIÓN DE LANZADOR EXTERNO --- def launch_browser_callback(self): url = self.url_var.get() if el.launch_browser(url): self.update_activity_status(f"Lanzando URL: {url}") else: self.update_activity_status("Error al lanzar el navegador.") def launch_custom_app_callback(self): command = self.app_command_var.get().split() if not command: messagebox.showwarning("Advertencia", "El campo de comando no puede estar vacío.") return app_name = command[0] if el.launch_custom_app(command, app_name): self.update_activity_status(f"Lanzando aplicación: {app_name}") else: self.update_activity_status(f"Fallo al lanzar la aplicación: {app_name}") # --- GESTIÓN DE ESTADO Y UTILIDADES --- def on_tab_change(self, event): selected_tab = self.notebook.tab(self.notebook.select(), "text") self.update_activity_status(f"{selected_tab}") def update_activity_status(self, message): self.label_status_1.config(text=f"{message}") def update_net_status(self, kb_sent, kb_recv): text = f"Red: ↑{kb_sent:.2f} / ↓{kb_recv:.2f} KB" self.label_net_status.config(text=text) def start_game_callback(self): for thread in self.camel_threads: thread.is_running = False self.update_activity_status("Preparando la carrera...") cgl.start_camel_game(self, track_length=50) def clear_camel_game_area(self): for widget in self.frame_game.winfo_children(): widget.destroy() def show_winner(self, winner_name): messagebox.showinfo("¡Carrera Terminada!", f"¡HA GANADO {winner_name.upper()}!") self.update_activity_status(f"¡{winner_name} ha ganado la carrera!") def backup_app_state(self): current_alarms = al.load_alarms() bl.create_backup(current_alarms) self.update_activity_status("Copia de seguridad de Alarmas creada.") def save_notes_callback(self): content = self.text_editor.get("1.0", tk.END) if nl.save_notes(content): self.text_editor.edit_modified(False) self.update_activity_status("Notas guardadas correctamente.") def load_notes_callback(self, startup=False): if not startup and self.text_editor.edit_modified(): response = messagebox.askyesno( "Confirmar Recarga", "El editor tiene cambios sin guardar. ¿Estás seguro de que quieres recargar el archivo (y perder los cambios)?" ) if not response: return loaded_content = nl.load_notes() self.text_editor.delete("1.0", tk.END) self.text_editor.insert("1.0", loaded_content) self.text_editor.edit_modified(False) if not startup: self.update_activity_status("Notas recargadas desde el archivo.") def set_selected_alarm(self, alarm_id, is_active): self.selected_alarm_id = alarm_id self.selected_alarm_is_active = is_active self.btn_toggle.config(state=tk.NORMAL) self.btn_delete.config(state=tk.NORMAL) toggle_text = "Desactivar" if is_active else "Activar" self.btn_toggle.config(text=toggle_text) self.update_activity_status(f"Alarma {alarm_id} seleccionada (Estado: {'ACTIVA' if is_active else 'INACTIVA'}).") def add_alarm_callback(self): try: hour = self.alarm_hour_var.get() minute = self.alarm_minute_var.get() message = self.alarm_message_var.get() if not (0 <= int(hour) <= 23 and 0 <= int(minute) <= 59): messagebox.showerror("Error", "La hora debe estar entre 00 y 23, y el minuto entre 00 y 59.") return alarms = al.add_alarm(hour, minute, message) self.alarm_list_widget.update_list(alarms) self.alarm_notification_label.config(text="Alarma programada con éxito.", fg='blue') self.update_activity_status(f"Alarma programada: {hour}:{minute}.") except ValueError: messagebox.showerror("Error", "Por favor, introduce números válidos para hora y minuto.") def toggle_alarm_callback(self): if self.selected_alarm_id is not None: new_state = not self.selected_alarm_is_active alarms = al.toggle_alarm(self.selected_alarm_id, new_state) self.alarm_list_widget.update_list(alarms) self.alarm_notification_label.config(text=f"Alarma {self.selected_alarm_id} {'ACTIVADA' if new_state else 'DESACTIVADA'}.", fg='green' if new_state else 'orange') self.tree_deselect_and_disable_buttons() self.update_activity_status(f"Alarma {self.selected_alarm_id} {'ACTIVADA' if new_state else 'DESACTIVADA'}.") def delete_alarm_callback(self): if self.selected_alarm_id is not None: alarms = al.delete_alarm(self.selected_alarm_id) self.alarm_list_widget.update_list(alarms) self.alarm_notification_label.config(text=f"Alarma {self.selected_alarm_id} eliminada.", fg='red') self.tree_deselect_and_disable_buttons() self.update_activity_status(f"Alarma {self.selected_alarm_id} eliminada.") def tree_deselect_and_disable_buttons(self): if self.alarm_list_widget.tree.selection(): self.alarm_list_widget.tree.selection_remove(self.alarm_list_widget.tree.selection()) self.selected_alarm_id = None self.btn_toggle.config(state=tk.DISABLED) self.btn_delete.config(state=tk.DISABLED) def show_alarm_popup(self, alarm_data): self.alarm_notification_label.config(text=f"¡ALARMA SONANDO!", fg='red') dialog = al.CustomAlarmDialog(self, alarm_data) result = dialog.result if result == 'posponer': alarms = al.postpone_alarm(alarm_data['id']) self.alarm_notification_label.config(text=f"Alarma pospuesta 1 minuto.", fg='blue') self.update_activity_status(f"Alarma {alarm_data['id']} pospuesta.") elif result == 'detener': alarms = al.toggle_alarm(alarm_data['id'], False) self.alarm_notification_label.config(text=f"Alarma detenida.", fg='green') self.update_activity_status(f"Alarma {alarm_data['id']} detenida.") else: alarms = al.toggle_alarm(alarm_data['id'], False) self.alarm_notification_label.config(text=f"Alarma detenida (cierre inesperado).", fg='green') self.update_activity_status(f"Alarma {alarm_data['id']} detenida (cierre inesperado).") self.alarm_list_widget.update_list() self.tree_deselect_and_disable_buttons() def start_monitor(self): self.after( 100, ml.update_monitor, self.root, self.monitor_elements['lines'], self.monitor_elements['ax_net'], self.canvas_usage, self.canvas_net, self.label_load, self.label_threads ) def start_alarm_checker(self): self.after(100, al.check_alarms, self.root, self.alarm_list_widget, self.show_alarm_popup) if __name__ == "__main__": app = MainApplication() app.mainloop()