This commit is contained in:
Santi 2025-02-20 21:10:12 +01:00
parent 7ca109af88
commit a74d546a39
15 changed files with 591 additions and 315 deletions

View File

@ -1,58 +1,202 @@
import tkinter as tk import tkinter as tk
import threading import threading
import time
import datetime import datetime
from tkinter import Menu, ttk from tkinter import Menu # Importar el widget Menu
from tkinter import ttk # Importar el widget ttk
from correo_server.EmailTab import MailTab
from correo_server.InboxTab import InboxTab
from correo_server.MailClient import MailClient
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
# Importación de widgets personalizados
from hilos.ChatWidget import ChatWidget # Widget de chat en tiempo real # Crear instancia del cliente de correo con configuración de puertos
from hilos.MusicPlayer import MusicPlayer # Reproductor de música email_client = MailClient()
from hilos.WeatherWidget import WeatherWidget # Widget del clima con API de OpenWeather
from hilos.SystemMonitor import SystemMonitor # Monitor del sistema (CPU, RAM, etc.) # Clave de API de OpenWeatherMap
from hilos.ApplicationLauncher import ApplicationLauncher # Lanzador de aplicaciones API_KEY = "1fa8fd05b650773bbc3f2130657e808a"
from hilos.LanguageChart import LanguageChart # Gráfico de lenguajes de programación
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 # Crear la ventana principal
root = tk.Tk() root = tk.Tk()
root.title("Ventana Responsive") # Título de la ventana root.title("Ventana Responsive")
root.geometry("1000x700") # Definir el tamaño inicial de la ventana root.geometry("1000x700") # Tamaño inicial
# Crear el menú superior de la aplicación (actualmente vacío) # 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) 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) root.config(menu=menu_bar)
# Crear los frames (paneles) laterales y central # Crear los frames laterales y el central
frame_izquierdo = tk.Frame(root, bg="lightblue", width=150) # Panel lateral izquierdo frame_izquierdo = tk.Frame(root, bg="lightblue", width=150)
frame_central = tk.Frame(root, bg="white") # Panel central principal frame_central = tk.Frame(root, bg="white")
frame_derecho = tk.Frame(root, bg="lightgreen", width=150) # Panel lateral derecho frame_derecho = tk.Frame(root, bg="lightgreen", width=150)
# Ubicar los frames en la cuadrícula de la ventana # Colocar los frames laterales y el central
frame_izquierdo.grid(row=0, column=0, sticky="ns") # Se extiende en dirección norte-sur frame_izquierdo.grid(row=0, column=0, sticky="ns")
frame_central.grid(row=0, column=1, sticky="nsew") # Se expande en ambas direcciones frame_central.grid(row=0, column=1, sticky="nsew")
frame_derecho.grid(row=0, column=2, sticky="ns") # Se extiende en dirección norte-sur frame_derecho.grid(row=0, column=2, sticky="ns")
# Añadir widgets a los paneles # Configurar los tamaños fijos de los frames laterales
weather_widget = WeatherWidget(frame_izquierdo, "API_KEY") # Widget del clima en el panel izquierdo frame_izquierdo.grid_propagate(False)
app_launcher = ApplicationLauncher(frame_izquierdo) # Lanzador de aplicaciones en el panel izquierdo frame_derecho.grid_propagate(False)
language_chart = LanguageChart(frame_izquierdo) # Gráfico de lenguajes de programación en el panel izquierdo
chat_widget = ChatWidget(frame_derecho) # Chat en vivo en el panel derecho
music_player = MusicPlayer(frame_derecho) # Reproductor de música en el panel derecho
# Crear la barra de estado en la parte inferior de la ventana # Integrar el widget del clima en el panel izquierdo
barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w") weather_widget = WeatherWidget(frame_izquierdo, API_KEY)
barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") # Se extiende horizontalmente
# Inicializar el monitor del sistema para mostrar uso de CPU, RAM, etc. # Añadir el lanzador de aplicaciones al panel izquierdo
stop_event = threading.Event() # Evento para detener el monitor al cerrar la app app_launcher = ApplicationLauncher(frame_izquierdo)
system_monitor = SystemMonitor(barra_estado, stop_event) # Monitoriza el sistema y actualiza la barra de estado
# Función para manejar el cierre de la aplicación # 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(): def on_closing():
"""Se ejecuta al cerrar la ventana, deteniendo los procesos activos.""" """Cerrar correctamente la aplicación."""
stop_event.set() # Detiene el monitor del sistema stop_event.set() # Detener los hilos
root.destroy() # Cierra la ventana principal root.destroy() # Destruir la ventana principal
# Asignar la función de cierre cuando el usuario intenta cerrar la ventana # Configurar el manejador de cierre
root.protocol("WM_DELETE_WINDOW", on_closing) root.protocol("WM_DELETE_WINDOW", on_closing)
# Iniciar el bucle principal de la aplicación # 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="Downloader", 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="Graphics", 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="Tic Tac Toe", 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="SQL Querys", 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="Web Scraper", padding=4)
# Añadir el widget de Web Scraper a la Solapa 5
web_scraper = WebScraperToDB(tab5)
# Crear pestañas de correo
MailTab(notebook, email_client)
InboxTab(notebook, email_client)
# 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() root.mainloop()

View File

@ -4,20 +4,12 @@ import threading
class MailTab: class MailTab:
def __init__(self, parent, mail_client): def __init__(self, parent, mail_client):
"""
Inicializa la pestaña de envío de correos en la interfaz.
Args:
parent (tk.Notebook): Contenedor de pestañas.
mail_client (MailClient): Cliente de correo que gestiona el envío de emails.
"""
self.mail_client = mail_client self.mail_client = mail_client
# Crear un nuevo frame dentro del notebook
self.frame = ttk.Frame(parent) self.frame = ttk.Frame(parent)
parent.add(self.frame, text="Correo") parent.add(self.frame, text="Correo")
# Campos de entrada para el correo, contraseña y destinatario # Campos para ingresar credenciales del usuario
ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5) ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5)
self.entry_email = ttk.Entry(self.frame, width=40) self.entry_email = ttk.Entry(self.frame, width=40)
self.entry_email.grid(row=0, column=1, padx=5, pady=5) self.entry_email.grid(row=0, column=1, padx=5, pady=5)
@ -26,16 +18,17 @@ class MailTab:
self.entry_password = ttk.Entry(self.frame, show="*", width=40) self.entry_password = ttk.Entry(self.frame, show="*", width=40)
self.entry_password.grid(row=1, column=1, padx=5, pady=5) self.entry_password.grid(row=1, column=1, padx=5, pady=5)
# Campo para ingresar destinatario
ttk.Label(self.frame, text="Destinatario:").grid(row=2, column=0, sticky="e", padx=5, pady=5) ttk.Label(self.frame, text="Destinatario:").grid(row=2, column=0, sticky="e", padx=5, pady=5)
self.entry_recipient = ttk.Entry(self.frame, width=40) self.entry_recipient = ttk.Entry(self.frame, width=40)
self.entry_recipient.grid(row=2, column=1, padx=5, pady=5) self.entry_recipient.grid(row=2, column=1, padx=5, pady=5)
# Campo para el asunto del correo # Campo para ingresar asunto
ttk.Label(self.frame, text="Asunto:").grid(row=3, column=0, sticky="e", padx=5, pady=5) ttk.Label(self.frame, text="Asunto:").grid(row=3, column=0, sticky="e", padx=5, pady=5)
self.entry_subject = ttk.Entry(self.frame, width=40) self.entry_subject = ttk.Entry(self.frame, width=40)
self.entry_subject.grid(row=3, column=1, padx=5, pady=5) self.entry_subject.grid(row=3, column=1, padx=5, pady=5)
# Área de texto para el mensaje # Campo para escribir el mensaje
ttk.Label(self.frame, text="Mensaje:").grid(row=4, column=0, sticky="ne", padx=5, pady=5) ttk.Label(self.frame, text="Mensaje:").grid(row=4, column=0, sticky="ne", padx=5, pady=5)
self.text_body = tk.Text(self.frame, width=50, height=10) self.text_body = tk.Text(self.frame, width=50, height=10)
self.text_body.grid(row=4, column=1, padx=5, pady=5) self.text_body.grid(row=4, column=1, padx=5, pady=5)
@ -45,11 +38,11 @@ class MailTab:
self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky="e") self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky="e")
def send_email_thread(self): def send_email_thread(self):
"""Ejecuta el envío de correo en un hilo separado para evitar bloquear la interfaz.""" """Ejecuta el envío de correo en un hilo separado para no congelar la interfaz."""
threading.Thread(target=self.send_email).start() threading.Thread(target=self.send_email).start()
def send_email(self): def send_email(self):
"""Envía un correo usando el cliente de correos.""" """Envía el correo utilizando el cliente de correo."""
sender_email = self.entry_email.get() sender_email = self.entry_email.get()
sender_password = self.entry_password.get() sender_password = self.entry_password.get()
recipient = self.entry_recipient.get() recipient = self.entry_recipient.get()
@ -60,6 +53,5 @@ class MailTab:
messagebox.showerror("Error", "Todos los campos son obligatorios.") messagebox.showerror("Error", "Todos los campos son obligatorios.")
return return
# Llamada al cliente de correo para enviar el email
result = self.mail_client.send_email(sender_email, sender_password, recipient, subject, body) result = self.mail_client.send_email(sender_email, sender_password, recipient, subject, body)
messagebox.showinfo("Resultado", result) messagebox.showinfo("Resultado", result)

View File

@ -4,20 +4,11 @@ import threading
class InboxTab: class InboxTab:
def __init__(self, parent, email_client): def __init__(self, parent, email_client):
"""
Inicializa la pestaña de la bandeja de entrada.
Args:
parent (tk.Notebook): Contenedor de pestañas.
email_client (MailClient): Cliente de correo para gestionar la recepción y eliminación de correos.
"""
self.email_client = email_client self.email_client = email_client
# Crear un nuevo frame en la pestaña
self.frame = ttk.Frame(parent) self.frame = ttk.Frame(parent)
parent.add(self.frame, text="Bandeja de Entrada") parent.add(self.frame, text="Bandeja de Entrada")
# Campos de entrada para el correo y contraseña
ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5) ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5)
self.entry_email = ttk.Entry(self.frame, width=40) self.entry_email = ttk.Entry(self.frame, width=40)
self.entry_email.grid(row=0, column=1, padx=5, pady=5) self.entry_email.grid(row=0, column=1, padx=5, pady=5)
@ -26,31 +17,25 @@ class InboxTab:
self.entry_password = ttk.Entry(self.frame, show="*", width=40) self.entry_password = ttk.Entry(self.frame, show="*", width=40)
self.entry_password.grid(row=1, column=1, padx=5, pady=5) self.entry_password.grid(row=1, column=1, padx=5, pady=5)
# Botón para cargar correos
self.load_button = ttk.Button(self.frame, text="Cargar Correos", command=self.load_emails_thread) self.load_button = ttk.Button(self.frame, text="Cargar Correos", command=self.load_emails_thread)
self.load_button.grid(row=2, column=1, sticky="e", padx=5, pady=5) self.load_button.grid(row=2, column=1, sticky="e", padx=5, pady=5)
# Lista para mostrar los correos
self.email_listbox = tk.Listbox(self.frame, width=80, height=20) self.email_listbox = tk.Listbox(self.frame, width=80, height=20)
self.email_listbox.grid(row=3, column=0, columnspan=2, padx=5, pady=5) self.email_listbox.grid(row=3, column=0, columnspan=2, padx=5, pady=5)
self.email_listbox.bind("<Double-Button-1>", self.show_email) self.email_listbox.bind("<Double-Button-1>", self.show_email)
# Botón para eliminar un correo seleccionado
self.delete_button = ttk.Button(self.frame, text="Eliminar Correo", command=self.delete_email_thread) self.delete_button = ttk.Button(self.frame, text="Eliminar Correo", command=self.delete_email_thread)
self.delete_button.grid(row=4, column=1, sticky="e", padx=5, pady=5) self.delete_button.grid(row=4, column=1, sticky="e", padx=5, pady=5)
self.emails = [] self.emails = []
def load_emails_thread(self): def load_emails_thread(self):
"""Carga los correos en un hilo separado."""
threading.Thread(target=self.load_emails).start() threading.Thread(target=self.load_emails).start()
def delete_email_thread(self): def delete_email_thread(self):
"""Elimina un correo en un hilo separado."""
threading.Thread(target=self.delete_email).start() threading.Thread(target=self.delete_email).start()
def load_emails(self): def load_emails(self):
"""Obtiene la lista de correos desde el servidor y la muestra en la lista."""
email_address = self.entry_email.get() email_address = self.entry_email.get()
password = self.entry_password.get() password = self.entry_password.get()
@ -61,7 +46,6 @@ class InboxTab:
self.emails = [] self.emails = []
self.email_listbox.delete(0, tk.END) self.email_listbox.delete(0, tk.END)
# Obtener los correos usando el cliente de correos
emails = self.email_client.fetch_emails(email_address, password) emails = self.email_client.fetch_emails(email_address, password)
if isinstance(emails, str): if isinstance(emails, str):
@ -73,7 +57,6 @@ class InboxTab:
self.emails.append((email_id, subject, sender)) self.emails.append((email_id, subject, sender))
def delete_email(self): def delete_email(self):
"""Elimina un correo seleccionado en la lista."""
selected_index = self.email_listbox.curselection() selected_index = self.email_listbox.curselection()
if not selected_index: if not selected_index:
messagebox.showwarning("Advertencia", "Seleccione un correo para eliminar.") messagebox.showwarning("Advertencia", "Seleccione un correo para eliminar.")
@ -83,7 +66,6 @@ class InboxTab:
email_address = self.entry_email.get() email_address = self.entry_email.get()
password = self.entry_password.get() password = self.entry_password.get()
# Eliminar el correo usando el cliente
result = self.email_client.delete_email(email_address, password, email_id) result = self.email_client.delete_email(email_address, password, email_id)
if "eliminado" in result: if "eliminado" in result:
@ -93,7 +75,6 @@ class InboxTab:
messagebox.showerror("Error", result) messagebox.showerror("Error", result)
def show_email(self, event): def show_email(self, event):
"""Muestra el contenido del correo seleccionado."""
selected_index = self.email_listbox.curselection() selected_index = self.email_listbox.curselection()
if selected_index: if selected_index:
email_id, subject, sender = self.emails[selected_index[0]] email_id, subject, sender = self.emails[selected_index[0]]

View File

@ -6,16 +6,8 @@ from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.utils import formatdate from email.utils import formatdate
class MailClient: class MailClient:
def __init__(self): def __init__(self):
"""
Inicializa el cliente de correo con la configuración del servidor.
Atributos:
server_ip (str): Dirección del servidor de correo.
ports (dict): Diccionario con los puertos para SMTP e IMAP.
"""
self.server_ip = "s1.ieslamar.org" self.server_ip = "s1.ieslamar.org"
self.ports = { self.ports = {
"SMTP": 25, # SMTP sin seguridad "SMTP": 25, # SMTP sin seguridad
@ -24,15 +16,7 @@ class MailClient:
} }
def check_smtp_connection(self, port): def check_smtp_connection(self, port):
""" """Verifica si el servidor SMTP responde en el puerto especificado."""
Verifica si el servidor SMTP responde en un puerto específico.
Args:
port (int): Número del puerto a verificar.
Returns:
bool: `True` si la conexión es exitosa, `False` en caso contrario.
"""
try: try:
with socket.create_connection((self.server_ip, port), timeout=15): with socket.create_connection((self.server_ip, port), timeout=15):
return True return True
@ -40,21 +24,8 @@ class MailClient:
return False return False
def send_email(self, sender_email, sender_password, recipient, subject, body): def send_email(self, sender_email, sender_password, recipient, subject, body):
""" """Envía un correo utilizando SMTP en los puertos 587 o 25."""
Envía un correo utilizando el protocolo SMTP.
Args:
sender_email (str): Dirección de correo del remitente.
sender_password (str): Contraseña del remitente.
recipient (str): Dirección de correo del destinatario.
subject (str): Asunto del correo.
body (str): Cuerpo del correo.
Returns:
str: Mensaje indicando el resultado del envío.
"""
try: try:
# Crear el mensaje de correo
message = MIMEMultipart() message = MIMEMultipart()
message["From"] = sender_email message["From"] = sender_email
message["To"] = recipient message["To"] = recipient
@ -62,27 +33,23 @@ class MailClient:
message["Subject"] = subject message["Subject"] = subject
message.attach(MIMEText(body, "plain", "utf-8")) message.attach(MIMEText(body, "plain", "utf-8"))
# Lista de puertos disponibles para SMTP smtp_ports = [587, 25] # Intenta primero 587, luego 25
smtp_ports = [587, 25] # Prioriza 587 y luego 25
puerto_activo = None puerto_activo = None
# Verificar qué puerto está disponible
for smtp_port in smtp_ports: for smtp_port in smtp_ports:
if self.check_smtp_connection(smtp_port): if self.check_smtp_connection(smtp_port):
puerto_activo = smtp_port puerto_activo = smtp_port
break break # Si encuentra un puerto disponible, lo usa
if not puerto_activo: if not puerto_activo:
return "Error: No se pudo conectar con el servidor SMTP en los puertos (587, 25)." return "Error: No se pudo conectar con el servidor SMTP en los puertos (587, 25)."
try: try:
# Conectar al servidor SMTP
with smtplib.SMTP(self.server_ip, puerto_activo, timeout=15) as server: with smtplib.SMTP(self.server_ip, puerto_activo, timeout=15) as server:
if puerto_activo == 587: if puerto_activo == 587:
server.starttls() # Activar TLS si se usa el puerto 587 server.starttls() # Activa TLS si está en el puerto 587
server.login(sender_email, sender_password) # Autenticación server.login(sender_email, sender_password)
server.sendmail(sender_email, recipient, message.as_string()) # Enviar el correo server.sendmail(sender_email, recipient, message.as_string())
return f"Correo enviado correctamente a {recipient} usando el puerto {puerto_activo}" return f"Correo enviado correctamente a {recipient} usando el puerto {puerto_activo}"
except smtplib.SMTPAuthenticationError: except smtplib.SMTPAuthenticationError:
@ -96,76 +63,46 @@ class MailClient:
return f"Error inesperado al enviar el correo: {str(e)}" return f"Error inesperado al enviar el correo: {str(e)}"
def fetch_emails(self, email_address, password): def fetch_emails(self, email_address, password):
""" """Recibe correos utilizando IMAP sin SSL."""
Recupera los correos electrónicos de la bandeja de entrada usando IMAP.
Args:
email_address (str): Dirección de correo del usuario.
password (str): Contraseña del usuario.
Returns:
list: Lista de tuplas con los correos (`email_id`, `subject`, `sender`).
str: Mensaje de error si la autenticación falla.
"""
try: try:
# Conectar al servidor IMAP
mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"])
mail.login(email_address, password) mail.login(email_address, password)
mail.select("inbox") mail.select("inbox")
# Buscar todos los correos en la bandeja de entrada
status, messages = mail.search(None, "ALL") status, messages = mail.search(None, "ALL")
email_ids = messages[0].split() email_ids = messages[0].split()
emails = [] emails = []
# Obtener los últimos 10 correos for email_id in email_ids[-10:]: # Obtener los últimos 10 correos
for email_id in email_ids[-10:]:
status, msg_data = mail.fetch(email_id, "(RFC822)") status, msg_data = mail.fetch(email_id, "(RFC822)")
for response_part in msg_data: for response_part in msg_data:
if isinstance(response_part, tuple): if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1]) msg = email.message_from_bytes(response_part[1])
# Decodificar el asunto del correo
subject, encoding = email.header.decode_header(msg["Subject"])[0] subject, encoding = email.header.decode_header(msg["Subject"])[0]
if isinstance(subject, bytes): if isinstance(subject, bytes):
subject = subject.decode(encoding or "utf-8") subject = subject.decode(encoding or "utf-8")
sender = msg.get("From") sender = msg.get("From")
emails.append((email_id, subject, sender)) emails.append((email_id, subject, sender))
mail.logout() mail.logout()
return emails return emails
except imaplib.IMAP4.error: except imaplib.IMAP4.error:
return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña." return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña."
except Exception as e: except Exception as e:
return f"Error al recibir los correos: {str(e)}" return f"Error al recibir los correos: {str(e)}"
def delete_email(self, email_address, password, email_id): def delete_email(self, email_address, password, email_id):
""" """Elimina un correo utilizando IMAP sin SSL."""
Elimina un correo electrónico utilizando IMAP.
Args:
email_address (str): Dirección de correo del usuario.
password (str): Contraseña del usuario.
email_id (bytes): ID del correo a eliminar.
Returns:
str: Mensaje indicando si la eliminación fue exitosa o no.
"""
try: try:
# Conectar al servidor IMAP
mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"])
mail.login(email_address, password) mail.login(email_address, password)
mail.select("inbox") mail.select("inbox")
# Marcar el correo como eliminado mail.store(email_id, "+FLAGS", "\\Deleted") # Marca el correo como eliminado
mail.store(email_id, "+FLAGS", "\\Deleted") mail.expunge() # Borra permanentemente los correos marcados como eliminados
mail.expunge() # Eliminar definitivamente los correos marcados
mail.logout() mail.logout()
return f"Correo con ID {email_id} eliminado correctamente." return f"Correo con ID {email_id} eliminado correctamente."
except imaplib.IMAP4.error: except imaplib.IMAP4.error:
return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña." return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña."
except Exception as e: except Exception as e:

View File

@ -3,40 +3,93 @@ import threading
import subprocess import subprocess
import os import os
class ApplicationLauncher: class ApplicationLauncher:
def __init__(self, parent): def __init__(self, parent):
"""Crea botones para abrir aplicaciones como VS Code, Eclipse y PyCharm.""" """
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 self.parent = parent
# Detectar rutas de instalación # Detectar rutas automáticamente
self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe"]) self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe",
self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe"]) "C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe"])
self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.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"])
# Crear botones de lanzamiento # Título para el grupo de botones
self.create_button("Visual Code", self.launch_vscode) title = tk.Label(self.parent, text="Aplicaciones", font=("Helvetica", 14, "bold"), bg="lightblue")
self.create_button("Eclipse", self.launch_eclipse) title.pack(pady=10)
self.create_button("PyCharm", self.launch_pycharm)
# 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): def detect_path(self, paths):
"""Detecta la primera ruta válida de una lista de rutas posibles.""" """
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: for path in paths:
path = os.path.expandvars(path) # Expande variables como %USERNAME%
if os.path.exists(path): if os.path.exists(path):
return path return path
return None return None
def create_button(self, name, command):
"""Crea un botón con la función de lanzar una aplicación."""
button = tk.Button(self.parent, text=name, command=command, bg="lightgreen")
button.pack(fill="x", pady=2)
def launch_vscode(self): def launch_vscode(self):
"""Lanza Visual Studio Code.""" """Lanza Visual Studio Code si se encuentra la ruta."""
self.launch_application(self.vscode_path, "Visual Studio Code") 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): def launch_application(self, path, name):
"""Ejecuta la aplicación si la ruta es válida.""" """
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: if path:
threading.Thread(target=subprocess.run, args=([path],), daemon=True).start() threading.Thread(target=self.run_command, args=([path],), daemon=True).start()
else: else:
print(f"{name} no está instalado.") 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}")

View File

@ -3,55 +3,71 @@ from tkinter import scrolledtext
import socket import socket
import threading import threading
class ChatWidget: class ChatWidget:
def __init__(self, parent): def __init__(self, parent):
"""Widget de chat con conexión a un servidor por sockets."""
self.parent = parent self.parent = parent
self.frame = tk.Frame(self.parent, bg="lightgreen") self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=300) # Ajustar tamaño del frame
self.frame.pack(fill="x", padx=10, pady=10) self.frame.pack(fill="x", expand=False, padx=10, pady=10)
# Área de texto para mostrar mensajes # Label superior
self.chat_display = scrolledtext.ScrolledText(self.frame, wrap=tk.WORD, state="disabled", width=40, height=10) 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) self.chat_display.pack(pady=5)
# Campo de entrada de mensajes # Campo de entrada para escribir mensajes
self.message_entry = tk.Entry(self.frame, width=35) self.message_entry = tk.Entry(self.frame, width=35) # Reducir ancho
self.message_entry.pack(pady=5) self.message_entry.pack(pady=5)
self.message_entry.bind("<Return>", self.send_message) self.message_entry.bind("<Return>", self.send_message)
# Botón de enviar mensaje # Botón para enviar mensajes
self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message) self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message, width=10) # Reducir tamaño
self.send_button.pack(pady=5) self.send_button.pack(pady=5)
# Configuración del socket # Configuración del cliente socket
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_address = ("127.0.0.1", 3333) self.server_address = ("127.0.0.1", 3333) # Cambiar a la IP del servidor si es necesario
try: try:
self.client_socket.connect(self.server_address) self.client_socket.connect(self.server_address)
threading.Thread(target=self.receive_messages, daemon=True).start() threading.Thread(target=self.receive_messages, daemon=True).start()
except: except Exception as e:
self.display_message("[ERROR] No se pudo conectar al servidor.") self.display_message(f"[ERROR] No se pudo conectar al servidor: {e}")
def send_message(self, event=None): def send_message(self, event=None):
"""Envía un mensaje al servidor."""
message = self.message_entry.get() message = self.message_entry.get()
if message: if message:
try:
self.client_socket.send(message.encode("utf-8")) self.client_socket.send(message.encode("utf-8"))
self.message_entry.delete(0, tk.END) 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): def receive_messages(self):
"""Recibe mensajes del servidor y los muestra en la interfaz."""
while True: while True:
try: try:
message = self.client_socket.recv(1024).decode("utf-8") message = self.client_socket.recv(1024).decode("utf-8")
if message:
self.display_message(message) self.display_message(message)
else:
break
except: except:
self.display_message("[DESCONECTADO] Conexión perdida con el servidor.")
break break
def display_message(self, message): def display_message(self, message):
"""Muestra un mensaje en la ventana de chat."""
self.chat_display.config(state="normal") self.chat_display.config(state="normal")
self.chat_display.insert(tk.END, message + "\n") self.chat_display.insert(tk.END, message + "\n")
self.chat_display.config(state="disabled") self.chat_display.config(state="disabled")
self.chat_display.see(tk.END) self.chat_display.see(tk.END)
def close_connection(self):
try:
self.client_socket.close()
except:
pass

View File

@ -7,44 +7,45 @@ import time
class LanguageChart: class LanguageChart:
def __init__(self, parent): def __init__(self, parent):
""" """
Inicializa un gráfico de barras que muestra los lenguajes de programación más usados. Inicializa el gráfico de los lenguajes de programación más usados.
Args: Args:
parent (tk.Frame): Frame donde se colocará el gráfico. parent (tk.Frame): Frame donde se colocará el gráfico.
""" """
self.parent = parent self.parent = parent
# Datos iniciales de lenguajes más utilizados # Datos iniciales (puedes actualizar esto dinámicamente)
self.languages = ["Python", "JavaScript", "Java", "C++", "C#"] self.languages = ["Python", "JavaScript", "Java", "C++", "C#"]
self.usage = [30, 25, 20, 15, 10] # Porcentajes de uso self.usage = [30, 25, 20, 15, 10] # Porcentajes de uso
# Crear el gráfico de barras # Crear figura para el gráfico
self.figure = Figure(figsize=(4, 3), dpi=100) self.figure = Figure(figsize=(4, 3), dpi=100)
self.ax = self.figure.add_subplot(111) self.ax = self.figure.add_subplot(111)
self.ax.bar(self.languages, self.usage, color="skyblue") self.ax.bar(self.languages, self.usage, color="skyblue")
self.ax.set_title("Lenguajes más usados") self.ax.set_title("Lenguajes más usados")
self.ax.set_ylabel("Porcentaje de uso") self.ax.set_ylabel("Porcentaje de uso")
# Incrustar el gráfico en Tkinter # Embebiendo el gráfico en Tkinter
self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent) self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent)
self.canvas.get_tk_widget().pack(fill="both", expand=True) self.canvas.get_tk_widget().pack(fill="both", expand=True)
# Iniciar el hilo para actualizar el gráfico periódicamente # Iniciar hilo para actualizar el gráfico
threading.Thread(target=self.update_chart, daemon=True).start() threading.Thread(target=self.update_chart, daemon=True).start()
def fetch_data(self): def fetch_data(self):
""" """
Simula la actualización de datos sobre el uso de lenguajes de programación. Simula la obtención de datos actualizados de lenguajes de programación.
Returns: Returns:
list: Lista de nuevos porcentajes de uso. 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] self.usage = [value + 1 if value < 50 else value - 10 for value in self.usage]
time.sleep(5) # Simular un retraso en la actualización time.sleep(5) # Simular retraso de actualización
def update_chart(self): def update_chart(self):
""" """
Actualiza el gráfico de lenguajes de programación periódicamente en un hilo. Actualiza el gráfico periódicamente en un hilo.
""" """
while True: while True:
self.fetch_data() self.fetch_data()

View File

@ -4,90 +4,98 @@ from tkinter import filedialog
import threading import threading
import pygame import pygame
# Carpeta donde se almacenarán los archivos de música descargados # Definir la carpeta donde se guardarán los archivos descargados
DOWNLOAD_FOLDER = "musicplayer" DOWNLOAD_FOLDER = "musicplayer"
class MusicPlayer: class MusicPlayer:
def __init__(self, parent): def __init__(self, parent):
"""Inicializa el reproductor de música con botones de control y lista de canciones."""
self.parent = parent self.parent = parent
self.is_playing = False self.is_playing = False
# Inicializar el motor de audio # Inicializar el reproductor de música
pygame.mixer.init() pygame.mixer.init()
# Crear la interfaz del reproductor # Crear marco para el reproductor
self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=100) 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) self.frame.pack(side="bottom", padx=10, pady=10, fill="both", expand=False)
# Etiqueta de título # 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 = tk.Label(
self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen"
)
self.title_label.pack(pady=5) self.title_label.pack(pady=5)
# Lista de canciones descargadas # Lista de canciones descargadas
self.song_listbox = tk.Listbox(self.frame, width=50, height=10) self.song_listbox = tk.Listbox(self.frame, width=50, height=10)
self.song_listbox.pack(pady=5) self.song_listbox.pack(pady=5)
self.load_songs() # Cargar canciones almacenadas self.load_songs()
# Botón para seleccionar archivo manualmente # Crear un marco para los botones de control
self.select_button = tk.Button(self.frame, text="Seleccionar Archivo", command=self.select_file, width=20)
self.select_button.pack(pady=5)
# Contenedor de botones de control
self.controls_frame = tk.Frame(self.frame, bg="lightgreen") self.controls_frame = tk.Frame(self.frame, bg="lightgreen")
self.controls_frame.pack(pady=10) self.controls_frame.pack(pady=10)
# Botón de reproducción # Botón para seleccionar un archivo manualmente
self.play_button = tk.Button(self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12) self.select_button = tk.Button(
self.frame, text="Seleccionar Archivo", command=self.select_file, width=20
)
self.select_button.pack(pady=5)
# Botones de control
self.play_button = tk.Button(
self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12
)
self.play_button.grid(row=0, column=0, padx=5) self.play_button.grid(row=0, column=0, padx=5)
# Botón para detener la reproducción self.stop_button = tk.Button(
self.stop_button = tk.Button(self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12) self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12
)
self.stop_button.grid(row=0, column=1, padx=5) self.stop_button.grid(row=0, column=1, padx=5)
def load_songs(self): def load_songs(self):
"""Carga la lista de canciones almacenadas en la carpeta 'musicplayer/'.""" """Carga la lista de canciones descargadas en la carpeta 'musicplayer/'."""
if not os.path.exists(DOWNLOAD_FOLDER): if not os.path.exists(DOWNLOAD_FOLDER):
os.makedirs(DOWNLOAD_FOLDER) # Crear la carpeta si no existe os.makedirs(DOWNLOAD_FOLDER)
self.song_listbox.delete(0, tk.END) # Limpiar lista antes de actualizar self.song_listbox.delete(0, tk.END) # Limpiar lista antes de recargar
for file in os.listdir(DOWNLOAD_FOLDER): for file in os.listdir(DOWNLOAD_FOLDER):
if file.endswith(".mp3"): # Filtrar solo archivos MP3 if file.endswith(".mp3"):
self.song_listbox.insert(tk.END, file) self.song_listbox.insert(tk.END, file)
def select_file(self): def select_file(self):
"""Permite al usuario seleccionar un archivo de audio manualmente.""" """Abrir el selector de archivos para elegir un archivo de música manualmente."""
self.music_file = filedialog.askopenfilename(filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")]) self.music_file = filedialog.askopenfilename(
filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")]
)
if self.music_file: if self.music_file:
self.title_label.config(text=f"Archivo: {os.path.basename(self.music_file)}") # Mostrar el archivo seleccionado self.title_label.config(text=f"Archivo: {os.path.basename(self.music_file)}")
def play_selected_music(self): def play_selected_music(self):
"""Reproduce la canción seleccionada en la lista.""" """Reproducir la canción seleccionada desde la lista de descargas."""
selected_index = self.song_listbox.curselection() selected_index = self.song_listbox.curselection()
if not selected_index: if not selected_index:
return # Si no hay selección, no hacer nada return
selected_song = self.song_listbox.get(selected_index) selected_song = self.song_listbox.get(selected_index)
self.music_file = os.path.join(DOWNLOAD_FOLDER, selected_song) self.music_file = os.path.join(DOWNLOAD_FOLDER, selected_song)
self.is_playing = True self.is_playing = True
self.play_button.config(state="disabled") # Deshabilitar botón de reproducción self.play_button.config(state="disabled")
self.stop_button.config(state="normal") # Habilitar botón de detener self.stop_button.config(state="normal")
threading.Thread(target=self._play_music_thread, daemon=True).start() # Iniciar hilo para reproducir threading.Thread(target=self._play_music_thread, daemon=True).start()
def _play_music_thread(self): def _play_music_thread(self):
"""Maneja la reproducción de la música en un hilo separado.""" """Hilo que reproduce la música."""
pygame.mixer.music.load(self.music_file) pygame.mixer.music.load(self.music_file)
pygame.mixer.music.play() pygame.mixer.music.play()
while pygame.mixer.music.get_busy(): while pygame.mixer.music.get_busy():
if not self.is_playing: if not self.is_playing:
pygame.mixer.music.stop() pygame.mixer.music.stop()
break # Salir del bucle si la música se detiene break
def stop_music(self): def stop_music(self):
"""Detiene la reproducción de música.""" """Detener la reproducción de música."""
self.is_playing = False self.is_playing = False
self.play_button.config(state="normal") self.play_button.config(state="normal")
self.stop_button.config(state="disabled") self.stop_button.config(state="disabled")

View File

@ -4,30 +4,66 @@ import tkinter as tk
class SystemMonitor: class SystemMonitor:
def __init__(self, parent, stop_event): def __init__(self, parent, stop_event):
"""Muestra el uso del CPU, RAM y red en tiempo real."""
self.parent = parent self.parent = parent
self.stop_event = stop_event self.stop_event = stop_event
# Etiquetas para mostrar métricas # Crear labels para cada métrica
self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen") 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") self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral", font=("Helvetica", 12), relief="groove")
self.cpu_label.pack(side="left", expand=True) self.battery_label = tk.Label(parent, text="Battery: N/A", bg="lightblue", font=("Helvetica", 12), relief="groove")
self.ram_label.pack(side="left", expand=True) self.network_label = tk.Label(parent, text="Net: N/A", bg="lightpink", font=("Helvetica", 12), relief="groove")
# Hilos para actualizar los datos # 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_cpu, daemon=True).start()
threading.Thread(target=self.update_ram, 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): def update_cpu(self):
"""Actualiza la etiqueta del uso de CPU.""" """Actualizar el uso de CPU."""
while not self.stop_event.is_set(): while not self.stop_event.is_set():
cpu_usage = psutil.cpu_percent() cpu_usage = psutil.cpu_percent()
self.cpu_label.config(text=f"CPU: {cpu_usage}%") self.cpu_label.config(text=f"CPU: {cpu_usage}%")
self.cpu_label.after(1000, lambda: None) # Evitar bloqueo
self.stop_event.wait(1) self.stop_event.wait(1)
def update_ram(self): def update_ram(self):
"""Actualiza la etiqueta del uso de RAM.""" """Actualizar el uso de RAM."""
while not self.stop_event.is_set(): while not self.stop_event.is_set():
ram_usage = psutil.virtual_memory().percent ram_usage = psutil.virtual_memory().percent
self.ram_label.config(text=f"RAM: {ram_usage}%") 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) self.stop_event.wait(1)

View File

@ -3,45 +3,127 @@ import threading
import requests import requests
import time import time
class WeatherWidget: class WeatherWidget:
def __init__(self, parent, api_key): def __init__(self, parent, api_key):
""" """
Crea un widget para mostrar información meteorológica usando OpenWeatherMap. Inicializa el widget del clima con detalles adicionales.
Args: Args:
parent (tk.Frame): Frame donde se colocará el widget. parent (tk.Frame): Frame en el que se colocará el widget.
api_key (str): Clave de la API de OpenWeatherMap. api_key (str): Clave de la API de OpenWeatherMap.
""" """
self.parent = parent self.parent = parent
self.api_key = api_key self.api_key = api_key
# Configurar interfaz del widget # Crear un Frame para contener los datos
self.frame = tk.Frame(self.parent, bg="white", bd=2, relief="groove") self.frame = tk.Frame(self.parent, bg="white", bd=2, relief="groove")
self.frame.pack(padx=10, pady=10, fill="x", anchor="n") 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 = tk.Label(self.frame, text="Weather in ...", font=("Helvetica", 14, "bold"), bg="white")
self.header_label.pack(pady=5) 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 = tk.Label(self.frame, text="--°C", font=("Helvetica", 28, "bold"), bg="white")
self.temp_label.pack() self.temp_label.pack()
# Detalles adicionales
self.details_label = tk.Label(self.frame, text="", font=("Helvetica", 12), bg="white", justify="left") self.details_label = tk.Label(self.frame, text="", font=("Helvetica", 12), bg="white", justify="left")
self.details_label.pack(pady=5) self.details_label.pack(pady=5)
# Iniciar actualización automática del clima # Iniciar el hilo para actualizar el clima
self.start_weather_updates() 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): def get_weather(self, lat, lon):
""" """
Obtiene la temperatura, viento y calidad del aire desde OpenWeatherMap. 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: Args:
lat (float): Latitud. lat (float): Latitud.
lon (float): Longitud. lon (float): Longitud.
""" """
# Llamada a la API de OpenWeatherMap para obtener datos meteorológicos try:
pass 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): def start_weather_updates(self):
"""Inicia un hilo que actualiza la información meteorológica cada 60 segundos.""" """
threading.Thread(target=self.update_weather, daemon=True).start() Inicia el hilo para actualizar el clima.
"""
weather_thread = threading.Thread(target=self.update_weather, daemon=True)
weather_thread.start()

View File

@ -23,22 +23,21 @@ class EconomyBitcoinChart:
self.ax_bitcoin = self.figure.add_subplot(212) # Gráfico inferior self.ax_bitcoin = self.figure.add_subplot(212) # Gráfico inferior
# Inicializar datos simulados # Inicializar datos simulados
self.economy_data = [random.randint(50, 100) for _ in range(10)] # Índice económico en meses 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)] # Precio del Bitcoin en días self.bitcoin_data = [random.randint(20000, 60000) for _ in range(10)] # Bitcoin en días
# Dibujar los gráficos iniciales
self.update_economy_chart() self.update_economy_chart()
self.update_bitcoin_chart() self.update_bitcoin_chart()
# Embebiendo los gráficos en la interfaz de Tkinter # Embebiendo los gráficos en Tkinter
self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent) self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent)
self.canvas.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=10) self.canvas.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=10)
# Iniciar hilos para actualizar los gráficos periódicamente # Iniciar hilos para actualizar los gráficos
threading.Thread(target=self.update_charts, daemon=True).start() threading.Thread(target=self.update_charts, daemon=True).start()
def update_economy_chart(self): def update_economy_chart(self):
"""Actualiza el gráfico de economía mundial con nuevos datos.""" """Actualiza el gráfico de economía mundial."""
self.ax_economy.clear() self.ax_economy.clear()
self.ax_economy.plot(self.economy_data, marker="o", color="blue") self.ax_economy.plot(self.economy_data, marker="o", color="blue")
self.ax_economy.set_title("Economía Mundial") self.ax_economy.set_title("Economía Mundial")
@ -46,22 +45,22 @@ class EconomyBitcoinChart:
self.ax_economy.grid(True) self.ax_economy.grid(True)
def update_bitcoin_chart(self): def update_bitcoin_chart(self):
"""Actualiza el gráfico del precio de Bitcoin.""" """Actualiza el gráfico de Bitcoin."""
self.ax_bitcoin.clear() self.ax_bitcoin.clear()
self.ax_bitcoin.plot(self.bitcoin_data, marker="o", color="green") self.ax_bitcoin.plot(self.bitcoin_data, marker="o", color="green")
self.ax_bitcoin.set_title("Precio de Bitcoin") self.ax_bitcoin.set_title("Precio de Bitcoin")
self.ax_bitcoin.set_ylabel("Precio en USD") self.ax_bitcoin.set_ylabel("Precio en USD")
self.ax_bitcoin.set_xlabel("Días") # Etiqueta del eje X self.ax_bitcoin.set_xlabel("Días") # Etiqueta para los días
self.ax_bitcoin.grid(True) self.ax_bitcoin.grid(True)
def update_charts(self): def update_charts(self):
"""Actualiza ambos gráficos con nuevos datos periódicamente.""" """Actualiza ambos gráficos periódicamente."""
while True: while True:
# Generar nuevos datos simulados # Actualizar datos simulados
self.economy_data = self.economy_data[1:] + [random.randint(50, 100)] 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)] self.bitcoin_data = self.bitcoin_data[1:] + [random.randint(20000, 60000)] # Bitcoin en días
# Redibujar gráficos con los nuevos datos # Actualizar gráficos
self.update_economy_chart() self.update_economy_chart()
self.update_bitcoin_chart() self.update_bitcoin_chart()
self.canvas.draw() self.canvas.draw()

View File

@ -10,7 +10,7 @@ DOWNLOAD_FOLDER = "musicplayer"
class MusicDownloader: class MusicDownloader:
def __init__(self, parent): def __init__(self, parent):
""" """
Inicializa la interfaz gráfica para descargar música de YouTube en formato MP3. Inicializa la interfaz para descargar música de YouTube en MP3.
""" """
self.parent = parent self.parent = parent
@ -22,7 +22,7 @@ class MusicDownloader:
title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold")) title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold"))
title.pack(pady=10) title.pack(pady=10)
# Campo de entrada para la URL de YouTube # Entrada para la URL
self.url_label = tk.Label(self.parent, text="URL de YouTube:") self.url_label = tk.Label(self.parent, text="URL de YouTube:")
self.url_label.pack(pady=5) self.url_label.pack(pady=5)
self.url_entry = tk.Entry(self.parent, width=50) self.url_entry = tk.Entry(self.parent, width=50)
@ -32,16 +32,16 @@ class MusicDownloader:
self.download_button = tk.Button(self.parent, text="Descargar MP3", command=self.start_download, bg="lightblue") self.download_button = tk.Button(self.parent, text="Descargar MP3", command=self.start_download, bg="lightblue")
self.download_button.pack(pady=10) self.download_button.pack(pady=10)
# Barra de progreso de la descarga # Barra de progreso
self.progress = ttk.Progressbar(self.parent, orient="horizontal", length=300, mode="determinate") self.progress = ttk.Progressbar(self.parent, orient="horizontal", length=300, mode="determinate")
self.progress.pack(pady=10) self.progress.pack(pady=10)
# Etiqueta de estado para mostrar mensajes # Etiqueta de estado
self.status_label = tk.Label(self.parent, text="", font=("Helvetica", 10)) self.status_label = tk.Label(self.parent, text="", font=("Helvetica", 10))
self.status_label.pack(pady=5) self.status_label.pack(pady=5)
def start_download(self): def start_download(self):
"""Inicia la descarga en un hilo separado para no bloquear la interfaz.""" """Inicia la descarga en un hilo separado."""
url = self.url_entry.get() url = self.url_entry.get()
if not url: if not url:
self.status_label.config(text="Por favor, ingrese una URL válida.", fg="red") self.status_label.config(text="Por favor, ingrese una URL válida.", fg="red")
@ -50,12 +50,11 @@ class MusicDownloader:
threading.Thread(target=self.download_music, args=(url,), daemon=True).start() threading.Thread(target=self.download_music, args=(url,), daemon=True).start()
def download_music(self, url): def download_music(self, url):
"""Descarga el audio de YouTube en formato MP3 usando `yt-dlp`.""" """Descarga el audio de YouTube en MP3 usando yt-dlp."""
try: try:
self.status_label.config(text="Descargando...", fg="blue") self.status_label.config(text="Descargando...", fg="blue")
output_template = os.path.join(DOWNLOAD_FOLDER, "%(title)s.%(ext)s") output_template = os.path.join(DOWNLOAD_FOLDER, "%(title)s.%(ext)s")
# Opciones para descargar solo el audio y convertirlo a MP3
ydl_opts = { ydl_opts = {
'format': 'bestaudio/best', 'format': 'bestaudio/best',
'outtmpl': output_template, 'outtmpl': output_template,
@ -72,7 +71,6 @@ class MusicDownloader:
self.status_label.config(text="¡Descarga completada!", fg="green") self.status_label.config(text="¡Descarga completada!", fg="green")
except Exception as e: except Exception as e:
self.status_label.config(text=f"Error: {str(e)}", fg="red") self.status_label.config(text=f"Error: {str(e)}", fg="red")
def update_progress(self, stream, chunk, bytes_remaining): def update_progress(self, stream, chunk, bytes_remaining):
"""Actualiza la barra de progreso durante la descarga.""" """Actualiza la barra de progreso durante la descarga."""
total_size = stream.filesize total_size = stream.filesize

View File

@ -4,32 +4,33 @@ import threading
import mysql.connector import mysql.connector
from mysql.connector import Error from mysql.connector import Error
class SQLQueryExecutor: class SQLQueryExecutor:
def __init__(self, parent): def __init__(self, parent):
""" """
Clase que permite ejecutar consultas SQL en una base de datos MySQL. Clase para ejecutar consultas SQL en una base de datos MySQL.
Args: Args:
parent (tk.Frame): Frame donde se colocarán los widgets de la interfaz gráfica. parent (tk.Frame): Frame donde se colocarán los widgets.
""" """
self.parent = parent self.parent = parent
# Frame para ingresar los datos de conexión a la base de datos # Campos para ingresar información de conexión
self.db_info_frame = tk.Frame(self.parent) self.db_info_frame = tk.Frame(self.parent)
self.db_info_frame.pack(pady=10, padx=10, fill="x") 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") 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 = tk.Entry(self.db_info_frame)
self.host_entry.insert(0, "localhost") # Valor por defecto self.host_entry.insert(0, "localhost")
self.host_entry.grid(row=0, column=1) self.host_entry.grid(row=0, column=1)
tk.Label(self.db_info_frame, text="Usuario:").grid(row=1, column=0, sticky="w") 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 = tk.Entry(self.db_info_frame)
self.user_entry.insert(0, "root") # Usuario por defecto self.user_entry.insert(0, "root")
self.user_entry.grid(row=1, column=1) self.user_entry.grid(row=1, column=1)
tk.Label(self.db_info_frame, text="Contraseña:").grid(row=2, column=0, sticky="w") 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="*") # Campo oculto self.password_entry = tk.Entry(self.db_info_frame, show="*")
self.password_entry.grid(row=2, column=1) 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") tk.Label(self.db_info_frame, text="Base de datos:").grid(row=3, column=0, sticky="w")
@ -40,7 +41,7 @@ class SQLQueryExecutor:
self.connect_button = tk.Button(self.db_info_frame, text="Conectar", command=self.connect_to_database) 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) self.connect_button.grid(row=4, column=0, columnspan=2, pady=5)
# Área para escribir la consulta SQL # Área para ingresar consultas SQL
self.query_frame = tk.Frame(self.parent) self.query_frame = tk.Frame(self.parent)
self.query_frame.pack(pady=10, padx=10, fill="both", expand=True) self.query_frame.pack(pady=10, padx=10, fill="both", expand=True)
@ -48,11 +49,11 @@ class SQLQueryExecutor:
self.query_text = tk.Text(self.query_frame, height=10) self.query_text = tk.Text(self.query_frame, height=10)
self.query_text.pack(fill="both", expand=True) self.query_text.pack(fill="both", expand=True)
# Botón para ejecutar la consulta # Botón para ejecutar consultas
self.execute_button = tk.Button(self.query_frame, text="Ejecutar", command=self.execute_query) self.execute_button = tk.Button(self.query_frame, text="Ejecutar", command=self.execute_query)
self.execute_button.pack(pady=5) self.execute_button.pack(pady=5)
# Área para mostrar los resultados # Área para mostrar resultados
self.result_frame = tk.Frame(self.parent) self.result_frame = tk.Frame(self.parent)
self.result_frame.pack(pady=10, padx=10, fill="both", expand=True) self.result_frame.pack(pady=10, padx=10, fill="both", expand=True)
@ -61,7 +62,7 @@ class SQLQueryExecutor:
self.result_text.pack(fill="both", expand=True) self.result_text.pack(fill="both", expand=True)
def connect_to_database(self): def connect_to_database(self):
"""Conecta a la base de datos MySQL utilizando los datos ingresados por el usuario.""" """Conecta a la base de datos utilizando los datos proporcionados."""
self.host = self.host_entry.get() self.host = self.host_entry.get()
self.user = self.user_entry.get() self.user = self.user_entry.get()
self.password = self.password_entry.get() self.password = self.password_entry.get()
@ -80,7 +81,7 @@ class SQLQueryExecutor:
messagebox.showerror("Error de Conexión", str(e)) messagebox.showerror("Error de Conexión", str(e))
def execute_query(self): def execute_query(self):
"""Ejecuta la consulta SQL en un hilo separado para evitar bloquear la interfaz.""" """Ejecuta la consulta SQL en un hilo separado."""
query = self.query_text.get("1.0", tk.END).strip() query = self.query_text.get("1.0", tk.END).strip()
if not query: if not query:
messagebox.showwarning("Consulta Vacía", "Por favor, ingrese una consulta SQL.") messagebox.showwarning("Consulta Vacía", "Por favor, ingrese una consulta SQL.")
@ -89,7 +90,7 @@ class SQLQueryExecutor:
threading.Thread(target=self.run_query, args=(query,), daemon=True).start() threading.Thread(target=self.run_query, args=(query,), daemon=True).start()
def run_query(self, query): def run_query(self, query):
"""Ejecuta la consulta en la base de datos y muestra los resultados.""" """Ejecuta la consulta y muestra los resultados."""
try: try:
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute(query) cursor.execute(query)

View File

@ -4,6 +4,7 @@ from tkinter import messagebox
import threading import threading
import random import random
class TicTacToe: class TicTacToe:
def __init__(self, parent): def __init__(self, parent):
""" """
@ -13,19 +14,19 @@ class TicTacToe:
parent (tk.Frame): Frame donde se colocará el juego. parent (tk.Frame): Frame donde se colocará el juego.
""" """
self.parent = parent self.parent = parent
self.board = [""] * 9 # Representación del tablero 3x3 self.board = [""] * 9 # Tablero de 3x3 representado como una lista
self.current_player = "X" # Jugador que inicia la partida self.current_player = "X" # Jugador inicial
self.vs_computer = False # Modo jugador vs máquina self.vs_computer = False # Modo jugador vs máquina
# Título del juego # Etiqueta para el título
title = tk.Label(self.parent, text="Tic Tac Toe", font=("Helvetica", 16, "bold")) title = tk.Label(self.parent, text="Tic Tac Toe", font=("Helvetica", 16, "bold"))
title.pack(pady=10) title.pack(pady=10)
# Botón para alternar entre modos de juego # 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 = tk.Button(self.parent, text="Modo: Jugador vs Jugador", command=self.toggle_mode)
self.mode_button.pack(pady=5) self.mode_button.pack(pady=5)
# Crear la cuadrícula del tablero # Crear el tablero
self.buttons = [] self.buttons = []
self.board_frame = tk.Frame(self.parent) self.board_frame = tk.Frame(self.parent)
self.board_frame.pack() self.board_frame.pack()
@ -37,12 +38,12 @@ class TicTacToe:
font=("Helvetica", 20), font=("Helvetica", 20),
width=5, width=5,
height=2, height=2,
command=self.create_button_command(i) command=self.create_button_command(i) # Aquí usamos la función auxiliar
) )
button.grid(row=i // 3, column=i % 3) button.grid(row=i // 3, column=i % 3)
self.buttons.append(button) self.buttons.append(button)
# Mensaje que indica el turno del jugador # Etiqueta para el estado del juego
self.status_label = tk.Label(self.parent, text="Turno: X", font=("Helvetica", 12)) self.status_label = tk.Label(self.parent, text="Turno: X", font=("Helvetica", 12))
self.status_label.pack(pady=5) self.status_label.pack(pady=5)
@ -54,7 +55,7 @@ class TicTacToe:
self.reset_game() self.reset_game()
def reset_game(self): def reset_game(self):
"""Reinicia el juego a su estado inicial.""" """Reinicia el tablero y el estado del juego."""
self.board = [""] * 9 self.board = [""] * 9
self.current_player = "X" self.current_player = "X"
for button in self.buttons: for button in self.buttons:
@ -62,11 +63,12 @@ class TicTacToe:
self.status_label.config(text="Turno: X") self.status_label.config(text="Turno: X")
def make_move(self, index): def make_move(self, index):
"""Registra un movimiento y actualiza el tablero.""" """Realiza un movimiento en el tablero."""
if self.board[index] == "": if self.board[index] == "":
self.board[index] = self.current_player self.board[index] = self.current_player
self.buttons[index].config(text=self.current_player) self.buttons[index].config(text=self.current_player)
# Verificar si hay un ganador
winner = self.check_winner() winner = self.check_winner()
if winner: if winner:
self.end_game(f"¡Ganador: {winner}!") self.end_game(f"¡Ganador: {winner}!")
@ -75,11 +77,28 @@ class TicTacToe:
self.end_game("¡Empate!") self.end_game("¡Empate!")
return return
# Cambiar de jugador
self.current_player = "O" if self.current_player == "X" else "X" self.current_player = "O" if self.current_player == "X" else "X"
self.status_label.config(text=f"Turno: {self.current_player}") 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): def check_winner(self):
"""Verifica si hay un ganador en el juego.""" """Verifica si hay un ganador."""
winning_combinations = [ winning_combinations = [
(0, 1, 2), (3, 4, 5), (6, 7, 8), # Filas (0, 1, 2), (3, 4, 5), (6, 7, 8), # Filas
(0, 3, 6), (1, 4, 7), (2, 5, 8), # Columnas (0, 3, 6), (1, 4, 7), (2, 5, 8), # Columnas
@ -89,3 +108,16 @@ class TicTacToe:
if self.board[a] == self.board[b] == self.board[c] and self.board[a] != "": if self.board[a] == self.board[b] == self.board[c] and self.board[a] != "":
return self.board[a] return self.board[a]
return None 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

View File

@ -9,59 +9,52 @@ from tkinter import messagebox
class WebScraperToDB: class WebScraperToDB:
def __init__(self, parent): def __init__(self, parent):
""" """
Inicializa la interfaz gráfica para scraping web con integración a base de datos. Inicializa el widget de scraping con integración a base de datos.
Args:
parent (tk.Frame): Contenedor en el que se mostrará la interfaz gráfica.
""" """
self.parent = parent self.parent = parent
self.scraping_thread = None # Hilo que ejecutará el scraping self.scraping_thread = None
self.stop_event = threading.Event() # Evento para detener el scraping self.stop_event = threading.Event()
# Crear la sección de conexión a la base de datos # Crear campos de conexión para la base de datos
db_frame = tk.Frame(self.parent) db_frame = tk.Frame(self.parent)
db_frame.pack(pady=5) db_frame.pack(pady=5)
# Campos de entrada para la conexión a MySQL
tk.Label(db_frame, text="Host:").grid(row=0, column=0) tk.Label(db_frame, text="Host:").grid(row=0, column=0)
self.host_entry = tk.Entry(db_frame) self.host_entry = tk.Entry(db_frame)
self.host_entry.insert(0, "localhost") # Valor predeterminado self.host_entry.insert(0, "localhost")
self.host_entry.grid(row=0, column=1) self.host_entry.grid(row=0, column=1)
tk.Label(db_frame, text="Usuario:").grid(row=1, column=0) tk.Label(db_frame, text="Usuario:").grid(row=1, column=0)
self.user_entry = tk.Entry(db_frame) self.user_entry = tk.Entry(db_frame)
self.user_entry.insert(0, "root") # Usuario predeterminado self.user_entry.insert(0, "root")
self.user_entry.grid(row=1, column=1) self.user_entry.grid(row=1, column=1)
tk.Label(db_frame, text="Contraseña:").grid(row=2, column=0) tk.Label(db_frame, text="Contraseña:").grid(row=2, column=0)
self.password_entry = tk.Entry(db_frame, show="*") # Campo oculto para seguridad self.password_entry = tk.Entry(db_frame, show="*")
self.password_entry.grid(row=2, column=1) self.password_entry.grid(row=2, column=1)
tk.Label(db_frame, text="Nombre BD:").grid(row=3, column=0) tk.Label(db_frame, text="Nombre BD:").grid(row=3, column=0)
self.database_entry = tk.Entry(db_frame) self.database_entry = tk.Entry(db_frame)
self.database_entry.insert(0, "scraping_db") # Base de datos predeterminada self.database_entry.insert(0, "scraping_db")
self.database_entry.grid(row=3, column=1) self.database_entry.grid(row=3, column=1)
# Botón para crear la base de datos
tk.Button(db_frame, text="Crear Base de Datos", command=self.create_database).grid(row=4, column=0, columnspan=2, pady=5) tk.Button(db_frame, text="Crear Base de Datos", command=self.create_database).grid(row=4, column=0, columnspan=2, pady=5)
# Sección de controles para scraping # Área para URL y botones de control
control_frame = tk.Frame(self.parent) control_frame = tk.Frame(self.parent)
control_frame.pack(pady=5) control_frame.pack(pady=5)
# Campo de entrada para la URL a scrape
tk.Label(control_frame, text="URL para Scraping:").grid(row=0, column=0) 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 = tk.Entry(control_frame, width=50)
self.url_entry.insert(0, "https://quotes.toscrape.com/") # URL de prueba self.url_entry.insert(0, "https://quotes.toscrape.com/")
self.url_entry.grid(row=0, column=1) self.url_entry.grid(row=0, column=1)
# Campo para ingresar el selector HTML # Campo para Selector HTML
tk.Label(control_frame, text="Selector HTML:").grid(row=2, column=0) tk.Label(control_frame, text="Selector HTML:").grid(row=2, column=0)
self.selector_entry = tk.Entry(control_frame, width=50) self.selector_entry = tk.Entry(control_frame, width=50)
self.selector_entry.insert(0, "h1") # Selector predeterminado self.selector_entry.insert(0, "h1") # Valor predeterminado
self.selector_entry.grid(row=2, column=1) self.selector_entry.grid(row=2, column=1)
# Botones de control
self.start_button = tk.Button(control_frame, text="Iniciar Scraping", command=self.start_scraping) 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.start_button.grid(row=1, column=0, pady=5)
@ -71,7 +64,7 @@ class WebScraperToDB:
self.reset_button = tk.Button(control_frame, text="Resetear Scraping", command=self.reset_database) self.reset_button = tk.Button(control_frame, text="Resetear Scraping", command=self.reset_database)
self.reset_button.grid(row=1, column=2, pady=5) self.reset_button.grid(row=1, column=2, pady=5)
# Etiqueta para mostrar el estado del scraping # Área para mostrar el estado
self.status_label = tk.Label(self.parent, text="Estado: Inactivo", fg="red") self.status_label = tk.Label(self.parent, text="Estado: Inactivo", fg="red")
self.status_label.pack(pady=5) self.status_label.pack(pady=5)
@ -85,7 +78,7 @@ class WebScraperToDB:
self.scraped_data_text.pack(fill="both", expand=True) self.scraped_data_text.pack(fill="both", expand=True)
def create_database(self): def create_database(self):
"""Crea la base de datos y la tabla para almacenar los datos de scraping.""" """Crea la base de datos y la tabla para almacenar datos de scraping."""
try: try:
connection = mysql.connector.connect( connection = mysql.connector.connect(
host=self.host_entry.get(), host=self.host_entry.get(),
@ -126,7 +119,9 @@ class WebScraperToDB:
selector = self.selector_entry.get() selector = self.selector_entry.get()
try: try:
headers = {"User-Agent": "Mozilla/5.0"} 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( connection = mysql.connector.connect(
host=self.host_entry.get(), host=self.host_entry.get(),
user=self.user_entry.get(), user=self.user_entry.get(),
@ -139,7 +134,7 @@ class WebScraperToDB:
response = requests.get(url, headers=headers) response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")
# Busca elementos en la página usando el selector proporcionado # Busca elementos según el selector ingresado por el usuario
elements = soup.select(selector) elements = soup.select(selector)
if not elements: if not elements:
self.status_label.config(text="Estado: Sin datos encontrados.", fg="orange") self.status_label.config(text="Estado: Sin datos encontrados.", fg="orange")
@ -148,9 +143,9 @@ class WebScraperToDB:
for element in elements: for element in elements:
title_text = element.get_text(strip=True) title_text = element.get_text(strip=True)
link = element.get("href", "Sin enlace") # Extrae el enlace si está disponible link = element.get("href", "Sin enlace") # Asegúrate de que el selector apunte a elementos <a>
# Insertar datos en la base de datos # Insertar en la base de datos
cursor.execute("INSERT INTO scraped_data (title, link) VALUES (%s, %s)", (title_text, link)) cursor.execute("INSERT INTO scraped_data (title, link) VALUES (%s, %s)", (title_text, link))
connection.commit() connection.commit()
@ -162,7 +157,8 @@ class WebScraperToDB:
self.status_label.config(text=f"Estado: Scrapeando {title_text}...", fg="green") self.status_label.config(text=f"Estado: Scrapeando {title_text}...", fg="green")
time.sleep(5) # Pausa entre iteraciones # Pausa entre iteraciones
time.sleep(5)
connection.close() connection.close()
self.status_label.config(text="Estado: Inactivo", fg="red") self.status_label.config(text="Estado: Inactivo", fg="red")
@ -177,7 +173,7 @@ class WebScraperToDB:
self.stop_button.config(state="disabled") self.stop_button.config(state="disabled")
def reset_database(self): def reset_database(self):
"""Elimina todos los datos de la tabla scraped_data.""" """Elimina todos los datos de la tabla."""
try: try:
connection = mysql.connector.connect( connection = mysql.connector.connect(
host=self.host_entry.get(), host=self.host_entry.get(),