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..ec63674
--- /dev/null
+++ b/.idea/ProyectoFinalProcesosServicios.iml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ 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..7a7809b
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..e69de29
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..42be5e5
--- /dev/null
+++ b/hilos/ChatWidget.py
@@ -0,0 +1,73 @@
+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):
+ while True:
+ try:
+ message = self.client_socket.recv(1024).decode("utf-8")
+ if message:
+ self.display_message(message)
+ 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
index e66e489..b36fd47 100644
Binary files a/hilos/__pycache__/ApplicationLauncher.cpython-313.pyc 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
index 9c80d7b..8359f65 100644
Binary files a/hilos/__pycache__/ChatWidget.cpython-313.pyc 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
index e488e86..aebcca1 100644
Binary files a/hilos/__pycache__/LanguageChart.cpython-313.pyc 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
index da9147f..2b857d9 100644
Binary files a/hilos/__pycache__/MusicPlayer.cpython-313.pyc 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
index dbac2fa..624b14f 100644
Binary files a/hilos/__pycache__/SystemMonitor.cpython-313.pyc 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
index 63f0e46..006bc77 100644
Binary files a/hilos/__pycache__/WeatherWidget.cpython-313.pyc 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
index f9d496c..d05b605 100644
Binary files a/hilos/__pycache__/__init__.cpython-313.pyc 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
index 288e701..20afe8f 100644
Binary files a/solapas/__pycache__/EconomyBitcoinChart.cpython-313.pyc 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
index 3d38836..94a3e0d 100644
Binary files a/solapas/__pycache__/MusicDownloader.cpython-313.pyc 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
index 325e8d1..b836030 100644
Binary files a/solapas/__pycache__/SQLQueryExecutor.cpython-313.pyc 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
index ffb0e71..22a4a71 100644
Binary files a/solapas/__pycache__/TicTacToe.cpython-313.pyc 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
index 4c6d68d..5a09aa7 100644
Binary files a/solapas/__pycache__/WebScraperToDB.cpython-313.pyc 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
index c22c805..32de69d 100644
Binary files a/solapas/__pycache__/__init__.cpython-313.pyc and b/solapas/__pycache__/__init__.cpython-313.pyc differ