commit 1d4908840f506d0c4f2d6a4cce4b0a59984279dc Author: Santi Date: Wed Jan 29 16:35:16 2025 +0100 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 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 0000000..e66e489 Binary files /dev/null and b/hilos/__pycache__/ApplicationLauncher.cpython-313.pyc differ diff --git a/hilos/__pycache__/ChatWidget.cpython-313.pyc b/hilos/__pycache__/ChatWidget.cpython-313.pyc new file mode 100644 index 0000000..9c80d7b Binary files /dev/null and b/hilos/__pycache__/ChatWidget.cpython-313.pyc differ diff --git a/hilos/__pycache__/LanguageChart.cpython-313.pyc b/hilos/__pycache__/LanguageChart.cpython-313.pyc new file mode 100644 index 0000000..e488e86 Binary files /dev/null and b/hilos/__pycache__/LanguageChart.cpython-313.pyc differ diff --git a/hilos/__pycache__/MusicPlayer.cpython-313.pyc b/hilos/__pycache__/MusicPlayer.cpython-313.pyc new file mode 100644 index 0000000..da9147f Binary files /dev/null and b/hilos/__pycache__/MusicPlayer.cpython-313.pyc differ diff --git a/hilos/__pycache__/SystemMonitor.cpython-313.pyc b/hilos/__pycache__/SystemMonitor.cpython-313.pyc new file mode 100644 index 0000000..dbac2fa Binary files /dev/null and b/hilos/__pycache__/SystemMonitor.cpython-313.pyc differ diff --git a/hilos/__pycache__/WeatherWidget.cpython-313.pyc b/hilos/__pycache__/WeatherWidget.cpython-313.pyc new file mode 100644 index 0000000..63f0e46 Binary files /dev/null and b/hilos/__pycache__/WeatherWidget.cpython-313.pyc differ diff --git a/hilos/__pycache__/__init__.cpython-313.pyc b/hilos/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..f9d496c Binary files /dev/null and b/hilos/__pycache__/__init__.cpython-313.pyc differ diff --git a/solapas/EconomyBitcoinChart.py b/solapas/EconomyBitcoinChart.py new file mode 100644 index 0000000..41cd3b9 --- /dev/null +++ b/solapas/EconomyBitcoinChart.py @@ -0,0 +1,69 @@ +import tkinter as tk +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import threading +import time +import random + +class EconomyBitcoinChart: + def __init__(self, parent): + """ + Inicializa los gráficos de economía mundial y Bitcoin en disposición vertical. + + Args: + parent (tk.Frame): Frame donde se colocarán los gráficos. + """ + self.parent = parent + + # Crear la figura para los gráficos + self.figure = Figure(figsize=(8, 6), dpi=100) + + # Subgráficos: Economía mundial y Bitcoin + self.ax_economy = self.figure.add_subplot(211) # Gráfico superior + self.ax_bitcoin = self.figure.add_subplot(212) # Gráfico inferior + + # Inicializar datos simulados + self.economy_data = [random.randint(50, 100) for _ in range(10)] # Economía en meses + self.bitcoin_data = [random.randint(20000, 60000) for _ in range(10)] # Bitcoin en días + + self.update_economy_chart() + self.update_bitcoin_chart() + + # Embebiendo los gráficos en Tkinter + self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent) + self.canvas.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=10) + + # Iniciar hilos para actualizar los gráficos + threading.Thread(target=self.update_charts, daemon=True).start() + + def update_economy_chart(self): + """Actualiza el gráfico de economía mundial.""" + self.ax_economy.clear() + self.ax_economy.plot(self.economy_data, marker="o", color="blue") + self.ax_economy.set_title("Economía Mundial") + self.ax_economy.set_ylabel("Índice económico") + self.ax_economy.grid(True) + + def update_bitcoin_chart(self): + """Actualiza el gráfico de Bitcoin.""" + self.ax_bitcoin.clear() + self.ax_bitcoin.plot(self.bitcoin_data, marker="o", color="green") + self.ax_bitcoin.set_title("Precio de Bitcoin") + self.ax_bitcoin.set_ylabel("Precio en USD") + self.ax_bitcoin.set_xlabel("Días") # Etiqueta para los días + self.ax_bitcoin.grid(True) + + def update_charts(self): + """Actualiza ambos gráficos periódicamente.""" + while True: + # Actualizar datos simulados + self.economy_data = self.economy_data[1:] + [random.randint(50, 100)] # Economía en meses + self.bitcoin_data = self.bitcoin_data[1:] + [random.randint(20000, 60000)] # Bitcoin en días + + # Actualizar gráficos + self.update_economy_chart() + self.update_bitcoin_chart() + self.canvas.draw() + + # Esperar 5 segundos antes de la próxima actualización + time.sleep(5) diff --git a/solapas/MusicDownloader.py b/solapas/MusicDownloader.py new file mode 100644 index 0000000..5d7859c --- /dev/null +++ b/solapas/MusicDownloader.py @@ -0,0 +1,68 @@ +import tkinter as tk +from tkinter import ttk +import threading +from pytube import YouTube + + +class MusicDownloader: + def __init__(self, parent): + """ + Inicializa la interfaz para descargar música de YouTube en MP3. + + Args: + parent (tk.Frame): Frame donde se colocará el downloader. + """ + self.parent = parent + + # Etiqueta de título + title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold")) + title.pack(pady=10) + + # Entrada para la URL + self.url_label = tk.Label(self.parent, text="URL de YouTube:") + self.url_label.pack(pady=5) + self.url_entry = tk.Entry(self.parent, width=50) + self.url_entry.pack(pady=5) + + # Botón para iniciar la descarga + self.download_button = tk.Button(self.parent, text="Descargar MP3", command=self.start_download, bg="lightblue") + self.download_button.pack(pady=10) + + # Barra de progreso + self.progress = ttk.Progressbar(self.parent, orient="horizontal", length=300, mode="determinate") + self.progress.pack(pady=10) + + # Etiqueta de estado + self.status_label = tk.Label(self.parent, text="", font=("Helvetica", 10)) + self.status_label.pack(pady=5) + + def start_download(self): + """Inicia la descarga en un hilo separado.""" + url = self.url_entry.get() + if not url: + self.status_label.config(text="Por favor, ingrese una URL válida.", fg="red") + return + self.status_label.config(text="Iniciando descarga...", fg="blue") + threading.Thread(target=self.download_music, args=(url,), daemon=True).start() + + def download_music(self, url): + """Descarga el audio de YouTube como MP3.""" + try: + # Inicializa la descarga + yt = YouTube(url, on_progress_callback=self.update_progress) + stream = yt.streams.filter(only_audio=True).first() + + # Descargar archivo + self.status_label.config(text="Descargando...") + self.progress["value"] = 0 + stream.download(filename=f"{yt.title}.mp3") + self.status_label.config(text="¡Descarga completada!", fg="green") + except Exception as e: + self.status_label.config(text=f"Error: {str(e)}", fg="red") + + def update_progress(self, stream, chunk, bytes_remaining): + """Actualiza la barra de progreso durante la descarga.""" + total_size = stream.filesize + bytes_downloaded = total_size - bytes_remaining + percentage = (bytes_downloaded / total_size) * 100 + self.progress["value"] = percentage diff --git a/solapas/SQLQueryExecutor.py b/solapas/SQLQueryExecutor.py new file mode 100644 index 0000000..a7f37a5 --- /dev/null +++ b/solapas/SQLQueryExecutor.py @@ -0,0 +1,113 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import threading +import mysql.connector +from mysql.connector import Error + + +class SQLQueryExecutor: + def __init__(self, parent): + """ + Clase para ejecutar consultas SQL en una base de datos MySQL. + + Args: + parent (tk.Frame): Frame donde se colocarán los widgets. + """ + self.parent = parent + + # Campos para ingresar información de conexión + self.db_info_frame = tk.Frame(self.parent) + self.db_info_frame.pack(pady=10, padx=10, fill="x") + + tk.Label(self.db_info_frame, text="Host:").grid(row=0, column=0, sticky="w") + self.host_entry = tk.Entry(self.db_info_frame) + self.host_entry.insert(0, "localhost") + self.host_entry.grid(row=0, column=1) + + tk.Label(self.db_info_frame, text="Usuario:").grid(row=1, column=0, sticky="w") + self.user_entry = tk.Entry(self.db_info_frame) + self.user_entry.insert(0, "root") + self.user_entry.grid(row=1, column=1) + + tk.Label(self.db_info_frame, text="Contraseña:").grid(row=2, column=0, sticky="w") + self.password_entry = tk.Entry(self.db_info_frame, show="*") + self.password_entry.grid(row=2, column=1) + + tk.Label(self.db_info_frame, text="Base de datos:").grid(row=3, column=0, sticky="w") + self.database_entry = tk.Entry(self.db_info_frame) + self.database_entry.grid(row=3, column=1) + + # Botón para conectar a la base de datos + self.connect_button = tk.Button(self.db_info_frame, text="Conectar", command=self.connect_to_database) + self.connect_button.grid(row=4, column=0, columnspan=2, pady=5) + + # Área para ingresar consultas SQL + self.query_frame = tk.Frame(self.parent) + self.query_frame.pack(pady=10, padx=10, fill="both", expand=True) + + tk.Label(self.query_frame, text="Consulta SQL:").pack(anchor="w") + self.query_text = tk.Text(self.query_frame, height=10) + self.query_text.pack(fill="both", expand=True) + + # Botón para ejecutar consultas + self.execute_button = tk.Button(self.query_frame, text="Ejecutar", command=self.execute_query) + self.execute_button.pack(pady=5) + + # Área para mostrar resultados + self.result_frame = tk.Frame(self.parent) + self.result_frame.pack(pady=10, padx=10, fill="both", expand=True) + + tk.Label(self.result_frame, text="Resultados:").pack(anchor="w") + self.result_text = tk.Text(self.result_frame, height=10, state="disabled") + self.result_text.pack(fill="both", expand=True) + + def connect_to_database(self): + """Conecta a la base de datos utilizando los datos proporcionados.""" + self.host = self.host_entry.get() + self.user = self.user_entry.get() + self.password = self.password_entry.get() + self.database = self.database_entry.get() + + try: + self.connection = mysql.connector.connect( + host=self.host, + user=self.user, + password=self.password, + database=self.database + ) + if self.connection.is_connected(): + messagebox.showinfo("Conexión Exitosa", "Conectado a la base de datos") + except Error as e: + messagebox.showerror("Error de Conexión", str(e)) + + def execute_query(self): + """Ejecuta la consulta SQL en un hilo separado.""" + query = self.query_text.get("1.0", tk.END).strip() + if not query: + messagebox.showwarning("Consulta Vacía", "Por favor, ingrese una consulta SQL.") + return + + threading.Thread(target=self.run_query, args=(query,), daemon=True).start() + + def run_query(self, query): + """Ejecuta la consulta y muestra los resultados.""" + try: + cursor = self.connection.cursor() + cursor.execute(query) + + if query.strip().lower().startswith("select"): + rows = cursor.fetchall() + column_names = [desc[0] for desc in cursor.description] + + # Mostrar los resultados + self.result_text.config(state="normal") + self.result_text.delete("1.0", tk.END) + self.result_text.insert(tk.END, "\t".join(column_names) + "\n") + for row in rows: + self.result_text.insert(tk.END, "\t".join(map(str, row)) + "\n") + self.result_text.config(state="disabled") + else: + self.connection.commit() + messagebox.showinfo("Éxito", "Consulta ejecutada correctamente.") + except Error as e: + messagebox.showerror("Error de Consulta", str(e)) diff --git a/solapas/TicTacToe.py b/solapas/TicTacToe.py new file mode 100644 index 0000000..7504754 --- /dev/null +++ b/solapas/TicTacToe.py @@ -0,0 +1,123 @@ +import time +import tkinter as tk +from tkinter import messagebox +import threading +import random + + +class TicTacToe: + def __init__(self, parent): + """ + Inicializa el juego de Tic Tac Toe. + + Args: + parent (tk.Frame): Frame donde se colocará el juego. + """ + self.parent = parent + self.board = [""] * 9 # Tablero de 3x3 representado como una lista + self.current_player = "X" # Jugador inicial + self.vs_computer = False # Modo jugador vs máquina + + # Etiqueta para el título + title = tk.Label(self.parent, text="Tic Tac Toe", font=("Helvetica", 16, "bold")) + title.pack(pady=10) + + # Botón para alternar entre modos + self.mode_button = tk.Button(self.parent, text="Modo: Jugador vs Jugador", command=self.toggle_mode) + self.mode_button.pack(pady=5) + + # Crear el tablero + self.buttons = [] + self.board_frame = tk.Frame(self.parent) + self.board_frame.pack() + + for i in range(9): + button = tk.Button( + self.board_frame, + text="", + font=("Helvetica", 20), + width=5, + height=2, + command=self.create_button_command(i) # Aquí usamos la función auxiliar + ) + button.grid(row=i // 3, column=i % 3) + self.buttons.append(button) + + # Etiqueta para el estado del juego + self.status_label = tk.Label(self.parent, text="Turno: X", font=("Helvetica", 12)) + self.status_label.pack(pady=5) + + def toggle_mode(self): + """Alterna entre los modos Jugador vs Jugador y Jugador vs Máquina.""" + self.vs_computer = not self.vs_computer + mode_text = "Modo: Jugador vs Máquina" if self.vs_computer else "Modo: Jugador vs Jugador" + self.mode_button.config(text=mode_text) + self.reset_game() + + def reset_game(self): + """Reinicia el tablero y el estado del juego.""" + self.board = [""] * 9 + self.current_player = "X" + for button in self.buttons: + button.config(text="", state=tk.NORMAL) + self.status_label.config(text="Turno: X") + + def make_move(self, index): + """Realiza un movimiento en el tablero.""" + if self.board[index] == "": + self.board[index] = self.current_player + self.buttons[index].config(text=self.current_player) + + # Verificar si hay un ganador + winner = self.check_winner() + if winner: + self.end_game(f"¡Ganador: {winner}!") + return + elif "" not in self.board: + self.end_game("¡Empate!") + return + + # Cambiar de jugador + self.current_player = "O" if self.current_player == "X" else "X" + self.status_label.config(text=f"Turno: {self.current_player}") + + # Si está en modo Jugador vs Máquina y es el turno de la máquina + if self.vs_computer and self.current_player == "O": + threading.Thread(target=self.computer_move).start() + + def computer_move(self): + """Simula el movimiento de la máquina.""" + self.status_label.config(text="Turno: Máquina (O)") + available_moves = [i for i, v in enumerate(self.board) if v == ""] + move = random.choice(available_moves) + + def delayed_move(): + time.sleep(1) # Simular el tiempo de "pensar" + self.make_move(move) + + threading.Thread(target=delayed_move).start() + + def check_winner(self): + """Verifica si hay un ganador.""" + winning_combinations = [ + (0, 1, 2), (3, 4, 5), (6, 7, 8), # Filas + (0, 3, 6), (1, 4, 7), (2, 5, 8), # Columnas + (0, 4, 8), (2, 4, 6) # Diagonales + ] + for a, b, c in winning_combinations: + if self.board[a] == self.board[b] == self.board[c] and self.board[a] != "": + return self.board[a] + return None + + def end_game(self, message): + """Finaliza el juego mostrando un mensaje.""" + messagebox.showinfo("Fin del Juego", message) + self.reset_game() + + def create_button_command(self, index): + """Crea un comando para un botón con un índice específico.""" + + def command(): + self.make_move(index) + + return command \ No newline at end of file diff --git a/solapas/WebScraperToDB.py b/solapas/WebScraperToDB.py new file mode 100644 index 0000000..248deab --- /dev/null +++ b/solapas/WebScraperToDB.py @@ -0,0 +1,195 @@ +import tkinter as tk +import threading +import time +import mysql.connector +import requests +from bs4 import BeautifulSoup +from tkinter import messagebox + +class WebScraperToDB: + def __init__(self, parent): + """ + Inicializa el widget de scraping con integración a base de datos. + """ + self.parent = parent + self.scraping_thread = None + self.stop_event = threading.Event() + + # Crear campos de conexión para la base de datos + db_frame = tk.Frame(self.parent) + db_frame.pack(pady=5) + + tk.Label(db_frame, text="Host:").grid(row=0, column=0) + self.host_entry = tk.Entry(db_frame) + self.host_entry.insert(0, "localhost") + self.host_entry.grid(row=0, column=1) + + tk.Label(db_frame, text="Usuario:").grid(row=1, column=0) + self.user_entry = tk.Entry(db_frame) + self.user_entry.insert(0, "root") + self.user_entry.grid(row=1, column=1) + + tk.Label(db_frame, text="Contraseña:").grid(row=2, column=0) + self.password_entry = tk.Entry(db_frame, show="*") + self.password_entry.grid(row=2, column=1) + + tk.Label(db_frame, text="Nombre BD:").grid(row=3, column=0) + self.database_entry = tk.Entry(db_frame) + self.database_entry.insert(0, "scraping_db") + self.database_entry.grid(row=3, column=1) + + tk.Button(db_frame, text="Crear Base de Datos", command=self.create_database).grid(row=4, column=0, columnspan=2, pady=5) + + # Área para URL y botones de control + control_frame = tk.Frame(self.parent) + control_frame.pack(pady=5) + + tk.Label(control_frame, text="URL para Scraping:").grid(row=0, column=0) + self.url_entry = tk.Entry(control_frame, width=50) + self.url_entry.insert(0, "https://quotes.toscrape.com/") + self.url_entry.grid(row=0, column=1) + + # Campo para Selector HTML + tk.Label(control_frame, text="Selector HTML:").grid(row=2, column=0) + self.selector_entry = tk.Entry(control_frame, width=50) + self.selector_entry.insert(0, "h1") # Valor predeterminado + self.selector_entry.grid(row=2, column=1) + + self.start_button = tk.Button(control_frame, text="Iniciar Scraping", command=self.start_scraping) + self.start_button.grid(row=1, column=0, pady=5) + + self.stop_button = tk.Button(control_frame, text="Parar Scraping", command=self.stop_scraping, state="disabled") + self.stop_button.grid(row=1, column=1, pady=5) + + self.reset_button = tk.Button(control_frame, text="Resetear Scraping", command=self.reset_database) + self.reset_button.grid(row=1, column=2, pady=5) + + # Área para mostrar el estado + self.status_label = tk.Label(self.parent, text="Estado: Inactivo", fg="red") + self.status_label.pack(pady=5) + + # Área para mostrar los datos scrapeados + self.scraped_data_frame = tk.Frame(self.parent) + self.scraped_data_frame.pack(pady=5, fill="both", expand=True) + + tk.Label(self.scraped_data_frame, text="Datos Scrapeados:").pack(anchor="w") + + self.scraped_data_text = tk.Text(self.scraped_data_frame, height=10, state="disabled") + self.scraped_data_text.pack(fill="both", expand=True) + + def create_database(self): + """Crea la base de datos y la tabla para almacenar datos de scraping.""" + try: + connection = mysql.connector.connect( + host=self.host_entry.get(), + user=self.user_entry.get(), + password=self.password_entry.get() + ) + cursor = connection.cursor() + cursor.execute(f"CREATE DATABASE IF NOT EXISTS {self.database_entry.get()}") + connection.database = self.database_entry.get() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS scraped_data ( + id INT AUTO_INCREMENT PRIMARY KEY, + title TEXT NOT NULL, + link TEXT NOT NULL, + scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + connection.close() + messagebox.showinfo("Éxito", "Base de datos y tabla creadas correctamente.") + except mysql.connector.Error as e: + messagebox.showerror("Error", str(e)) + + def start_scraping(self): + """Inicia el scraping en un hilo separado.""" + if self.scraping_thread and self.scraping_thread.is_alive(): + messagebox.showwarning("Aviso", "El scraping ya está en ejecución.") + return + self.stop_event.clear() + self.scraping_thread = threading.Thread(target=self.scrape_data, daemon=True) + self.scraping_thread.start() + self.status_label.config(text="Estado: Ejecutando...", fg="green") + self.start_button.config(state="disabled") + self.stop_button.config(state="normal") + + def scrape_data(self): + """Realiza el scraping de manera continua y guarda los datos en la base de datos.""" + url = self.url_entry.get() + selector = self.selector_entry.get() + + try: + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } + 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() + + while not self.stop_event.is_set(): + response = requests.get(url, headers=headers) + soup = BeautifulSoup(response.text, "html.parser") + + # Busca elementos según el selector ingresado por el usuario + elements = soup.select(selector) + if not elements: + self.status_label.config(text="Estado: Sin datos encontrados.", fg="orange") + time.sleep(5) # Pausa antes de intentar de nuevo + continue + + for element in elements: + title_text = element.get_text(strip=True) + link = element.get("href", "Sin enlace") # Asegúrate de que el selector apunte a elementos + + # 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 0000000..288e701 Binary files /dev/null and b/solapas/__pycache__/EconomyBitcoinChart.cpython-313.pyc differ diff --git a/solapas/__pycache__/MusicDownloader.cpython-313.pyc b/solapas/__pycache__/MusicDownloader.cpython-313.pyc new file mode 100644 index 0000000..3d38836 Binary files /dev/null and b/solapas/__pycache__/MusicDownloader.cpython-313.pyc differ diff --git a/solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc b/solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc new file mode 100644 index 0000000..325e8d1 Binary files /dev/null and b/solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc differ diff --git a/solapas/__pycache__/TicTacToe.cpython-313.pyc b/solapas/__pycache__/TicTacToe.cpython-313.pyc new file mode 100644 index 0000000..ffb0e71 Binary files /dev/null and b/solapas/__pycache__/TicTacToe.cpython-313.pyc differ diff --git a/solapas/__pycache__/WebScraperToDB.cpython-313.pyc b/solapas/__pycache__/WebScraperToDB.cpython-313.pyc new file mode 100644 index 0000000..4c6d68d Binary files /dev/null and b/solapas/__pycache__/WebScraperToDB.cpython-313.pyc differ diff --git a/solapas/__pycache__/__init__.cpython-313.pyc b/solapas/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..c22c805 Binary files /dev/null and b/solapas/__pycache__/__init__.cpython-313.pyc differ