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

This commit is contained in:
Santi 2025-01-29 16:35:16 +01:00
commit 1d4908840f
34 changed files with 1307 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (ProyectoFinalProcesosServicios)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (ProyectoFinalProcesosServicios)" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ProyectoFinalProcesosServicios.iml" filepath="$PROJECT_DIR$/.idea/ProyectoFinalProcesosServicios.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

0
app/__init__.py Normal file
View File

194
app/main.py Normal file
View File

@ -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()

View File

@ -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}")

74
hilos/ChatWidget.py Normal file
View File

@ -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("<Return>", 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

57
hilos/LanguageChart.py Normal file
View File

@ -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

77
hilos/MusicPlayer.py Normal file
View File

@ -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()

69
hilos/SystemMonitor.py Normal file
View File

@ -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)

129
hilos/WeatherWidget.py Normal file
View File

@ -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()

0
hilos/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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)

View File

@ -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

113
solapas/SQLQueryExecutor.py Normal file
View File

@ -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))

123
solapas/TicTacToe.py Normal file
View File

@ -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

195
solapas/WebScraperToDB.py Normal file
View File

@ -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 <a>
# 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))

0
solapas/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.