From 1d4908840f506d0c4f2d6a4cce4b0a59984279dc Mon Sep 17 00:00:00 2001 From: Santi Date: Wed, 29 Jan 2025 16:35:16 +0100 Subject: [PATCH] Optimizacion y cambios masivos en la app IMPORTANTE falta implementar el READ.ME y la carpeta de msuica para el reproductor y cambiar el nombre de las solapas --- .idea/.gitignore | 8 + .idea/ProyectoFinalProcesosServicios.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + app/__init__.py | 0 app/main.py | 194 +++++++++++++++++ hilos/ApplicationLauncher.py | 95 +++++++++ hilos/ChatWidget.py | 74 +++++++ hilos/LanguageChart.py | 57 +++++ hilos/MusicPlayer.py | 77 +++++++ hilos/SystemMonitor.py | 69 +++++++ hilos/WeatherWidget.py | 129 ++++++++++++ hilos/__init__.py | 0 .../ApplicationLauncher.cpython-313.pyc | Bin 0 -> 5211 bytes hilos/__pycache__/ChatWidget.cpython-313.pyc | Bin 0 -> 5007 bytes .../__pycache__/LanguageChart.cpython-313.pyc | Bin 0 -> 3369 bytes hilos/__pycache__/MusicPlayer.cpython-313.pyc | Bin 0 -> 5064 bytes .../__pycache__/SystemMonitor.cpython-313.pyc | Bin 0 -> 6293 bytes .../__pycache__/WeatherWidget.cpython-313.pyc | Bin 0 -> 6354 bytes hilos/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 176 bytes solapas/EconomyBitcoinChart.py | 69 +++++++ solapas/MusicDownloader.py | 68 ++++++ solapas/SQLQueryExecutor.py | 113 ++++++++++ solapas/TicTacToe.py | 123 +++++++++++ solapas/WebScraperToDB.py | 195 ++++++++++++++++++ solapas/__init__.py | 0 .../EconomyBitcoinChart.cpython-313.pyc | Bin 0 -> 4486 bytes .../MusicDownloader.cpython-313.pyc | Bin 0 -> 4439 bytes .../SQLQueryExecutor.cpython-313.pyc | Bin 0 -> 8374 bytes solapas/__pycache__/TicTacToe.cpython-313.pyc | Bin 0 -> 6890 bytes .../WebScraperToDB.cpython-313.pyc | Bin 0 -> 12914 bytes solapas/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 178 bytes 34 files changed, 1307 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/ProyectoFinalProcesosServicios.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 app/__init__.py create mode 100644 app/main.py create mode 100644 hilos/ApplicationLauncher.py create mode 100644 hilos/ChatWidget.py create mode 100644 hilos/LanguageChart.py create mode 100644 hilos/MusicPlayer.py create mode 100644 hilos/SystemMonitor.py create mode 100644 hilos/WeatherWidget.py create mode 100644 hilos/__init__.py create mode 100644 hilos/__pycache__/ApplicationLauncher.cpython-313.pyc create mode 100644 hilos/__pycache__/ChatWidget.cpython-313.pyc create mode 100644 hilos/__pycache__/LanguageChart.cpython-313.pyc create mode 100644 hilos/__pycache__/MusicPlayer.cpython-313.pyc create mode 100644 hilos/__pycache__/SystemMonitor.cpython-313.pyc create mode 100644 hilos/__pycache__/WeatherWidget.cpython-313.pyc create mode 100644 hilos/__pycache__/__init__.cpython-313.pyc create mode 100644 solapas/EconomyBitcoinChart.py create mode 100644 solapas/MusicDownloader.py create mode 100644 solapas/SQLQueryExecutor.py create mode 100644 solapas/TicTacToe.py create mode 100644 solapas/WebScraperToDB.py create mode 100644 solapas/__init__.py create mode 100644 solapas/__pycache__/EconomyBitcoinChart.cpython-313.pyc create mode 100644 solapas/__pycache__/MusicDownloader.cpython-313.pyc create mode 100644 solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc create mode 100644 solapas/__pycache__/TicTacToe.cpython-313.pyc create mode 100644 solapas/__pycache__/WebScraperToDB.cpython-313.pyc create mode 100644 solapas/__pycache__/__init__.cpython-313.pyc diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ProyectoFinalProcesosServicios.iml b/.idea/ProyectoFinalProcesosServicios.iml new file mode 100644 index 0000000..eccdbc3 --- /dev/null +++ b/.idea/ProyectoFinalProcesosServicios.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fba7da9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7bd9d81 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..60f65c5 --- /dev/null +++ b/app/main.py @@ -0,0 +1,194 @@ +import tkinter as tk +import threading +import time +import datetime +from tkinter import Menu # Importar el widget Menu +from tkinter import ttk # Importar el widget ttk + +from hilos.ChatWidget import ChatWidget +from hilos.MusicPlayer import MusicPlayer +from hilos.WeatherWidget import WeatherWidget +from hilos.SystemMonitor import SystemMonitor +from hilos.ApplicationLauncher import ApplicationLauncher +from hilos.LanguageChart import LanguageChart +from solapas.MusicDownloader import MusicDownloader +from solapas.EconomyBitcoinChart import EconomyBitcoinChart +from solapas.SQLQueryExecutor import SQLQueryExecutor +from solapas.TicTacToe import TicTacToe +from solapas.WebScraperToDB import WebScraperToDB + +# Clave de API de OpenWeatherMap +API_KEY = "1fa8fd05b650773bbc3f2130657e808a" + +def update_time(status_bar): + """Función que actualiza la hora y el día de la semana en un label""" + while True: + # Obtener la fecha y hora actual + now = datetime.datetime.now() + day_of_week = now.strftime("%A") # Día de la semana + time_str = now.strftime("%H:%M:%S") # Hora en formato HH:MM:SS + date_str = now.strftime("%Y-%m-%d") # Fecha en formato YYYY-MM-DD + label_text = f"{day_of_week}, {date_str} - {time_str}" + + # Actualizar el label (debemos usar `after` para asegurarnos que se actualice en el hilo principal de Tkinter) + label_fecha_hora.after(1000, status_bar.config, {"text": label_text}) + + # Espera 1 segundo antes de actualizar de nuevo + time.sleep(1) + +# Crear la ventana principal +root = tk.Tk() +root.title("Ventana Responsive") +root.geometry("1000x700") # Tamaño inicial + +# Configurar la ventana principal para que sea responsive +root.columnconfigure(0, weight=0) # Columna izquierda, tamaño fijo +root.columnconfigure(1, weight=1) # Columna central, tamaño variable +root.columnconfigure(2, weight=0) # Columna derecha, tamaño fijo +root.rowconfigure(0, weight=1) # Fila principal, tamaño variable +root.rowconfigure(1, weight=0) # Barra de estado, tamaño fijo + +# Crear el menú superior +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_bar.add_cascade(label="Archivo", menu=file_menu) +menu_bar.add_cascade(label="Editar", menu=edit_menu) +menu_bar.add_cascade(label="Ayuda", menu=help_menu) + +root.config(menu=menu_bar) + +# Crear los frames laterales y el central +frame_izquierdo = tk.Frame(root, bg="lightblue", width=150) +frame_central = tk.Frame(root, bg="white") +frame_derecho = tk.Frame(root, bg="lightgreen", width=150) + +# Colocar los frames laterales y el central +frame_izquierdo.grid(row=0, column=0, sticky="ns") +frame_central.grid(row=0, column=1, sticky="nsew") +frame_derecho.grid(row=0, column=2, sticky="ns") + +# Configurar los tamaños fijos de los frames laterales +frame_izquierdo.grid_propagate(False) +frame_derecho.grid_propagate(False) + +# Integrar el widget del clima en el panel izquierdo +weather_widget = WeatherWidget(frame_izquierdo, API_KEY) + +# Añadir el lanzador de aplicaciones al panel izquierdo +app_launcher = ApplicationLauncher(frame_izquierdo) + +# Añadir gráfico de lenguajes al panel izquierdo +language_chart = LanguageChart(frame_izquierdo) + +# Crear el widget de Chat en el panel derecho con más espacio +chat_widget = ChatWidget(frame_derecho) + +# Agregar el reproductor de música al panel derecho, en la parte inferior +music_player = MusicPlayer(frame_derecho) + +# Dividir el frame central en dos partes (superior variable e inferior fija) +frame_central.rowconfigure(0, weight=1) # Parte superior, tamaño variable +frame_central.rowconfigure(1, weight=0) # Parte inferior, tamaño fijo +frame_central.columnconfigure(0, weight=1) # Ocupa toda la anchura + +# Crear subframes dentro del frame central +frame_superior = tk.Frame(frame_central, bg="lightyellow") +frame_inferior = tk.Frame(frame_central, bg="lightgray", height=100) + +# Colocar los subframes dentro del frame central +frame_superior.grid(row=0, column=0, sticky="nsew") +frame_inferior.grid(row=1, column=0, sticky="ew") + +# Fijar el tamaño de la parte inferior +frame_inferior.grid_propagate(False) + +# Crear un evento de parada +stop_event = threading.Event() + +# Definir el manejador para el cierre de la ventana +def on_closing(): + """Cerrar correctamente la aplicación.""" + stop_event.set() # Detener los hilos + root.destroy() # Destruir la ventana principal + +# Configurar el manejador de cierre +root.protocol("WM_DELETE_WINDOW", on_closing) + +# Crear la barra de estado +barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w") +barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") + +# Inicializar el monitor del sistema +system_monitor = SystemMonitor(barra_estado, stop_event) + +# Notebook para las pestañas +style = ttk.Style() +style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold")) +notebook = ttk.Notebook(frame_superior, style="CustomNotebook.TNotebook") +notebook.pack(fill="both", expand=True) + +# Crear la Solapa 1 y añadir el downloader +tab1 = ttk.Frame(notebook) +notebook.add(tab1, text="Solapa 1", padding=4) + +# Añadir el downloader a la Solapa 1 +music_downloader = MusicDownloader(tab1) + +# Crear la Solapa 2 y añadir los gráficos +tab2 = ttk.Frame(notebook) +notebook.add(tab2, text="Solapa 2", padding=4) + +# Añadir los gráficos de economía mundial y Bitcoin a la Solapa 2 +economy_bitcoin_chart = EconomyBitcoinChart(tab2) + +# Crear la Solapa 3 y añadir el Tic Tac Toe +tab3 = ttk.Frame(notebook) +notebook.add(tab3, text="Solapa 3", padding=4) + +# Añadir el juego de Tic Tac Toe a la Solapa 3 +tic_tac_toe = TicTacToe(tab3) + +# Crear la Solapa 4 y añadir el SQL Query Executor +tab4 = ttk.Frame(notebook) +notebook.add(tab4, text="Solapa 4", padding=4) + +# Añadir el ejecutor de consultas SQL a la Solapa 4 +sql_query_executor = SQLQueryExecutor(tab4) + +# Crear la Solapa 5 y añadir el Web Scraper +tab5 = ttk.Frame(notebook) +notebook.add(tab5, text="Solapa 5", padding=4) + +# Añadir el widget de Web Scraper a la Solapa 5 +web_scraper = WebScraperToDB(tab5) + +# Barra de estado +# Dividir la barra de estado en 4 labels + +# Usar pack para alinear los labels horizontalmente +label_fecha_hora = tk.Label(barra_estado, text="Hilo fecha-hora", font=("Helvetica", 14), bd=1, fg="blue", relief="sunken", anchor="w", width=20, padx=10) + + +label_fecha_hora.pack(side="right", fill="x", expand=True) +# barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") + + +update_thread = threading.Thread(target=update_time, args=(label_fecha_hora,)) +update_thread.daemon = True # Hacemos el hilo un demonio para que termine con la app +update_thread.start() + +# Ejecución de la aplicación +root.mainloop() \ No newline at end of file diff --git a/hilos/ApplicationLauncher.py b/hilos/ApplicationLauncher.py new file mode 100644 index 0000000..d831832 --- /dev/null +++ b/hilos/ApplicationLauncher.py @@ -0,0 +1,95 @@ +import tkinter as tk +import threading +import subprocess +import os + + +class ApplicationLauncher: + def __init__(self, parent): + """ + Inicializa los botones para lanzar aplicaciones con detección automática de rutas. + + Args: + parent (tk.Frame): Frame donde se colocarán los botones. + """ + self.parent = parent + + # Detectar rutas automáticamente + self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe", + "C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe"]) + self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe", + "C:\\Program Files\\Eclipse Foundation\\eclipse.exe"]) + self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe", + "C:\\Program Files\\JetBrains\\PyCharm Community Edition 2023.1.4\\bin\\pycharm64.exe"]) + + # Título para el grupo de botones + title = tk.Label(self.parent, text="Aplicaciones", font=("Helvetica", 14, "bold"), bg="lightblue") + title.pack(pady=10) + + # Botón para abrir Visual Studio Code + self.vscode_button = tk.Button(self.parent, text="Visual Code", command=self.launch_vscode, bg="lightgreen", + font=("Helvetica", 10)) + self.vscode_button.pack(fill="x", pady=2) + + # Botón para abrir Eclipse + self.eclipse_button = tk.Button(self.parent, text="Eclipse", command=self.launch_eclipse, bg="lightgreen", + font=("Helvetica", 10)) + self.eclipse_button.pack(fill="x", pady=2) + + # Botón para abrir PyCharm + self.pycharm_button = tk.Button(self.parent, text="PyCharm", command=self.launch_pycharm, bg="lightgreen", + font=("Helvetica", 10)) + self.pycharm_button.pack(fill="x", pady=2) + + def detect_path(self, paths): + """ + Detecta automáticamente la primera ruta existente de una lista de posibles rutas. + + Args: + paths (list): Lista de rutas posibles para un ejecutable. + + Returns: + str: La primera ruta válida encontrada, o None si no se encuentra ninguna. + """ + for path in paths: + path = os.path.expandvars(path) # Expande variables como %USERNAME% + if os.path.exists(path): + return path + return None + + def launch_vscode(self): + """Lanza Visual Studio Code si se encuentra la ruta.""" + self.launch_application(self.vscode_path, "Visual Studio Code") + + def launch_eclipse(self): + """Lanza Eclipse si se encuentra la ruta.""" + self.launch_application(self.eclipse_path, "Eclipse") + + def launch_pycharm(self): + """Lanza PyCharm si se encuentra la ruta.""" + self.launch_application(self.pycharm_path, "PyCharm") + + def launch_application(self, path, name): + """ + Lanza una aplicación si la ruta es válida. + + Args: + path (str): Ruta al ejecutable. + name (str): Nombre de la aplicación (para mensajes de error). + """ + if path: + threading.Thread(target=self.run_command, args=([path],), daemon=True).start() + else: + print(f"No se encontró {name}. Por favor, instálalo o configura la ruta.") + + def run_command(self, command): + """ + Ejecuta un comando del sistema operativo para abrir una aplicación. + + Args: + command (list): Comando a ejecutar (lista de argumentos). + """ + try: + subprocess.run(command, check=True) + except Exception as e: + print(f"Error al intentar abrir la aplicación: {e}") diff --git a/hilos/ChatWidget.py b/hilos/ChatWidget.py new file mode 100644 index 0000000..308fa37 --- /dev/null +++ b/hilos/ChatWidget.py @@ -0,0 +1,74 @@ +import tkinter as tk +from tkinter import scrolledtext +import socket +import threading + + +class ChatWidget: + def __init__(self, parent): + self.parent = parent + self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=300) # Ajustar tamaño del frame + self.frame.pack(fill="x", expand=False, padx=10, pady=10) + + # Label superior + self.label = tk.Label(self.frame, text="Chat", font=("Arial", 14, "bold"), fg="red", bg="lightgreen") + self.label.pack(pady=5) + + # Caja de texto para los mensajes + self.chat_display = scrolledtext.ScrolledText( + self.frame, wrap=tk.WORD, state="disabled", width=40, height=10 # Reducir dimensiones + ) + self.chat_display.pack(pady=5) + + # Campo de entrada para escribir mensajes + self.message_entry = tk.Entry(self.frame, width=35) # Reducir ancho + self.message_entry.pack(pady=5) + self.message_entry.bind("", self.send_message) + + # Botón para enviar mensajes + self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message, width=10) # Reducir tamaño + self.send_button.pack(pady=5) + + # Configuración del cliente socket + self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server_address = ("127.0.0.1", 3333) # Cambiar a la IP del servidor si es necesario + + try: + self.client_socket.connect(self.server_address) + threading.Thread(target=self.receive_messages, daemon=True).start() + except Exception as e: + self.display_message(f"[ERROR] No se pudo conectar al servidor: {e}") + + def send_message(self, event=None): + message = self.message_entry.get() + if message: + try: + self.client_socket.send(message.encode("utf-8")) + self.message_entry.delete(0, tk.END) + except Exception as e: + self.display_message(f"[ERROR] No se pudo enviar el mensaje: {e}") + + def receive_messages(self): + """Recibe mensajes del servidor y los muestra en el chat.""" + while True: + try: + message = self.client_socket.recv(1024).decode("utf-8") + if message: + self.display_message(message) # Mostrar mensaje en la caja de chat + else: + break + except: + self.display_message("[DESCONECTADO] Conexión perdida con el servidor.") + break + + def display_message(self, message): + self.chat_display.config(state="normal") + self.chat_display.insert(tk.END, message + "\n") + self.chat_display.config(state="disabled") + self.chat_display.see(tk.END) + + def close_connection(self): + try: + self.client_socket.close() + except: + pass \ No newline at end of file diff --git a/hilos/LanguageChart.py b/hilos/LanguageChart.py new file mode 100644 index 0000000..91fb6d0 --- /dev/null +++ b/hilos/LanguageChart.py @@ -0,0 +1,57 @@ +import tkinter as tk +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import threading +import time + +class LanguageChart: + def __init__(self, parent): + """ + Inicializa el gráfico de los lenguajes de programación más usados. + + Args: + parent (tk.Frame): Frame donde se colocará el gráfico. + """ + self.parent = parent + + # Datos iniciales (puedes actualizar esto dinámicamente) + self.languages = ["Python", "JavaScript", "Java", "C++", "C#"] + self.usage = [30, 25, 20, 15, 10] # Porcentajes de uso + + # Crear figura para el gráfico + self.figure = Figure(figsize=(4, 3), dpi=100) + self.ax = self.figure.add_subplot(111) + self.ax.bar(self.languages, self.usage, color="skyblue") + self.ax.set_title("Lenguajes más usados") + self.ax.set_ylabel("Porcentaje de uso") + + # Embebiendo el gráfico en Tkinter + self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent) + self.canvas.get_tk_widget().pack(fill="both", expand=True) + + # Iniciar hilo para actualizar el gráfico + threading.Thread(target=self.update_chart, daemon=True).start() + + def fetch_data(self): + """ + Simula la obtención de datos actualizados de lenguajes de programación. + + Returns: + list: Lista de nuevos porcentajes de uso. + """ + # Simulación: aquí puedes conectar a una API real + self.usage = [value + 1 if value < 50 else value - 10 for value in self.usage] + time.sleep(5) # Simular retraso de actualización + + def update_chart(self): + """ + Actualiza el gráfico periódicamente en un hilo. + """ + while True: + self.fetch_data() + self.ax.clear() + self.ax.bar(self.languages, self.usage, color="skyblue") + self.ax.set_title("Lenguajes más usados") + self.ax.set_ylabel("Porcentaje de uso") + self.canvas.draw() + time.sleep(5) # Actualizar cada 5 segundos \ No newline at end of file diff --git a/hilos/MusicPlayer.py b/hilos/MusicPlayer.py new file mode 100644 index 0000000..5402a8a --- /dev/null +++ b/hilos/MusicPlayer.py @@ -0,0 +1,77 @@ +import tkinter as tk +from tkinter import filedialog +import threading +import pygame # Necesitas instalar pygame: pip install pygame + + +class MusicPlayer: + def __init__(self, parent): + self.parent = parent + self.is_playing = False + + # Inicializar el reproductor de música + pygame.mixer.init() + + # Crear marco para el reproductor + self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=100) + self.frame.pack(side="bottom", padx=10, pady=10, fill="both", expand=False) + + # Etiqueta de título + self.title_label = tk.Label( + self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen" + ) + self.title_label.pack(pady=5) + + # Botón para seleccionar archivo + self.select_button = tk.Button( + self.frame, text="Seleccionar Archivo", command=self.select_file, width=20 + ) + self.select_button.pack(pady=5) + + # Crear un marco para los botones de control + self.controls_frame = tk.Frame(self.frame, bg="lightgreen") + self.controls_frame.pack(pady=10) + + # Botones de control (centrados) + self.play_button = tk.Button( + self.controls_frame, text="▶ Reproducir", command=self.play_music, width=12 + ) + self.play_button.grid(row=0, column=0, padx=5) + + self.stop_button = tk.Button( + self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12 + ) + self.stop_button.grid(row=0, column=1, padx=5) + + def select_file(self): + """Abrir el selector de archivos para elegir un archivo de música.""" + self.music_file = filedialog.askopenfilename( + filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")] + ) + if self.music_file: + self.title_label.config(text=f"Archivo: {self.music_file.split('/')[-1]}") + + def play_music(self): + """Iniciar la reproducción de música.""" + if hasattr(self, "music_file"): + self.is_playing = True + self.play_button.config(state="disabled") + self.stop_button.config(state="normal") + + threading.Thread(target=self._play_music_thread, daemon=True).start() + + def _play_music_thread(self): + """Hilo que reproduce la música.""" + pygame.mixer.music.load(self.music_file) + pygame.mixer.music.play() + while pygame.mixer.music.get_busy(): + if not self.is_playing: + pygame.mixer.music.stop() + break + + def stop_music(self): + """Detener la reproducción de música.""" + self.is_playing = False + self.play_button.config(state="normal") + self.stop_button.config(state="disabled") + pygame.mixer.music.stop() diff --git a/hilos/SystemMonitor.py b/hilos/SystemMonitor.py new file mode 100644 index 0000000..3ba1e35 --- /dev/null +++ b/hilos/SystemMonitor.py @@ -0,0 +1,69 @@ +import psutil +import threading +import tkinter as tk + +class SystemMonitor: + def __init__(self, parent, stop_event): + self.parent = parent + self.stop_event = stop_event + + # Crear labels para cada métrica + self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen", font=("Helvetica", 12), relief="groove") + self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral", font=("Helvetica", 12), relief="groove") + self.battery_label = tk.Label(parent, text="Battery: N/A", bg="lightblue", font=("Helvetica", 12), relief="groove") + self.network_label = tk.Label(parent, text="Net: N/A", bg="lightpink", font=("Helvetica", 12), relief="groove") + + # Posicionar los labels + self.cpu_label.pack(side="left", fill="both", expand=True) + self.ram_label.pack(side="left", fill="both", expand=True) + self.battery_label.pack(side="left", fill="both", expand=True) + self.network_label.pack(side="left", fill="both", expand=True) + + # Iniciar hilos + threading.Thread(target=self.update_cpu, daemon=True).start() + threading.Thread(target=self.update_ram, daemon=True).start() + threading.Thread(target=self.update_battery, daemon=True).start() + threading.Thread(target=self.update_network, daemon=True).start() + + def update_cpu(self): + """Actualizar el uso de CPU.""" + while not self.stop_event.is_set(): + cpu_usage = psutil.cpu_percent() + self.cpu_label.config(text=f"CPU: {cpu_usage}%") + self.cpu_label.after(1000, lambda: None) # Evitar bloqueo + self.stop_event.wait(1) + + def update_ram(self): + """Actualizar el uso de RAM.""" + while not self.stop_event.is_set(): + ram_usage = psutil.virtual_memory().percent + self.ram_label.config(text=f"RAM: {ram_usage}%") + self.ram_label.after(1000, lambda: None) # Evitar bloqueo + self.stop_event.wait(1) + + def update_battery(self): + """Actualizar el estado de la batería.""" + while not self.stop_event.is_set(): + battery = psutil.sensors_battery() + if battery: + percent = battery.percent + time_left = battery.secsleft // 3600 if battery.secsleft > 0 else "N/A" + self.battery_label.config(text=f"Battery: {percent}%, ({time_left}h left)") + else: + self.battery_label.config(text="Battery: N/A") + self.battery_label.after(1000, lambda: None) # Evitar bloqueo + self.stop_event.wait(5) + + def update_network(self): + """Actualizar el uso de red.""" + old_sent = psutil.net_io_counters().bytes_sent + old_recv = psutil.net_io_counters().bytes_recv + while not self.stop_event.is_set(): + new_sent = psutil.net_io_counters().bytes_sent + new_recv = psutil.net_io_counters().bytes_recv + sent_mb = (new_sent - old_sent) / (1024 * 1024) + recv_mb = (new_recv - old_recv) / (1024 * 1024) + self.network_label.config(text=f"Net: {sent_mb:.2f} MB sent, {recv_mb:.2f} MB recv") + old_sent, old_recv = new_sent, new_recv + self.network_label.after(1000, lambda: None) # Evitar bloqueo + self.stop_event.wait(1) diff --git a/hilos/WeatherWidget.py b/hilos/WeatherWidget.py new file mode 100644 index 0000000..4a3f306 --- /dev/null +++ b/hilos/WeatherWidget.py @@ -0,0 +1,129 @@ +import tkinter as tk +import threading +import requests +import time + + +class WeatherWidget: + def __init__(self, parent, api_key): + """ + Inicializa el widget del clima con detalles adicionales. + + Args: + parent (tk.Frame): Frame en el que se colocará el widget. + api_key (str): Clave de la API de OpenWeatherMap. + """ + self.parent = parent + self.api_key = api_key + + # Crear un Frame para contener los datos + self.frame = tk.Frame(self.parent, bg="white", bd=2, relief="groove") + self.frame.pack(padx=10, pady=10, fill="x", anchor="n") + + # Encabezado del clima + self.header_label = tk.Label(self.frame, text="Weather in ...", font=("Helvetica", 14, "bold"), bg="white") + self.header_label.pack(pady=5) + + # Temperatura principal + self.temp_label = tk.Label(self.frame, text="--°C", font=("Helvetica", 28, "bold"), bg="white") + self.temp_label.pack() + + # Detalles adicionales + self.details_label = tk.Label(self.frame, text="", font=("Helvetica", 12), bg="white", justify="left") + self.details_label.pack(pady=5) + + # Iniciar el hilo para actualizar el clima + self.start_weather_updates() + + def get_location(self): + """ + Obtiene la ubicación actual (latitud y longitud) usando ip-api. + """ + try: + response = requests.get("http://ip-api.com/json/") + response.raise_for_status() + data = response.json() + return data["lat"], data["lon"], data["city"] + except Exception as e: + return None, None, f"Error al obtener ubicación: {e}" + + def get_weather(self, lat, lon): + """ + Obtiene el clima actual usando OpenWeatherMap. + + Args: + lat (float): Latitud de la ubicación. + lon (float): Longitud de la ubicación. + """ + try: + weather_url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={self.api_key}&units=metric" + response = requests.get(weather_url) + response.raise_for_status() + data = response.json() + + # Información principal + city = data["name"] + temp = data["main"]["temp"] + real_feel = data["main"]["feels_like"] + wind_speed = data["wind"]["speed"] + wind_gusts = data["wind"].get("gust", "N/A") + weather = data["weather"][0]["description"].capitalize() + + # Obtener calidad del aire (Air Quality) + air_quality = self.get_air_quality(lat, lon) + + # Formatear detalles adicionales + details = ( + f"RealFeel: {real_feel}°\n" + f"Wind: {wind_speed} km/h\n" + f"Wind Gusts: {wind_gusts} km/h\n" + f"Air Quality: {air_quality}" + ) + + return city, temp, details + except Exception as e: + return None, None, f"Error al obtener el clima: {e}" + + def get_air_quality(self, lat, lon): + """ + Obtiene la calidad del aire usando OpenWeatherMap. + + Args: + lat (float): Latitud. + lon (float): Longitud. + """ + try: + aqi_url = f"http://api.openweathermap.org/data/2.5/air_pollution?lat={lat}&lon={lon}&appid={self.api_key}" + response = requests.get(aqi_url) + response.raise_for_status() + data = response.json() + aqi = data["list"][0]["main"]["aqi"] + + # Mapear AQI a descripciones + aqi_mapping = {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"} + return aqi_mapping.get(aqi, "Unknown") + except Exception as e: + return f"Error: {e}" + + def update_weather(self): + """ + Actualiza la información del clima periódicamente. + """ + while True: + lat, lon, location_info = self.get_location() + if lat and lon: + city, temp, details = self.get_weather(lat, lon) + self.header_label.config(text=f"Weather in {city}") + self.temp_label.config(text=f"{temp}°C") + self.details_label.config(text=details) + else: + self.header_label.config(text=location_info) # Error de ubicación + + time.sleep(60) # Actualizar cada 60 segundos + + def start_weather_updates(self): + """ + Inicia el hilo para actualizar el clima. + """ + weather_thread = threading.Thread(target=self.update_weather, daemon=True) + weather_thread.start() diff --git a/hilos/__init__.py b/hilos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hilos/__pycache__/ApplicationLauncher.cpython-313.pyc b/hilos/__pycache__/ApplicationLauncher.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e66e4892cae17cb3a75055ebee8c63431466db8a GIT binary patch literal 5211 zcmcgwU2GHC6}~h6iDNrISVBDcMpD{giC$scD5huL8{}&knv? z2udIPmj4L7x5?Rhjdx=)LB?>T;*kVN>=F-owoVYOr0q5GKFPmAzNGUt@&PHhLcVz3 z(|8zaSU#=@{uA^v{#_z`s*uY*3n-B>o%;G zmujUtslGP=CseWi=!afR;<5zuWwamV{DWCE07LbGTV~PcsSh8GG(6eg7dx~{}$*OKnj$R&^mf6f?Qq?E3ms41OyN56H-8avI zeb3C;x@ujfgJ~5xiyqj2;9$Hv-m_Yv6YVd%yqM3a9#*tj1+VKl0?6?v4K3|dYiefN zN@}(OB7RGmvmF1Fp<9la%sfY@j0uiEE2l5dN6x6GEo=PnoiLx4VTz*1M1};(kTqg{ zhA!h&rNB~IPcvMJ)nJz}_Z(R;CW3xd(-_i+Q{^3{Q{^4ZNUY8Yy1;QF9Bfu1D_he} zWY$cy3Jv zo!a6O731DoPHk~fcN`DTRJ19EEa3z!)dJ8Wi-R9H@59x)05xUZQUL_~;sM|nl#~Ua zV8&&r7(=QqYtTq3reR77n+5+g%*kmL+!Fy^8L8sg%T6efPyrE%gwwW`L~#^v$dn-< zT_Z1Q$)>G8kGu$yHd<)in{VBl+xLT|eOF%ignd5}mWj}IRk-f|Q)u%SFy7XgZ|ls( zhnM0>7>}oMe5=C8x0H>mFGSKcD5A)ah$8QbsA9`kA+n+Ich4uS0FmjEK_F`2oy;BRGCvvi;n=W+d6~~X!EF$HB2=L zuCQi!u%=DA2Sp(=oGQ-XafFuU@n~f0Q~_6kr9cVZW@TnN zLB2;b=40Q0qj3T#V6rZ#7u}r2pux7_X!}~OSjJ|ZIPD!Mu94*?(%f?W=+j}QOi zaG`NmzH!$=`f=l4=&Rds^TQh--n@F_>Ya3?FcjgYhy~dpLUnXPfc`SPKLc#tasVhsPmd30gACD3I^L3kWVPD>H^}V_r5= zTN?OI^cXqGiC1unC@vO+Mf3GLe`e)JR zLbUDkXxqZcLPt-&qi1n^acHskv+;a%tPnk$kDh%Jeg7}f4Y{U{h4F=<+*@aI4c~tf zJ?rAGoGCFPbKK2CCM2NU>1(g zzW7Rb_E8MCVbe}#dCqG(KEQ`lJvf(AviNnw20>R58Tj{sBUHd*06W)=wq;1003q{R zP+;!st3Dw-i*CF*^3#z!haYr2j&}X?S>0wxS$RKT;K<_l76)<-#|zQ^e6;`HkkZ1) zzf5$CkD51=JK;XxZ)y+u{t)Q%#j02bbi2Hc)Xa8c!C7wy)-cA6#G;s_Bcvfo4<`~c zM%vb}9!(@J!3UK$QkO_fsm!!ARabN)kzjA31`KfwV-~6j97X;-Sl!ZahWEF)R>iCZ;GqAuq)pfrBrHh1x*VODYD2g_j{QaLg+rfpfy& zTEc-70>?j}8w)yB)q|B`l5wUt}C>NECcwCv?Q!=rMK%`ULoy?NGpZ d3c^#;^pw;;B^#cS>VE{!3c?BDCE+iw;6G9s@oxYC literal 0 HcmV?d00001 diff --git a/hilos/__pycache__/ChatWidget.cpython-313.pyc b/hilos/__pycache__/ChatWidget.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c80d7b6fe85e0822b403a9c47aa7c333be0e2c5 GIT binary patch literal 5007 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 A3;+NC literal 0 HcmV?d00001 diff --git a/hilos/__pycache__/LanguageChart.cpython-313.pyc b/hilos/__pycache__/LanguageChart.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e488e863511eb8db2ba0961e4c5796314c7cab60 GIT binary patch literal 3369 zcmahLOKcm*b#_TES4-+2DUxEVYs+c16++iaWx#Bj)RAK=`dQV=ZUG5cjK~qS(Q22O zT`G}JMGFL0>cUO|A#N|V(L?P+Q5U_ar?&8^xm1i*$QZ|`o_I^821R@7drK}UDGE9u z-@JMA-kUdX-e>oln>7OCx8#?F6_t>G;369F8nQnI$R;s~DNPVA!7NY6<1rr7krtbX zk0*E{#H9&^Dd&oQ zEEIB8i7J$8i~X+yvPl*~Be-lzJZ8#$gvZSozyux!$0vBQpcqNfJW1U`i5A%CGUY)f z39`AGA)!76U=05IlR(`hKWuYCGJ>;k)$dE!h!dTR6gi~SHO!;JaF{VOZYD0sz#qAc zTqCpK&ZA>Cb(BJgsf^T40eQ4393Y>L@Wwpb+Q1_WIO{e=G=k^XZD~-=q^Vwr>9~8G z_Vxy6gqcl|bzdpf8Syq|Q)@`gcSUqK^%$Q3HDWk0o2k&Bb$TEZH`Wmb(ybTd*@0-?n4?p5QfM>UOFxAj?QPf2 z+0?ObP@Oq?fj_>#Wam6R&veK0b%$YezQcSZ6uDR6bd~1p$A573)yMaJz2wup=VvtS zA}{z^O@~pWoVkHM7_4M212;3WdN}KO&jlqu({rBVqx_D~WDw1j_wZ$DYT=z)9vw|&OrIk*jY03A^BJ*)_;)DIXJ@kK9K zo-F$dJ_^!Y>S}PPV6N-#T z(R|+WON&Lv3xrJH7qLakMU&40E3gBH2@TP8hb}UQ<7I`C3(Jvch6Al&#d^=q*0V3{+Tx7~tJ=0a4YOT|15n3cnFBN9HC8sHUt<}C5kpwY)X)pap9nq9c&Gwv_= z)D7%~nROIdo#Ea)EEo6-fUE=JUAA57z#_+d&o>!ggK+Zvg)tk#XW>wB%M{mzYFV~x z2bLwe5AQ{E%su}(fFF_1TgZziZ>9DW(sxC=8Nbycx(t}Ke8pX(r?D^k3X_+rpoH~YP-8? zZ&~f#?mM@m4%bP2WwmenrOb|c7Ec;{5dU53VQTBrpC7vI&-mGyI5XUgguyXsqI^{v0FSN7q8Bv-H7 zut>lkLt+0efKAM7n2(N5a$WM}v+zcpWUPY_uiccgFjJ7eCYkapAaFGfSnL%@h#7eb z5+p@*;CHc4)YZ+`Yw>aQ7aL-uG8PiPT-C5d45joFP@SKD;UWNY~M*-yK_``2u_>pDj1 zXAmRWhuWQUKWqNaUW|1AK=RSZ+r1Z)@k1|P@9-bMbv8*qzWu?-03jTW+70{l_p9z9 zoJH+N#{ZS6e;qUgLHqIjFZ={+@F8YG%+)&eaTYZn<_;xdjmY|ndtg03G7iQgNa2eg z8MDXq_r~q#u~CHMxc&_Bo@iJ#^%v+QN(m!GyBJ0C!A7MQ8HWa#w{y_k+<@uK)l06v z3*i}hu=hJlC?W-GCchV;jyRvcR_SDAI zt*g;uVq@ZN{8uf%Xn~5SJ=C_0n-k^qrP}Up`eZqMa=U+ICw;z7>My7Jx4$;LlYSLR zAitYFQBI%ON%wyS>+uiA@4S0!>YuG}G(Qh!7)p5k@Ou1ZAXG1Z8Zk`VX+y;Z;eubG zL1>n>>g7ug;%Uoz7ivmvr`58SZ0-k+?K0Q1EdFiO5V|*XNtb0o1qtk&MT3Cbi=}}1 zmi3T?MpRvgNyiaj2KZ1y$p>Ul?oEtId*_=H-CuO45`9&6WHOpWGFY+UvAK|g716#* z18mWby_l(X?rIx9b%4GI4VbxkKT|`XInzQRY}y9dF|j9aODL>TA_|zs{$D nhVSj;C(q|GNs^wC7oL+d&&eClNZT`#`geR->f0j#Lizszxwg$h literal 0 HcmV?d00001 diff --git a/hilos/__pycache__/MusicPlayer.cpython-313.pyc b/hilos/__pycache__/MusicPlayer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da9147ffea0bfff5fa027a3af03b1dc8026d56e6 GIT binary patch literal 5064 zcmcIoU2GHC6&~ATdty&)6Os^;kPIRGOi;tJ5Ed`AG;Bn7SqR`sSSV3P6ML|Sj6K;q zV<36rsRD^A-AXK!7qU;wzEM@$rF|*YN-a;7hk_gpw5ql|G;b8B`q-Xx$Kx@28qh(Xe9@mvQl`Ka=OSa|# z)yx46!T*aJK(8^UQaT+*A_XQH-Lub}@!p!rMCu}SCeu=9>&!@v6PPn}FR`29>(g~+ zfSF`6^`6g2K`A7$QaH_mALuxJKsMp=ew$h2^ByNfq$vDxQb1}+hhOoc&6Dw4>O~XH zQcQ}2Cp`6{{vO~(yT8`sntIVHCDMUctap0ye)~r2NwC!A%XyrXlGad-KoR zU8P5FI+bSo5tQ1}K{%2699Zu4EqYv2%h6TuMH7wETCmjT>GxZyy|x!AZw%xFkN4Zm zCZG2>sRRC6rOtF{RVUQUt94*)(9`F)QWyBOJ{^XgTI-F1w zVP}=xMM~!!7IKd6MAVD3N-0;ysLZId#K@JimO+G^Djfa#w~#s%FcKamkeik8%7`Jd9vUZ_HHt;B zOHe1QLJeh%sc%00Qt_>s5;@Ib`f;3 zT1m5k}nRe$A zS==u}H)U5_46VyW%+XGq@-;y=Pqds9Gc9A*o2I-wEe7F0RsAf%pf+)T1i{I?_wLlB zsSspPx{2W3tbG#zL|xfq_E zb2wSXeUN3Ry>Z4H#rk3X3^w%=v((vpHNMo{f0@0Sd>LU{k_-G5zRIn)x%CgZ9vJED zx_;^xr*7nK&HQrawtjEZ{jA-Q0qNKh$1lXL#1@iQk~dD>K6oc{_w4=2%SoG)U{!D5 zt-Uw*F7c^_!&eShHjGsEpQxOi{%0uI8@tRtu{YUKmM=BF1K|vo?&|Oo|ZFAjKuHWYR7f)2M-3ki&H{L4TEG$}| z&wn<5N4NXlLz6w=Zh+$dJsWN{s#q-Eo%o&n8@V#^lgin`YLqpIm9^SAHv}1G|`Bl>VkXU+zh6;na331x+^fL=0T=h5^Cd^ zI4vG!9TcT2M^I zvIs!~WEYZyNcP~$7OOy1B}WU{1ZABzTH4&|A-mJG77~k$!IH*k!p{q*voV_dx`gV@ z0uTtMc-zl+-{`Hzx7hJ5OML^E$F3e*89YM}A2vU-a(EjZ-u0C4sPerJ_};~_>fk9iF7MLhlghIVi8t_ndK)MPiY!{>3Z zjBx*bfF;7mWwm~fRJ>GP+J<{UHiHyFWDU@b0x4EElpMWX0CfRhIUL+1dAe-Q(W?W{ z2~gE*cVHYf^flgr*YD2(kTEXr?v3+Uny6Xq7A9#}Lyv~2ZS5pIaYM`3hYNxh7Oo>aWj+ap;+0kU_ zqr~>t?+Odv(Yx{oVBqHoEaAYp55#=EoO@${`!Q>;c<}SPmS3Bd^kysI#|WBVIZsul z{=w{xH2(=8ll133t|8XKVua?@8p{9HoO+174;sjikO)A=MXp)8^katLZx7@ol9fpj zU3fJq`e+=jm`8zJV*ci0UWy7`7)nuOfuhJAD~Hd~;hjw)qSt1UGQbOBTDn$JONK0y zG-^N?K`{Fh41YpjxkHxmi%HAMSZy@;;!sUl{(_<2x#$CrpCG|HYvMKiDf2uy7=GvZ zkw7H;QQ&zh8qNfsw?)I9|7r_|2i&E&K#Vw1Yep-<2N~H0eF})k5#%-_p_&o8_|_b5 tzxz%0l3LmcgcX z2C{fpRn&)ysF606O50sk3j0*@P>Dw#BlRWilLuHCO=z`J`_Md8ib8*+st-M9Yy&pq zT}O#}C7(Ix+HvZEn}2|s893)FupLxJf%$@g&4-S4PZ(RbqKt@6uYV9_{ zYlZpA?ZVzj#9Qd!c^)EBMr1^n=oVSgbJ`On+V=PgEWM&{mnAOIZWUN^qJNj=_R1=- z42V^`EPaJJqa=cFq@+daN)#Z9L9tq_5o=Goc>-4UsVE**fmN{B0;LM9YF^&zc74Gn z(pcbjp78xhq-jSkaInA7X;-ABAn!a+A2M~3AZ zOIYl9Drs0=s%WY*p7uq~-`M3SmZGxupWp@AFcdmB$Pe|MxBT`LW7>?8=7tnww{lud zPFk#{j2jj^mNF)W1-HfOYFvR?R86zI%16_3G7c;A8Zu2NFmGH|rcy~EWO=7$s=!JD zx{;celv(6m#-!!BE{`di<&RCzNLr4i@>HI!9?Pw)ASM;#PKr+EMRr<_OFsw^ftp^Uk;Bymxz3iZ)L1n9+SebObb>=dIz3 zgQHPhq57yUCk=J<)?92trc<|Q>O&=F=%c{Q0h79{CS?tTn4+h2QK7SHOik&dS5*yY z5rFni&sm%#seq=EWHlA{74(Qh`Y2%MU9!>CcE4()wc~#6KfI*1?P2)y@Zwubb1S2n zTXN<@YPRU}?ERX}mVM=V_8)vW^7+W(`0}~OlbI1UL(}H|kLTHSu61*}Yw+>W%&?M4 z&YJu0*j>-P#9#es&Apl|x6kDEEjBHUuMB69zp%jt%XVbBR+DSZatBTB;NttsZ#{0z zAb%Nb%5}l6{PkHbyvl_a+27WFQMpGAgd}_kpTMPC&c}=ySyR(8KaZd#u)RL!bF6q{CP4A6}t;#=NWDmktuERQ@6 z5Nok_WYrK{vP~jyX33-reF2_yf0gS#H>G( zt?x4HyEelIvf-}PaMxP6d;Z!+F!brry`gNd-3+!by|Fa6G`UijIeaFAinH6jycy&d z8^8EuJ=l-BoA+*JgSM`2J^1ExKWS(#n&!*;F8ftD$4U}j&@08tFDs7~r_#!fAE)BU zDGK(+kCUZS6jT;JPG#Qfi=TyWh#$!H5I=Utr+9;)$BWy7b;@Tvc61mTqdk#p<6uNpyrsyu=q zT9`tXl7|RfyBuT61$u|b5ShVW5F$f-l<9Z%0I=O;M0e5+S|7i2Y0)D6^o z+#~uhx7}=c2@h~x&F4d>Ih~@?F9s^ehnQwcnppMP^5G`Lb1)GUs%P+^7Nk0LxWA>{P`ju7+m(Ep?d;}{qAe_$n32z+%l$bVz`|-s2$bSLt*#{X6xF4?FM1iqS)8uKble2xK`ey>HkV3$MJ9ej zol+#U(OIy8$~~W2@u*vEU$_2l7@_|Wh%-bqwPl;SSDU)mntJE2{JFMiqqY5E-)DUf zd>aGrWe2XB16Q9MTN}9k>nqu|ZnLd>nO$w`&qTiWz`qe{%!Uq`p+nh_V1|U{mX%X0 z!sGrY{>L9@BHz#SeUJ$WtD)Op6%SkbF{sVZIY`Ud-~lsuARFv3gB^>*kIt?K2ON@h zLq*AY8)_nOeiq1W*lw<&T-pv1Oaj;<_%cgclBw8w``?ru7fDy;#cp901-OdCSE_h! zrv`pejvzMlS41G@VzaNQR&mCv{6&8+R$YQ!((6ce)hmHql~M~r!1Cn)i%V(ooT<&Bp~WSYJ9!;)w$6v{rKvH? zhaxaK1}`ESwA_6Gi>xxBmftAWUHadF+$Dc)XvsDRs|~_h!_jQRNweYPJh$nf+i(D}oScHzN9#kf~j4)+9aE^XjtjN0GB{#VDYEb&7@8p8iy zk}@O=$qGo)R4P8BAs>{aA3;%?@2Qofah2+ZrY4nSN|Goh3j3-?vF4c4)dbh>*;tAvPqSvKX)?>^ORISC84LJ@!#yX M7^dR|L2A$RKN;(;ga7~l literal 0 HcmV?d00001 diff --git a/hilos/__pycache__/WeatherWidget.cpython-313.pyc b/hilos/__pycache__/WeatherWidget.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63f0e462a05f21f0f034e855ce54f89a78d21c51 GIT binary patch literal 6354 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 literal 0 HcmV?d00001 diff --git a/hilos/__pycache__/__init__.cpython-313.pyc b/hilos/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9d496c7a90765991250dc02b0d9a47c89aac5a1 GIT binary patch literal 176 zcmey&%ge<81bo#q(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iqvsFxJacWU< zOmSjfNoGtyWpYMhQEos{epYI7NpTF2Q3+(^yJhAj<^Va#sm1xl!Kp=MnaP>?#W5b4 uIY2T#J~J<~BtBlRpz;=nO>TZlX-=wL5i8Jqkp0CV#z$sGM#ds$APWGzeJ + + # Insertar en la base de datos + cursor.execute("INSERT INTO scraped_data (title, link) VALUES (%s, %s)", (title_text, link)) + connection.commit() + + # Mostrar en la interfaz + self.scraped_data_text.config(state="normal") + self.scraped_data_text.insert("end", f"{title_text} - {link}\n") + self.scraped_data_text.see("end") + self.scraped_data_text.config(state="disabled") + + self.status_label.config(text=f"Estado: Scrapeando {title_text}...", fg="green") + + # Pausa entre iteraciones + time.sleep(5) + + connection.close() + self.status_label.config(text="Estado: Inactivo", fg="red") + except Exception as e: + messagebox.showerror("Error", str(e)) + self.status_label.config(text="Estado: Error", fg="red") + + def stop_scraping(self): + """Detiene el proceso de scraping.""" + self.stop_event.set() + self.start_button.config(state="normal") + self.stop_button.config(state="disabled") + + def reset_database(self): + """Elimina todos los datos de la tabla.""" + try: + connection = mysql.connector.connect( + host=self.host_entry.get(), + user=self.user_entry.get(), + password=self.password_entry.get(), + database=self.database_entry.get() + ) + cursor = connection.cursor() + cursor.execute("TRUNCATE TABLE scraped_data") + connection.commit() + connection.close() + messagebox.showinfo("Éxito", "Datos reseteados correctamente.") + + # Limpiar el cuadro de texto + self.scraped_data_text.config(state="normal") + self.scraped_data_text.delete("1.0", "end") + self.scraped_data_text.config(state="disabled") + except Exception as e: + messagebox.showerror("Error", str(e)) diff --git a/solapas/__init__.py b/solapas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solapas/__pycache__/EconomyBitcoinChart.cpython-313.pyc b/solapas/__pycache__/EconomyBitcoinChart.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..288e701f2004b8176b76bea17a29561b2311a37e GIT binary patch literal 4486 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 QQRhGXCz-xi2nJX9zd4W2Jpcdz literal 0 HcmV?d00001 diff --git a/solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc b/solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..325e8d1b09e1ff3dd17a4f7cd1f9143692b00942 GIT binary patch literal 8374 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 literal 0 HcmV?d00001 diff --git a/solapas/__pycache__/TicTacToe.cpython-313.pyc b/solapas/__pycache__/TicTacToe.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffb0e71a6ed871381d9b18c08cf0db6c2c32670e GIT binary patch literal 6890 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