import tkinter as tk
from tkinter import Menu, filedialog, simpledialog, messagebox
from tkinter import ttk
import threading
import time
import datetime
import webbrowser
import subprocess 
import os 
import collections 
import random 
import urllib.request 
import re 
import urllib.error 

# --- Importar y configurar Pygame para la música/alarma ---
try:
    import pygame
    # Intentar inicializar Pygame Mixer. Si falla, deshabilitar la música.
    try:
        pygame.mixer.init()
        MUSIC_AVAILABLE = True
    except pygame.error as e: 
        MUSIC_AVAILABLE = False
        print(f"Error al inicializar pygame mixer: {e}. Funcionalidad de audio deshabilitada.")
        
    MUSIC_FILE = "music.mp3" # Archivo música de fondo
    ALARM_FILE = "alarm.mp3" # <--- ¡NUEVO ARCHIVO DE ALARMA!
    
except ImportError:
    MUSIC_AVAILABLE = False
    print("Pygame no está instalado. La funcionalidad de audio no estará disponible.")


# --- Importación específica para el sonido de alarma (Solo Windows) ---
try:
    import winsound
    WINSOUND_AVAILABLE = True
except ImportError:
    WINSOUND_AVAILABLE = False
# ---------------------------------------------------------------------

# --- IMPORTACIONES PARA GRÁFICOS DE RECURSOS ---
import psutil 
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 
# -----------------------------------------------

# --- HILOS GLOBALES PARA CONTROL ---
cpu_monitor = None
memory_monitor = None
disk_monitor = None
net_monitor = None
network_thread = None 
clock_thread = None 
status_bar_time_thread = None
alarm_stop_event = threading.Event()
countdown_after_id = None 
# --- VARIABLES GLOBALES DEL JUEGO DE COCHES ---
race_threads = [] 
race_lock = threading.Lock() 
winner = None 
race_stop_event = threading.Event() 
# --- VARIABLES GLOBALES DE MÚSICA ---
music_state = False # False = parada, True = sonando
# ---------------------------------------------


# --- CLASE DE GESTIÓN DE LA BARRA DE ESTADO ---
class StatusBarManager:
    """Gestiona los tres labels dinámicos de la barra inferior (más los dos nuevos)."""
    def __init__(self, parent_frame, root):
        self.parent_frame = parent_frame
        self.root = root
        self.labels = {}
        self._initialize_labels()

    def _initialize_labels(self):
        label_configs = [
            ("correos", "Correos sin leer (0)", "yellow"),
            ("temperatura", "Temperatura local (0°C)", "orange"),
            ("status_1", "Estado 1 (Libre)", "green"),
            ("status_2", "Estado 2 (Listo)", "blue"),
            ("status_3", "Estado 3", "cyan"),
        ]
        
        for name, initial_text, bg_color in label_configs[2:]: 
             label = tk.Label(
                self.parent_frame,
                text=initial_text,
                bg=bg_color,
                anchor="w",
                width=20,
                bd=1,
                relief="sunken"
            )
             label.pack(side="left", fill="x", expand=True)
             self.labels[name] = label
             
        for name, initial_text, bg_color in label_configs[:2]: 
             label = tk.Label(
                self.parent_frame,
                text=initial_text,
                bg=bg_color,
                anchor="w",
                width=25,
                bd=1,
                relief="flat",
                font=("Helvetica", 10)
            )
             label.pack(side="right", fill="x", padx=5)
             self.labels[name] = label


    def set_status(self, label_name, text, bg_color=None):
        if label_name in self.labels:
            label = self.labels[label_name]
            config = {"text": text}
            if bg_color:
                config["bg"] = bg_color
            
            self.root.after(0, label.config, config)
        else:
            print(f"Error: Label '{label_name}' no encontrado.")


# --- CLASE PARA MONITORIZAR Y GRAFICAR RECURSOS ---
class SystemMonitor:
    def __init__(self, master, resource_type, width=4, height=3): 
        self.master = master
        self.resource_type = resource_type 
        self.update_interval = 1000

        if resource_type == 'net':
            self.data_history_sent = collections.deque([0] * 30, maxlen=30)
            self.data_history_recv = collections.deque([0] * 30, maxlen=30)
            self._last_net_io = psutil.net_io_counters()
        else:
            self.data_history = collections.deque([0] * 30, maxlen=30) 

        self.fig, self.ax = plt.subplots(figsize=(width, height), dpi=100) 
        
        self.canvas = FigureCanvasTkAgg(self.fig, master=master)
        self.canvas_widget = self.canvas.get_tk_widget()
        self.canvas_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        self.setup_plot()
        
        self.stop_event = threading.Event()
        self.monitor_thread = threading.Thread(target=self._data_collection_loop, daemon=True)
        self.monitor_thread.start()

    def setup_plot(self):
        title_map = {'cpu': "Uso de CPU", 'memory': "Uso de RAM", 'disk': "Uso de Disco (I/O)", 'net': "Tasa de Red (Kb/s)"}
        ylabel_map = {'cpu': "% Uso", 'memory': "% Uso", 'disk': "% Disco", 'net': "Kb/s"}
        
        self.ax.set_title(title_map.get(self.resource_type, "Recurso"), fontsize=10)
        self.ax.set_ylim(0, 100 if self.resource_type != 'net' else 500) 
        self.ax.set_ylabel(ylabel_map.get(self.resource_type, "Valor"), fontsize=8)
        self.ax.set_xlabel("Tiempo (s)", fontsize=8)
        self.ax.tick_params(axis='both', which='major', labelsize=7)
        
        if self.resource_type == 'net':
            self.line_sent, = self.ax.plot(self.data_history_sent, label='Enviado (Kb)', color='red')
            self.line_recv, = self.ax.plot(self.data_history_recv, label='Recibido (Kb)', color='blue')
            self.ax.legend(loc='upper right', fontsize=7)
        else:
            self.line, = self.ax.plot(self.data_history, label=f'{self.resource_type.upper()} %', color='blue')
            self.ax.legend(loc='upper right', fontsize=7)

        self.fig.tight_layout(pad=1.0) 

    def _get_data(self):
        if self.resource_type == 'cpu':
            return psutil.cpu_percent(interval=0.1) 
        elif self.resource_type == 'memory':
            return psutil.virtual_memory().percent
        elif self.resource_type == 'disk':
            return psutil.disk_usage('/').percent
        elif self.resource_type == 'net':
            current_net_io = psutil.net_io_counters()
            time_diff = self.update_interval / 1000.0
            
            bytes_sent = current_net_io.bytes_sent - self._last_net_io.bytes_sent
            bytes_recv = current_net_io.bytes_recv - self._last_net_io.bytes_recv
            
            kbs_sent = (bytes_sent / 1024) / time_diff
            kbs_recv = (bytes_recv / 1024) / time_diff
            
            self._last_net_io = current_net_io
            return kbs_sent, kbs_recv
        return 0
    
    def _data_collection_loop(self):
        while not self.stop_event.is_set():
            data_point = self._get_data()
            
            if self.resource_type == 'net':
                kbs_sent, kbs_recv = data_point
                self.data_history_sent.append(kbs_sent)
                self.data_history_recv.append(kbs_recv)
            else:
                self.data_history.append(data_point)
            
            self.master.after(0, self._update_gui)
            
            time.sleep(self.update_interval / 1000.0)
            
    def _update_gui(self):
        
        if self.resource_type == 'net':
            self.line_sent.set_ydata(self.data_history_sent)
            self.line_recv.set_ydata(self.data_history_recv)
            
            latest_sent = self.data_history_sent[-1]
            latest_recv = self.data_history_recv[-1]
            self.ax.set_title(f"Tasa de Red: S: {latest_sent:.1f} Kb/s, R: {latest_recv:.1f} Kb/s", fontsize=10)
        else:
            self.line.set_ydata(self.data_history)
            latest_value = self.data_history[-1]
            self.ax.set_title(f"Uso de {self.resource_type.upper()}: {latest_value:.1f}%", fontsize=10)
        
        self.canvas.draw_idle()
        
    def stop(self):
        self.stop_event.set()


# --- GESTOR DE HILO DE RED ---
class NetworkTrafficThread:
    """Hilo que monitorea y actualiza el tráfico total de red."""
    def __init__(self, root, label_in, label_out):
        self.root = root
        self.label_in = label_in
        self.label_out = label_out
        self.update_interval = 1000 # 1 segundo
        self.stop_event = threading.Event()
        
        self.initial_net_io = psutil.net_io_counters()

        self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.thread.start()

    def _monitor_loop(self):
        while not self.stop_event.is_set():
            current_net_io = psutil.net_io_counters()
            
            bytes_sent_total = current_net_io.bytes_sent - self.initial_net_io.bytes_sent
            bytes_recv_total = current_net_io.bytes_recv - self.initial_net_io.bytes_recv
            
            kb_sent_total = bytes_sent_total / 1024
            kb_recv_total = bytes_recv_total / 1024

            self.root.after(0, self._update_gui, kb_sent_total, kb_recv_total)
            
            time.sleep(self.update_interval / 1000.0)

    def _update_gui(self, kb_sent, kb_recv):
        self.label_in.config(text=f"KB Recibidos (Input): {kb_recv:,.2f} KB")
        self.label_out.config(text=f"KB Enviados (Output): {kb_sent:,.2f} KB")
        
    def stop(self):
        self.stop_event.set()


# --- FUNCIONES DE MÚSICA ---

def initialize_music():
    """Carga el archivo de música. Llamar una vez al inicio o al cambiar a T2."""
    global MUSIC_AVAILABLE
    
    # Solo intentamos cargar si Pygame está disponible y si no hay música ya cargada
    if MUSIC_AVAILABLE and not pygame.mixer.music.get_busy() and pygame.mixer.get_init():
        try:
            pygame.mixer.music.load(MUSIC_FILE)
            status_manager.set_status("status_3", f"Música cargada: {MUSIC_FILE}", "purple")
        except pygame.error as e:
            messagebox.showerror("Error de Audio", f"No se pudo cargar el archivo '{MUSIC_FILE}'. Asegúrate de que existe y es compatible. Error: {e}")
            MUSIC_AVAILABLE = False 
            status_manager.set_status("status_3", "Error al cargar música", "magenta")


def toggle_music(button):
    """Inicia o detiene la reproducción de música en bucle."""
    global music_state, MUSIC_AVAILABLE
    
    if not MUSIC_AVAILABLE:
        messagebox.showwarning("Advertencia", "La funcionalidad de música no está disponible (Pygame o archivo no encontrado).")
        return

    if not music_state:
        # Asegurarse de que el archivo esté cargado antes de intentar reproducir
        if pygame.mixer.music.get_pos() == 0 and not pygame.mixer.music.get_busy():
            initialize_music() 
            if not MUSIC_AVAILABLE:
                return

        # 1. Iniciar la música
        try:
            # -1 para reproducir en bucle (infinitamente)
            pygame.mixer.music.play(-1) 
            music_state = True
            button.config(text="STOP MÚSICA (En Bucle)", style="Red.TButton")
            status_manager.set_status("status_3", "Música sonando en bucle", "purple")
        except pygame.error as e:
            messagebox.showerror("Error de Reproducción", f"Error al reproducir: {e}")
            MUSIC_AVAILABLE = False
    else:
        # 2. Detener la música
        pygame.mixer.music.stop()
        music_state = False
        button.config(text="MÚSICA (Play)", style="Green.TButton")
        status_manager.set_status("status_3", "Música detenida", "cyan")

def stop_music_clean():
    """Detiene la música de forma limpia al salir de la app o cambiar de pestaña."""
    global music_state, MUSIC_AVAILABLE
    if MUSIC_AVAILABLE and music_state:
        pygame.mixer.music.stop()
        music_state = False

# --- FIN DE FUNCIONES DE MÚSICA ---


# --- FUNCIONES PARA LA PESTAÑA T2: SCRAPING (Implementación Books To Scrape) ---

def run_simple_scraper(label_resultado):
    """
    Inicia el scraping de la página de práctica de libros (books.toscrape.com).
    """
    URL = "http://books.toscrape.com/"
    status_manager.set_status("status_3", "Iniciando scraping de libros...", "orange")
    label_resultado.config(text="Buscando títulos...", fg="orange")
    
    # 2. Hilo para no bloquear la GUI
    threading.Thread(target=_scrape_thread, args=(URL, label_resultado), daemon=True).start()

def _scrape_thread(URL, label_resultado):
    """Ejecuta la lógica de scraping de libros dentro de un hilo."""
    global root, status_manager
    
    # User-Agent no es estrictamente necesario aquí, pero es una buena práctica
    USER_AGENT = 'Mozilla/5.0' 
    req = urllib.request.Request(URL, headers={'User-Agent': USER_AGENT})

    try:
        # 1. Obtener la página
        with urllib.request.urlopen(req, timeout=10) as response:
            html = response.read().decode('utf-8')

        # 2. Buscamos todos los títulos de libros.
        # El patrón busca la etiqueta HTML: title="[TÍTULO DEL LIBRO]"
        title_matches = re.findall(r'title="(.*?)"', html)
        
        # Filtramos los títulos que no son de libros, buscando títulos largos
        # Esto es una heurística; el primer libro tiene un título corto ("A Light in the Attic")
        book_titles = [t for t in title_matches if len(t) > 30 and t != "A Light in the Attic"]
        
        if book_titles:
            resultado_text = "✅ **Títulos de Libros Encontrados (books.toscrape.com):**\n\n"
            for i, title in enumerate(book_titles[:20]): # Limitar a 20 resultados
                resultado_text += f"[{i+1:02d}] {title}\n"
                
            color = "green"
            status_text = f"Scraping OK: {len(book_titles)} títulos encontrados"
        else:
            resultado_text = "⚠️ Advertencia: No se pudo encontrar el patrón o el HTML ha cambiado."
            color = "blue"
            status_text = "Scraping parcial/patrón fallido"

    except urllib.error.HTTPError as e:
        resultado_text = f"❌ Error HTTP: El servidor denegó el acceso (Error {e.code})."
        color = "red"
        status_text = "Error HTTP (4xx/5xx)"
    except Exception as e:
        resultado_text = f"❌ Error de Conexión/Scraping: {e}"
        color = "red"
        status_text = "Error de Red/Otro"

    # 4. Actualizar la GUI desde el hilo principal
    root.after(0, label_resultado.config, {"text": resultado_text, "fg": color, "font": ("Consolas", 10)})
    status_manager.set_status("status_3", status_text, color)


def setup_scraping_tab(tab_frame):
    """Configura la interfaz para la pestaña de Scraping."""
    for widget in tab_frame.winfo_children():
        widget.destroy()

    main_frame = tk.Frame(tab_frame)
    main_frame.pack(expand=True, padx=20, pady=20, fill="both")
    
    tk.Label(main_frame, text="Web Scraping de Práctica (Books to Scrape)", font=("Arial", 16, "bold")).pack(pady=10)
    
    tk.Label(
        main_frame, 
        text="Esto ejecuta un hilo que descarga la página de prueba 'books.toscrape.com' y extrae los 20 primeros títulos.", 
        wraplength=500,
        justify="left"
    ).pack(pady=5)
    
    # Etiqueta para mostrar el resultado
    label_resultado = tk.Label(
        main_frame,
        text="Pulse el botón 'Scrapear' para comenzar...",
        font=("Consolas", 10),
        fg="gray",
        justify="left"
    )
    label_resultado.pack(pady=20, padx=10)
    
    # Botón de ejecución
    ttk.Button(
        main_frame,
        text="SCRAPEAR LIBROS DE PRÁCTICA",
        style="Green.TButton",
        command=lambda: run_simple_scraper(label_resultado)
    ).pack(pady=15)

# --- FIN DE FUNCIONES DE SCRAPING ---


# --- FUNCIONES DE LA GUI GENERAL ---
def update_time(status_bar, stop_event):
    """Bucle que actualiza la fecha y hora en la barra de estado."""
    while not stop_event.is_set():
        now = datetime.datetime.now()
        day_of_week = now.strftime("%A")
        time_str = now.strftime("%H:%M:%S")
        date_str = now.strftime("%Y-%m-%d")
        label_text = f"{day_of_week}, {date_str} - {time_str}"
        status_bar.after(1000, status_bar.config, {"text": label_text})
        time.sleep(1)

def stop_status_bar_time_thread():
    """Detiene el hilo de la hora de la barra de estado."""
    global status_bar_time_thread
    if status_bar_time_thread and hasattr(status_bar_time_thread, 'stop_event'):
        status_bar_time_thread.stop_event.set()
        status_bar_time_thread = None


def open_external_url(url):
    status_manager.set_status("status_2", f"Abriendo URL...", "orange")
    webbrowser.open(url)
    root.after(2000, lambda: status_manager.set_status("status_2", "URL lanzada.", "blue"))

def run_bash_backup():
    def execute_script():
        status_manager.set_status("status_3", "Copia en curso (Bash)...", "red")
        script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "backup_script.sh")
        command = [script_path] 
        
        try:
            subprocess.run(command, capture_output=True, text=True, check=True)
            status_manager.set_status("status_3", "Copia OK: ¡Éxito!", "lime")
        except subprocess.CalledProcessError as e:
            status_manager.set_status("status_3", f"Copia ¡FALLO! Código: {e.returncode}", "magenta")
        except FileNotFoundError:
            status_manager.set_status("status_3", "Error: Script no encontrado", "black")
            
        root.after(5000, lambda: status_manager.set_status("status_3", "Esperando tarea", "cyan"))

    threading.Thread(target=execute_script, daemon=True).start()

def save_text_file(text_widget):
    text_content = text_widget.get("1.0", "end-1c") 
    
    file_path = filedialog.asksaveasfilename(
        defaultextension=".txt", 
        filetypes=[
            ("Archivos de Texto", "*.txt"),
            ("Todos los archivos", "*.*")
        ],
        title="Guardar documento del editor"
    )
    
    if file_path:
        try:
            with open(file_path, "w", encoding="utf-8") as file:
                file.write(text_content)
            
            status_manager.set_status("status_3", f"Archivo guardado: {os.path.basename(file_path)}", "lime")
            root.after(3000, lambda: status_manager.set_status("status_3", "Estado 3", "cyan"))
            
        except Exception as e:
            status_manager.set_status("status_3", f"Error al guardar: {e}", "magenta")
            root.after(3000, lambda: status_manager.set_status("status_3", "Estado 3", "cyan"))


# --- CONTROL DE HILOS Y PESTAÑAS T1 ---
class SystemMonitor:
    def __init__(self, master, resource_type, width=4, height=3): 
        self.master = master
        self.resource_type = resource_type 
        self.update_interval = 1000

        if resource_type == 'net':
            self.data_history_sent = collections.deque([0] * 30, maxlen=30)
            self.data_history_recv = collections.deque([0] * 30, maxlen=30)
            self._last_net_io = psutil.net_io_counters()
        else:
            self.data_history = collections.deque([0] * 30, maxlen=30) 

        self.fig, self.ax = plt.subplots(figsize=(width, height), dpi=100) 
        
        self.canvas = FigureCanvasTkAgg(self.fig, master=master)
        self.canvas_widget = self.canvas.get_tk_widget()
        self.canvas_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        self.setup_plot()
        
        self.stop_event = threading.Event()
        self.monitor_thread = threading.Thread(target=self._data_collection_loop, daemon=True)
        self.monitor_thread.start()

    def setup_plot(self):
        title_map = {'cpu': "Uso de CPU", 'memory': "Uso de RAM", 'disk': "Uso de Disco (I/O)", 'net': "Tasa de Red (Kb/s)"}
        ylabel_map = {'cpu': "% Uso", 'memory': "% Uso", 'disk': "% Disco", 'net': "Kb/s"}
        
        self.ax.set_title(title_map.get(self.resource_type, "Recurso"), fontsize=10)
        self.ax.set_ylim(0, 100 if self.resource_type != 'net' else 500) 
        self.ax.set_ylabel(ylabel_map.get(self.resource_type, "Valor"), fontsize=8)
        self.ax.set_xlabel("Tiempo (s)", fontsize=8)
        self.ax.tick_params(axis='both', which='major', labelsize=7)
        
        if self.resource_type == 'net':
            self.line_sent, = self.ax.plot(self.data_history_sent, label='Enviado (Kb)', color='red')
            self.line_recv, = self.ax.plot(self.data_history_recv, label='Recibido (Kb)', color='blue')
            self.ax.legend(loc='upper right', fontsize=7)
        else:
            self.line, = self.ax.plot(self.data_history, label=f'{self.resource_type.upper()} %', color='blue')
            self.ax.legend(loc='upper right', fontsize=7)

        self.fig.tight_layout(pad=1.0) 

    def _get_data(self):
        if self.resource_type == 'cpu':
            return psutil.cpu_percent(interval=0.1) 
        elif self.resource_type == 'memory':
            return psutil.virtual_memory().percent
        elif self.resource_type == 'disk':
            return psutil.disk_usage('/').percent
        elif self.resource_type == 'net':
            current_net_io = psutil.net_io_counters()
            time_diff = self.update_interval / 1000.0
            
            bytes_sent = current_net_io.bytes_sent - self._last_net_io.bytes_sent
            bytes_recv = current_net_io.bytes_recv - self._last_net_io.bytes_recv
            
            kbs_sent = (bytes_sent / 1024) / time_diff
            kbs_recv = (bytes_recv / 1024) / time_diff
            
            self._last_net_io = current_net_io
            return kbs_sent, kbs_recv
        return 0
    
    def _data_collection_loop(self):
        while not self.stop_event.is_set():
            data_point = self._get_data()
            
            if self.resource_type == 'net':
                kbs_sent, kbs_recv = data_point
                self.data_history_sent.append(kbs_sent)
                self.data_history_recv.append(kbs_recv)
            else:
                self.data_history.append(data_point)
            
            self.master.after(0, self._update_gui)
            
            time.sleep(self.update_interval / 1000.0)
            
    def _update_gui(self):
        
        if self.resource_type == 'net':
            self.line_sent.set_ydata(self.data_history_sent)
            self.line_recv.set_ydata(self.data_history_recv)
            
            latest_sent = self.data_history_sent[-1]
            latest_recv = self.data_history_recv[-1]
            self.ax.set_title(f"Tasa de Red: S: {latest_sent:.1f} Kb/s, R: {latest_recv:.1f} Kb/s", fontsize=10)
        else:
            self.line.set_ydata(self.data_history)
            latest_value = self.data_history[-1]
            self.ax.set_title(f"Uso de {self.resource_type.upper()}: {latest_value:.1f}%", fontsize=10)
        
        self.canvas.draw_idle()
        
    def stop(self):
        self.stop_event.set()

class NetworkTrafficThread:
    """Hilo que monitorea y actualiza el tráfico total de red."""
    def __init__(self, root, label_in, label_out):
        self.root = root
        self.label_in = label_in
        self.label_out = label_out
        self.update_interval = 1000 # 1 segundo
        self.stop_event = threading.Event()
        
        self.initial_net_io = psutil.net_io_counters()

        self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.thread.start()

    def _monitor_loop(self):
        while not self.stop_event.is_set():
            current_net_io = psutil.net_io_counters()
            
            bytes_sent_total = current_net_io.bytes_sent - self.initial_net_io.bytes_sent
            bytes_recv_total = current_net_io.bytes_recv - self.initial_net_io.bytes_recv
            
            kb_sent_total = bytes_sent_total / 1024
            kb_recv_total = bytes_recv_total / 1024

            self.root.after(0, self._update_gui, kb_sent_total, kb_recv_total)
            
            time.sleep(self.update_interval / 1000.0)

    def _update_gui(self, kb_sent, kb_recv):
        self.label_in.config(text=f"KB Recibidos (Input): {kb_recv:,.2f} KB")
        self.label_out.config(text=f"KB Enviados (Output): {kb_sent:,.2f} KB")
        
    def stop(self):
        self.stop_event.set()


def setup_monitors_tab(tab_frame):
    tk.Label(tab_frame, text="Monitorización de Recursos del Sistema", font=("Arial", 14, "bold")).pack(pady=10)
    
    monitor_grid_frame = tk.Frame(tab_frame)
    monitor_grid_frame.pack(fill="both", expand=True, padx=10, pady=10)
    
    monitor_grid_frame.columnconfigure(0, weight=1)
    monitor_grid_frame.columnconfigure(1, weight=1)
    monitor_grid_frame.rowconfigure(0, weight=1)
    monitor_grid_frame.rowconfigure(1, weight=1)

    global cpu_monitor, memory_monitor, disk_monitor, net_monitor
    
    cpu_frame = tk.Frame(monitor_grid_frame, bd=1, relief="solid")
    cpu_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
    cpu_monitor = SystemMonitor(cpu_frame, 'cpu') 

    memory_frame = tk.Frame(monitor_grid_frame, bd=1, relief="solid")
    memory_frame.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
    memory_monitor = SystemMonitor(memory_frame, 'memory') 

    disk_frame = tk.Frame(monitor_grid_frame, bd=1, relief="solid")
    disk_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
    disk_monitor = SystemMonitor(disk_frame, 'disk') 

    net_frame = tk.Frame(monitor_grid_frame, bd=1, relief="solid")
    net_frame.grid(row=1, column=1, sticky="nsew", padx=5, pady=5)
    net_monitor = SystemMonitor(net_frame, 'net') 
    
def setup_editor_tab(tab_frame):
    tk.Label(tab_frame, text="Editor de Texto Simple", font=("Arial", 14, "bold")).pack(pady=10)
    
    editor_frame = tk.Frame(tab_frame)
    editor_frame.pack(fill="both", expand=True, padx=10, pady=10)
    
    editor_scrollbar = ttk.Scrollbar(editor_frame)
    editor_scrollbar.pack(side="right", fill="y")
    
    text_area = tk.Text(
        editor_frame, 
        wrap="word", 
        undo=True,   
        font=("Consolas", 11),
        yscrollcommand=editor_scrollbar.set
    )
    text_area.pack(fill="both", expand=True)
    editor_scrollbar.config(command=text_area.yview)

    ttk.Button(
        tab_frame, 
        text="Guardar Documento", 
        command=lambda: save_text_file(text_area) 
    ).pack(pady=5)

def setup_network_thread_tab(tab_frame):
    """Configura la pestaña 'Hilo' con el contador de tráfico de red, centrado y ampliado."""
    global network_thread
    
    center_frame = tk.Frame(tab_frame)
    center_frame.pack(expand=True) 

    tk.Label(
        center_frame, 
        text="Monitorización Total de Tráfico de Red (KB)", 
        font=("Arial", 18, "bold") 
    ).pack(pady=20)
    
    label_in = tk.Label(
        center_frame, 
        text="KB Recibidos (Input): -- KB", 
        font=("Consolas", 20, "bold"), 
        fg="blue"
    )
    label_in.pack(pady=15, padx=50) 

    label_out = tk.Label(
        center_frame, 
        text="KB Enviados (Output): -- KB", 
        font=("Consolas", 20, "bold"), 
        fg="red"
    )
    label_out.pack(pady=15, padx=50) 
    
    tk.Label(
        center_frame, 
        text="El conteo se reinicia al navegar fuera de la pestaña T1.", 
        fg="gray", 
        font=("Arial", 10)
    ).pack(pady=30)
    
    if not network_thread:
        network_thread = NetworkTrafficThread(root, label_in, label_out)


def stop_network_thread():
    global network_thread
    if 'network_thread' in globals() and network_thread:
        network_thread.stop()
        network_thread = None


def setup_t1_processes(tab_frame, root_instance):
    """Configura el Notebook interno para la pestaña T1. Procesos."""
    for widget in tab_frame.winfo_children():
        widget.destroy()
        
    internal_notebook = ttk.Notebook(tab_frame)
    internal_notebook.pack(fill="both", expand=True, padx=5, pady=5)

    estado_tab = ttk.Frame(internal_notebook)
    internal_notebook.add(estado_tab, text="Estado")
    setup_monitors_tab(estado_tab)
    
    editor_tab = ttk.Frame(internal_notebook)
    internal_notebook.add(editor_tab, text="Editor de texto")
    setup_editor_tab(editor_tab)
    
    hilo_tab = ttk.Frame(internal_notebook)
    internal_notebook.add(hilo_tab, text="Hilo")
    
    def handle_internal_tab_change(event):
        selected_index = internal_notebook.index(internal_notebook.select())
        current_tab_title = internal_notebook.tab(selected_index, "text")
        
        if internal_notebook.tab(0, "text") != "Estado": 
             stop_monitors() 
        
        if current_tab_title == "Hilo":
            if not network_thread:
                 setup_network_thread_tab(hilo_tab)
        else:
            stop_network_thread()

    internal_notebook.bind("<<NotebookTabChanged>>", handle_internal_tab_change)


def stop_monitors():
    """Detiene todos los monitores y el hilo de red de T1."""
    global cpu_monitor, memory_monitor, disk_monitor, net_monitor
    
    if 'cpu_monitor' in globals() and cpu_monitor:
        cpu_monitor.stop()
        cpu_monitor = None
    if 'memory_monitor' in globals() and memory_monitor:
        memory_monitor.stop()
        memory_monitor = None
    if 'disk_monitor' in globals() and disk_monitor:
        disk_monitor.stop()
        disk_monitor = None
    if 'net_monitor' in globals() and net_monitor:
        net_monitor.stop()
        net_monitor = None
        
    stop_network_thread()

# --- FUNCIONES PARA LA PESTAÑA T2: RELOJ Y ALARMA ---

def stop_clock_thread():
    """Detiene el hilo del reloj digital y el hilo de la alarma."""
    global clock_thread
    if 'clock_thread' in globals() and clock_thread and hasattr(clock_thread, 'stop_event'):
        clock_thread.stop_event.set()
        clock_thread = None
    
    stop_alarm_countdown()
    
def stop_alarm_countdown():
    """Detiene la cuenta atrás de la alarma si está activa."""
    global countdown_after_id, alarm_stop_event
    
    alarm_stop_event.set() 
    
    if countdown_after_id:
        root.after_cancel(countdown_after_id)
        countdown_after_id = None

def update_digital_clock(label_clock, stop_event):
    """Función de bucle para actualizar la hora digital en un hilo."""
    while not stop_event.is_set():
        now = datetime.datetime.now()
        time_str = now.strftime("%H:%M:%S")
        date_str = now.strftime("%Y-%m-%d")
        
        display_text = f"{date_str}\n{time_str}"
        
        root.after(0, label_clock.config, {"text": display_text})
        time.sleep(1)

def play_alarm_sound():
    """Produce una notificación sonora."""
    global MUSIC_AVAILABLE
    if MUSIC_AVAILABLE:
        # Detenemos la música de fondo para que la alarma se escuche clara
        pygame.mixer.music.stop()
        
        try:
            # Cargamos y reproducimos el archivo de alarma una sola vez
            alarm_sound = pygame.mixer.Sound(ALARM_FILE)
            alarm_sound.play()
        except pygame.error as e:
            messagebox.showerror("Error de Audio", f"No se pudo reproducir {ALARM_FILE}. Error: {e}")
            
    if WINSOUND_AVAILABLE:
        # Fallback de Windows si Pygame no funciona bien
        winsound.Beep(1000, 1000)
        winsound.MessageBeep(winsound.MB_ICONEXCLAMATION)
    else:
        print("\n*** ALARMA DISPARADA (Sin sonido de sistema) ***\n")

def alarm_loop(wait_seconds, alarm_time_str, label_status):
    """Hilo de la alarma que espera y se dispara."""
    global alarm_stop_event
    
    time.sleep(wait_seconds)
    
    if alarm_stop_event.is_set():
        return
        
    root.after(0, play_alarm_sound)
    root.after(0, lambda: messagebox.showinfo("ALARMA", f"¡ALARMA TERMINADA! Son las {alarm_time_str}"))
    root.after(0, lambda: label_status.config(text="ALARMA TERMINADA", fg="red"))
    root.after(0, stop_alarm_countdown)

def start_alarm_thread(alarm_time_str, label_status, label_countdown):
    """Calcula el tiempo de espera, inicia el hilo de alarma y la cuenta atrás."""
    global alarm_stop_event
    
    try:
        alarm_hour, alarm_minute = map(int, alarm_time_str.split(':'))
        
        if not (0 <= alarm_hour <= 23 and 0 <= alarm_minute <= 59):
            raise ValueError("Hora fuera de rango.")
            
    except ValueError:
        messagebox.showerror("Error de Formato", "Por favor, introduce la hora en formato HH:MM (ej: 07:30)")
        return
        
    now = datetime.datetime.now()
    
    alarm_dt = now.replace(hour=alarm_hour, minute=alarm_minute, second=0, microsecond=0)
    
    if alarm_dt <= now:
        alarm_dt += datetime.timedelta(days=1)
        
    time_difference = alarm_dt - now
    wait_seconds = time_difference.total_seconds()
    
    stop_alarm_countdown()
    alarm_stop_event.clear()
    
    label_status.config(text=f"ALARMA ACTIVA: {alarm_time_str} (Hacia adelante)", fg="orange")
    update_countdown_label(alarm_dt, label_countdown, label_status)
    
    threading.Thread(target=alarm_loop, args=(wait_seconds, alarm_time_str, label_status), daemon=True).start()

def update_countdown_label(alarm_dt, label_countdown, label_status):
    """Actualiza la etiqueta de cuenta atrás cada segundo."""
    global countdown_after_id, alarm_stop_event
    
    now = datetime.datetime.now()
    
    if alarm_stop_event.is_set() or alarm_dt <= now:
        label_countdown.config(text="00:00:00")
        label_status.config(text="ALARMA INACTIVA", fg="gray")
        return
        
    time_remaining = alarm_dt - now
    
    total_seconds = int(time_remaining.total_seconds())
    
    hours, remainder = divmod(total_seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    
    countdown_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
    
    label_countdown.config(text=countdown_str)
    
    countdown_after_id = root.after(1000, lambda: update_countdown_label(alarm_dt, label_countdown, label_status))


def set_alarm(entry_hour, label_status, label_countdown):
    """Recoge la hora de entrada y lanza el hilo de alarma."""
    alarm_time = entry_hour.get()
    if alarm_time:
        start_alarm_thread(alarm_time, label_status, label_countdown)
        entry_hour.delete(0, tk.END)
        
def setup_clock_tab(tab_frame):
    """Configura la interfaz de la subpestaña Reloj/Alarma."""
    for widget in tab_frame.winfo_children():
        widget.destroy()

    center_frame = tk.Frame(tab_frame)
    center_frame.pack(expand=True, pady=20)
    
    tk.Label(center_frame, text="Reloj Digital y Alarma", font=("Arial", 16, "bold")).pack(pady=10)
    
    label_clock = tk.Label(
        center_frame,
        text="00:00:00",
        font=("Consolas", 60, "bold"),
        bg="black",
        fg="#00FF00",
        relief="sunken",
        padx=20, pady=10
    )
    label_clock.pack(pady=20)

    global clock_thread
    stop_clock_thread()
    
    stop_event = threading.Event() 
    clock_thread = threading.Thread(
        target=update_digital_clock, 
        args=(label_clock, stop_event), 
        daemon=True
    )
    clock_thread.stop_event = stop_event
    clock_thread.start()
    
    alarm_frame = ttk.LabelFrame(center_frame, text="Configurar Alarma", padding=10)
    alarm_frame.pack(pady=30, padx=20, fill="x")
    
    tk.Label(alarm_frame, text="Hora (HH:MM):", font=("Arial", 12)).pack(side="left", padx=5)
    
    entry_hour = ttk.Entry(alarm_frame, width=8, font=("Arial", 12))
    entry_hour.pack(side="left", padx=5)
    
    label_alarm_status = tk.Label(alarm_frame, text="ALARMA INACTIVA", fg="gray", font=("Arial", 12, "italic"))
    label_alarm_status.pack(side="right", padx=15)
    
    label_countdown = tk.Label(alarm_frame, text="00:00:00", fg="red", font=("Consolas", 14, "bold"))
    label_countdown.pack(side="right", padx=10)
    
    ttk.Button(
        alarm_frame, 
        text="Establecer Alarma", 
        command=lambda: set_alarm(entry_hour, label_alarm_status, label_countdown)
    ).pack(side="right", padx=10)
    
    ttk.Button(
        alarm_frame, 
        text="Cancelar", 
        command=lambda: stop_alarm_countdown() or label_alarm_status.config(text="ALARMA CANCELADA", fg="gray")
    ).pack(side="right", padx=10)
    
# --- FUNCIONES PARA LA PESTAÑA T2: COCHES ---

def stop_race():
    """Detiene la carrera actual, si está en curso."""
    global race_stop_event, race_threads, winner
    
    if not race_stop_event.is_set():
        race_stop_event.set()
        
    for thread in race_threads:
        if thread.is_alive():
            pass
            
    race_threads = []
    winner = None

def car_movement(canvas, car_id, car_color, label_result, start_x, goal_x):
    """Hilo que mueve un coche de color."""
    global winner, race_lock
    
    current_x = start_x
    delay = random.randint(10, 50) / 1000.0 
    step = 5 

    while current_x < goal_x and not race_stop_event.is_set():
        current_x += step
        
        canvas.after(0, canvas.coords, car_id, current_x, canvas.coords(car_id)[1], current_x + 50, canvas.coords(car_id)[3])
        
        time.sleep(delay)

    if not race_stop_event.is_set():
        with race_lock:
            if winner is None:
                winner = car_color
                
                race_stop_event.set() 
                
                root.after(0, label_result.config, {"text": f"🎉 ¡Ha ganado el coche {car_color.upper()}! 🎉", "fg": car_color, "font": ("Arial", 18, "bold")})
                
                status_manager.set_status("status_3", f"Ganador: {car_color}", car_color)
                
            else:
                pass
    
def start_race(canvas, cars, label_result):
    """Inicia la carrera, lanzando un hilo para cada coche."""
    
    stop_race()
    
    global race_threads, winner, race_stop_event
    
    winner = None
    race_stop_event.clear()
    
    label_result.config(text="¡CARRERA EN CURSO!", fg="black", font=("Arial", 14, "italic"))
    
    START_X = 10
    GOAL_X = canvas.winfo_width() - 60 

    for car_color, car_id in cars:
        
        canvas.coords(car_id, START_X, canvas.coords(car_id)[1], START_X + 50, canvas.coords(car_id)[3])
        
        thread = threading.Thread(
            target=car_movement, 
            args=(canvas, car_id, car_color, label_result, START_X, GOAL_X), 
            daemon=True
        )
        race_threads.append(thread)
        thread.start()
        
    status_manager.set_status("status_3", "Carrera iniciada...", "yellow")
    

def setup_race_game(tab_frame):
    """Configura la interfaz del juego de la carrera de coches."""
    for widget in tab_frame.winfo_children():
        widget.destroy()

    control_frame = tk.Frame(tab_frame)
    control_frame.pack(pady=10, fill="x")
    
    tk.Label(control_frame, text="Simulación de Carrera Multihilo (Coches)", font=("Arial", 16, "bold")).pack(side="left", padx=15)
    
    label_result = tk.Label(control_frame, text="Presiona 'START' para comenzar", fg="black", font=("Arial", 14))
    label_result.pack(side="right", padx=15)
    
    canvas_frame = tk.Frame(tab_frame, bd=2, relief="sunken")
    canvas_frame.pack(fill="both", expand=True, padx=10, pady=5)
    
    canvas = tk.Canvas(canvas_frame, bg="lightgray")
    canvas.pack(fill="both", expand=True)

    CARS_CONFIG = [
        ("red", 50),
        ("blue", 150),
        ("green", 250)
    ]
    
    cars_data = []
    
    def draw_cars_and_goal():
        canvas_width = canvas.winfo_width()
        if canvas_width < 100:
            root.after(100, draw_cars_and_goal)
            return

        GOAL_X = canvas_width - 50
        canvas.create_line(GOAL_X, 0, GOAL_X, canvas.winfo_height(), width=3, fill="black", dash=(5, 5))
        canvas.create_text(GOAL_X - 20, 15, text="META", fill="black")

        for color, y_pos in CARS_CONFIG:
            car_id = canvas.create_rectangle(10, y_pos, 60, y_pos + 50, fill=color, outline="black")
            cars_data.append((color, car_id))
            
    canvas.bind("<Configure>", lambda event: draw_cars_and_goal() if not cars_data else None)
    
    button_frame = tk.Frame(tab_frame)
    button_frame.pack(pady=10)
    
    ttk.Button(
        button_frame, 
        text="START RACE (Hilos)", 
        command=lambda: start_race(canvas, cars_data, label_result)
    ).pack(side="left", padx=10)
    
    ttk.Button(
        button_frame, 
        text="STOP/RESET", 
        command=lambda: stop_race() or label_result.config(text="Carrera detenida y reiniciada.", fg="gray")
    ).pack(side="left", padx=10)


def setup_t2_threads(tab_frame):
    """Configura el Notebook interno para la pestaña T2. Threads."""
    for widget in tab_frame.winfo_children():
        widget.destroy()
        
    internal_notebook = ttk.Notebook(tab_frame)
    internal_notebook.pack(fill="both", expand=True, padx=5, pady=5)

    clock_tab = ttk.Frame(internal_notebook)
    internal_notebook.add(clock_tab, text="Reloj / Alarma")
    
    race_tab = ttk.Frame(internal_notebook)
    internal_notebook.add(race_tab, text="Coches") 

    # --- PESTAÑA DE SCRAPING ---
    scraping_tab = ttk.Frame(internal_notebook)
    internal_notebook.add(scraping_tab, text="Scraping") 
    # ---------------------------------

    def handle_internal_tab_change_t2(event):
        selected_index = internal_notebook.index(internal_notebook.select())
        current_tab_title = internal_notebook.tab(selected_index, "text")
        
        # Detener hilos al cambiar de pestaña
        if current_tab_title != "Reloj / Alarma":
            stop_clock_thread()
        else:
            setup_clock_tab(clock_tab)
            
        if current_tab_title != "Coches":
            stop_race()
        else:
            setup_race_game(race_tab)
            
        if current_tab_title == "Scraping":
            setup_scraping_tab(scraping_tab)

    internal_notebook.bind("<<NotebookTabChanged>>", handle_internal_tab_change_t2)
    
    # Configuración inicial de todas las pestañas
    setup_clock_tab(clock_tab)
    setup_race_game(race_tab) 
    setup_scraping_tab(scraping_tab) 


# --- FUNCIÓN DE NAVEGACIÓN PRINCIPAL ---

def navigate_to_tab(event):
    """Maneja el cambio de pestaña principal (T1, T2, T3...)."""
    selected_index = notebook.index(notebook.select())
    tab_name = notebook.tab(selected_index, "text")

    # 1. Limpiar el frame izquierdo (Botones)
    for widget in frame_izquierdo.winfo_children():
        widget.destroy()

    # 2. Detener todos los hilos activos de otras pestañas
    stop_monitors() 
    stop_clock_thread()
    stop_race() 
    stop_music_clean() # Detener la música al cambiar de pestaña

    # 3. Configurar la pestaña principal y los botones laterales
    if selected_index == 0: # T1. Procesos
        setup_t1_processes(tabs[0], root) 
        setup_buttons_t1(frame_izquierdo) 
    elif selected_index == 1: # T2. Threads
        setup_t2_threads(tabs[1])
        setup_buttons_t2(frame_izquierdo) 
        initialize_music() # Cargar la música solo cuando entramos en T2
    elif selected_index == 2: # T3. Sockets (Pendiente de implementación)
        setup_buttons_default(frame_izquierdo, tab_name)
    else: 
        setup_buttons_default(frame_izquierdo, tab_name)

    status_manager.set_status("status_1", f"Navegando a: {tab_name}", "yellow")
    root.after(2000, lambda: status_manager.set_status("status_1", "Navegación completa", "green"))


# --- FUNCIONES DE SETUP PARA BOTONES CONTEXTUALES ---

def create_section(parent, title):
    """Crea un label de título de sección estilizado."""
    tk.Label(parent, text=title, bg="#AAAAAA", fg="black", font=("Arial", 10, "bold"), anchor="w").pack(side="top", fill="x", pady=(10, 2), padx=5)


def setup_buttons_t1(frame):
    """Configura los botones para la pestaña T1. Procesos."""
    URL_GOOGLE = "https://www.google.com"
    URL_YOUTUBE = "https://www.youtube.com"
    URL_FILMIN = "https://www.filmin.es/"
    
    create_section(frame, "Aplicaciones")
    
    ttk.Button(frame, text="Google", command=lambda: open_external_url(URL_GOOGLE)).pack(pady=3, padx=10, fill="x")
    ttk.Button(frame, text="YouTube", command=lambda: open_external_url(URL_YOUTUBE)).pack(pady=3, padx=10, fill="x")
    ttk.Button(frame, text="Filmin", command=lambda: open_external_url(URL_FILMIN)).pack(pady=3, padx=10, fill="x")

    create_section(frame, "Procesos batch")
    ttk.Button(
        frame, 
        text="Copias de seguridad",
        command=run_bash_backup 
    ).pack(pady=3, padx=10, fill="x")

def setup_buttons_t2(frame):
    """Configura los botones para la pestaña T2. Threads (Incluyendo el de Música)."""
    create_section(frame, "Hilos de Control")
    
    # Crear un botón con estilo y pasarlo a la función de toggle
    global music_state
    
    # Definir el estilo inicial
    initial_text = "MÚSICA (Play)" if not music_state else "STOP MÚSICA (En Bucle)"
    initial_style = "Green.TButton" if not music_state else "Red.TButton"
    
    music_button = ttk.Button(
        frame, 
        text=initial_text,
        style=initial_style
    )
    # Pasar el botón a la función toggle_music para que pueda cambiar su propio texto y estilo
    music_button.config(command=lambda: toggle_music(music_button))
    music_button.pack(pady=10, padx=10, fill="x")
    
    create_section(frame, "Configuración General")
    tk.Label(frame, text="Controles de T2...", fg="gray", font=("Arial", 10)).pack(pady=10)


def setup_buttons_default(frame, tab_title):
    """Configura botones genéricos para pestañas no implementadas."""
    create_section(frame, f"Acciones para {tab_title}")
    tk.Label(frame, text="Funcionalidad pendiente...", fg="gray", font=("Arial", 10)).pack(pady=10)
    ttk.Button(frame, text="Ejecutar Acción Genérica").pack(pady=3, padx=10, fill="x")


# --- CONFIGURACIÓN PRINCIPAL ---
root = tk.Tk()
root.title("Ventana Responsive - Proyecto Nico")
root.geometry("1200x800")

root.columnconfigure(0, weight=0) 
root.columnconfigure(1, weight=1) 
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=0)

# Menú 
menu_bar = Menu(root)
file_menu = Menu(menu_bar, tearoff=0)
file_menu.add_command(label="Nuevo")
file_menu.add_command(label="Abrir")
file_menu.add_separator()
file_menu.add_command(label="Salir", command=root.quit)
edit_menu = Menu(menu_bar, tearoff=0)
edit_menu.add_command(label="Copiar")
edit_menu.add_command(label="Pegar")
help_menu = Menu(menu_bar, tearoff=0)
help_menu.add_command(label="Acerca de")
menu_config = Menu(menu_bar, tearoff=0)
menu_config.add_command(label="Preferencias")
menu_bar.add_cascade(label="Archivo", menu=file_menu)
menu_bar.add_cascade(label="Editar", menu=edit_menu)
menu_bar.add_cascade(label="Configuración", menu=menu_config)
menu_bar.add_cascade(label="Ayuda", menu=help_menu)
root.config(menu=menu_bar)

# Estilos personalizados 
style = ttk.Style()
style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold"))
style.configure("Green.TButton", background="#4CAF50", foreground="black")
style.map("Green.TButton",
    background=[('active', '#66BB6A')],
    foreground=[('active', 'black')]
)
style.configure("Red.TButton", background="#F44336", foreground="black")
style.map("Red.TButton",
    background=[('active', '#E57373')],
    foreground=[('active', 'black')]
)

# Estructura de Frames 
frame_izquierdo = tk.Frame(root, bg="#DDDDDD", width=250)
frame_central = tk.Frame(root, bg="white")

frame_izquierdo.grid(row=0, column=0, sticky="ns")
frame_central.grid(row=0, column=1, sticky="nsew") 

frame_izquierdo.grid_propagate(False)

frame_central.rowconfigure(0, weight=1)
frame_central.rowconfigure(1, weight=0)
frame_central.columnconfigure(0, weight=1)

frame_superior = tk.Frame(frame_central, bg="#F0F0F0")
frame_inferior = tk.Frame(frame_central, bg="#D0FFD0", height=100)

frame_superior.grid(row=0, column=0, sticky="nsew")
frame_inferior.grid(row=1, column=0, sticky="ew")

frame_inferior.grid_propagate(False)
tk.Label(frame_inferior, text="Panel para notas informativas y mensajes sobre la ejecución de los hilos.", bg="#D0FFD0", fg="#333333", justify="left", padx=10, pady=5).pack(fill="both", expand=True)

# Barra de estado 
barra_estado = tk.Frame(root, bg="lightgray")
barra_estado.grid(row=1, column=0, columnspan=2, sticky="ew") 
barra_estado.grid_columnconfigure(0, weight=1)

status_manager = StatusBarManager(barra_estado, root)

label_fecha_hora = tk.Label(barra_estado, text="Hilo fecha-hora", font=("Helvetica", 10, "bold"), bd=1, fg="blue", relief="raised", anchor="center", width=25, padx=5)
label_fecha_hora.pack(side="right", fill="x", padx=5)

# INICIALIZACIÓN DEL HILO DE LA BARRA DE ESTADO
stop_event_status_bar = threading.Event()
status_bar_time_thread = threading.Thread(target=update_time, args=(label_fecha_hora, stop_event_status_bar), daemon=True)
status_bar_time_thread.stop_event = stop_event_status_bar
status_bar_time_thread.start()

# Notebook principal 
notebook = ttk.Notebook(frame_superior, style="CustomNotebook.TNotebook")
notebook.pack(fill="both", expand=True)

TAB_TITLES = ["T1. Procesos", "T2. Threads", "T3. Sockets", "T4. Servicios", "T5. Seguridad"]
tabs = []

for i, title in enumerate(TAB_TITLES):
    tab = ttk.Frame(notebook)
    tabs.append(tab)
    notebook.add(tab, text=title, padding=4)
    
    if i == 0:
        pass
    elif i == 1:
        pass 
    else:
        ttk.Label(tab, text=f"Contenido de {title}", font=("Arial", 14)).pack(pady=20)

notebook.bind("<<NotebookTabChanged>>", navigate_to_tab)

# Configuración inicial (inicia en T1)
root.after(100, lambda: setup_t1_processes(tabs[0], root)) 
root.after(100, lambda: setup_buttons_t1(frame_izquierdo))


# FUNCIÓN DE CIERRE DE VENTANA
def on_closing():
    stop_monitors()
    stop_clock_thread() 
    stop_race() 
    stop_music_clean() 
    stop_status_bar_time_thread() 
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()