diff --git a/app/main.py b/app/main.py index 33f9dae..d915686 100644 --- a/app/main.py +++ b/app/main.py @@ -1,58 +1,202 @@ import tkinter as tk import threading +import time 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 -from hilos.MusicPlayer import MusicPlayer # Reproductor de música -from hilos.WeatherWidget import WeatherWidget # Widget del clima con API de OpenWeather -from hilos.SystemMonitor import SystemMonitor # Monitor del sistema (CPU, RAM, etc.) -from hilos.ApplicationLauncher import ApplicationLauncher # Lanzador de aplicaciones -from hilos.LanguageChart import LanguageChart # Gráfico de lenguajes de programación + +# Crear instancia del cliente de correo con configuración de puertos +email_client = MailClient() + +# 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") # Título de la ventana -root.geometry("1000x700") # Definir el tamaño inicial de la ventana +root.title("Ventana Responsive") +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) + +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 (paneles) laterales y central -frame_izquierdo = tk.Frame(root, bg="lightblue", width=150) # Panel lateral izquierdo -frame_central = tk.Frame(root, bg="white") # Panel central principal -frame_derecho = tk.Frame(root, bg="lightgreen", width=150) # Panel lateral derecho +# 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) -# Ubicar los frames en la cuadrícula de la ventana -frame_izquierdo.grid(row=0, column=0, sticky="ns") # Se extiende en dirección norte-sur -frame_central.grid(row=0, column=1, sticky="nsew") # Se expande en ambas direcciones -frame_derecho.grid(row=0, column=2, sticky="ns") # Se extiende en dirección norte-sur +# 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") -# Añadir widgets a los paneles -weather_widget = WeatherWidget(frame_izquierdo, "API_KEY") # Widget del clima en el panel izquierdo -app_launcher = ApplicationLauncher(frame_izquierdo) # Lanzador de aplicaciones en el panel izquierdo -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 +# Configurar los tamaños fijos de los frames laterales +frame_izquierdo.grid_propagate(False) +frame_derecho.grid_propagate(False) -# Crear la barra de estado en la parte inferior de la ventana -barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w") -barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") # Se extiende horizontalmente +# Integrar el widget del clima en el panel izquierdo +weather_widget = WeatherWidget(frame_izquierdo, API_KEY) -# Inicializar el monitor del sistema para mostrar uso de CPU, RAM, etc. -stop_event = threading.Event() # Evento para detener el monitor al cerrar la app -system_monitor = SystemMonitor(barra_estado, stop_event) # Monitoriza el sistema y actualiza la barra de estado +# Añadir el lanzador de aplicaciones al panel izquierdo +app_launcher = ApplicationLauncher(frame_izquierdo) -# 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(): - """Se ejecuta al cerrar la ventana, deteniendo los procesos activos.""" - stop_event.set() # Detiene el monitor del sistema - root.destroy() # Cierra la ventana principal + """Cerrar correctamente la aplicación.""" + stop_event.set() # Detener los hilos + 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) -# Iniciar el bucle principal de la aplicación -root.mainloop() +# 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() \ No newline at end of file diff --git a/correo_server/EmailTab.py b/correo_server/EmailTab.py index 3d86518..4fa1644 100644 --- a/correo_server/EmailTab.py +++ b/correo_server/EmailTab.py @@ -4,20 +4,12 @@ import threading class MailTab: 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 - # Crear un nuevo frame dentro del notebook self.frame = ttk.Frame(parent) 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) self.entry_email = ttk.Entry(self.frame, width=40) 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.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) self.entry_recipient = ttk.Entry(self.frame, width=40) 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) self.entry_subject = ttk.Entry(self.frame, width=40) 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) self.text_body = tk.Text(self.frame, width=50, height=10) 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") 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() 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_password = self.entry_password.get() recipient = self.entry_recipient.get() @@ -60,6 +53,5 @@ class MailTab: messagebox.showerror("Error", "Todos los campos son obligatorios.") return - # Llamada al cliente de correo para enviar el email result = self.mail_client.send_email(sender_email, sender_password, recipient, subject, body) - messagebox.showinfo("Resultado", result) + messagebox.showinfo("Resultado", result) \ No newline at end of file diff --git a/correo_server/InboxTab.py b/correo_server/InboxTab.py index cd04b70..f66806f 100644 --- a/correo_server/InboxTab.py +++ b/correo_server/InboxTab.py @@ -4,20 +4,11 @@ import threading class InboxTab: 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 - # Crear un nuevo frame en la pestaña self.frame = ttk.Frame(parent) 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) self.entry_email = ttk.Entry(self.frame, width=40) 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.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.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.grid(row=3, column=0, columnspan=2, padx=5, pady=5) self.email_listbox.bind("", 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.grid(row=4, column=1, sticky="e", padx=5, pady=5) self.emails = [] def load_emails_thread(self): - """Carga los correos en un hilo separado.""" threading.Thread(target=self.load_emails).start() def delete_email_thread(self): - """Elimina un correo en un hilo separado.""" threading.Thread(target=self.delete_email).start() def load_emails(self): - """Obtiene la lista de correos desde el servidor y la muestra en la lista.""" email_address = self.entry_email.get() password = self.entry_password.get() @@ -61,7 +46,6 @@ class InboxTab: self.emails = [] self.email_listbox.delete(0, tk.END) - # Obtener los correos usando el cliente de correos emails = self.email_client.fetch_emails(email_address, password) if isinstance(emails, str): @@ -73,7 +57,6 @@ class InboxTab: self.emails.append((email_id, subject, sender)) def delete_email(self): - """Elimina un correo seleccionado en la lista.""" selected_index = self.email_listbox.curselection() if not selected_index: messagebox.showwarning("Advertencia", "Seleccione un correo para eliminar.") @@ -83,7 +66,6 @@ class InboxTab: email_address = self.entry_email.get() password = self.entry_password.get() - # Eliminar el correo usando el cliente result = self.email_client.delete_email(email_address, password, email_id) if "eliminado" in result: @@ -93,7 +75,6 @@ class InboxTab: messagebox.showerror("Error", result) def show_email(self, event): - """Muestra el contenido del correo seleccionado.""" selected_index = self.email_listbox.curselection() if selected_index: email_id, subject, sender = self.emails[selected_index[0]] diff --git a/correo_server/MailClient.py b/correo_server/MailClient.py index 8258d06..fa9213e 100644 --- a/correo_server/MailClient.py +++ b/correo_server/MailClient.py @@ -6,16 +6,8 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.utils import formatdate - class MailClient: 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.ports = { "SMTP": 25, # SMTP sin seguridad @@ -24,15 +16,7 @@ class MailClient: } def check_smtp_connection(self, port): - """ - 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. - """ + """Verifica si el servidor SMTP responde en el puerto especificado.""" try: with socket.create_connection((self.server_ip, port), timeout=15): return True @@ -40,21 +24,8 @@ class MailClient: return False def send_email(self, sender_email, sender_password, recipient, subject, body): - """ - 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. - """ + """Envía un correo utilizando SMTP en los puertos 587 o 25.""" try: - # Crear el mensaje de correo message = MIMEMultipart() message["From"] = sender_email message["To"] = recipient @@ -62,27 +33,23 @@ class MailClient: message["Subject"] = subject message.attach(MIMEText(body, "plain", "utf-8")) - # Lista de puertos disponibles para SMTP - smtp_ports = [587, 25] # Prioriza 587 y luego 25 + smtp_ports = [587, 25] # Intenta primero 587, luego 25 puerto_activo = None - # Verificar qué puerto está disponible for smtp_port in smtp_ports: if self.check_smtp_connection(smtp_port): puerto_activo = smtp_port - break + break # Si encuentra un puerto disponible, lo usa if not puerto_activo: return "Error: No se pudo conectar con el servidor SMTP en los puertos (587, 25)." try: - # Conectar al servidor SMTP with smtplib.SMTP(self.server_ip, puerto_activo, timeout=15) as server: if puerto_activo == 587: - server.starttls() # Activar TLS si se usa el puerto 587 - server.login(sender_email, sender_password) # Autenticación - server.sendmail(sender_email, recipient, message.as_string()) # Enviar el correo - + server.starttls() # Activa TLS si está en el puerto 587 + server.login(sender_email, sender_password) + server.sendmail(sender_email, recipient, message.as_string()) return f"Correo enviado correctamente a {recipient} usando el puerto {puerto_activo}" except smtplib.SMTPAuthenticationError: @@ -96,77 +63,47 @@ class MailClient: return f"Error inesperado al enviar el correo: {str(e)}" def fetch_emails(self, email_address, password): - """ - 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. - """ + """Recibe correos utilizando IMAP sin SSL.""" try: - # Conectar al servidor IMAP mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) mail.login(email_address, password) mail.select("inbox") - # Buscar todos los correos en la bandeja de entrada status, messages = mail.search(None, "ALL") email_ids = messages[0].split() emails = [] - # Obtener los últimos 10 correos - for email_id in email_ids[-10:]: + for email_id in email_ids[-10:]: # Obtener los últimos 10 correos status, msg_data = mail.fetch(email_id, "(RFC822)") for response_part in msg_data: if isinstance(response_part, tuple): msg = email.message_from_bytes(response_part[1]) - - # Decodificar el asunto del correo subject, encoding = email.header.decode_header(msg["Subject"])[0] if isinstance(subject, bytes): subject = subject.decode(encoding or "utf-8") - sender = msg.get("From") emails.append((email_id, subject, sender)) mail.logout() return emails - except imaplib.IMAP4.error: return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña." except Exception as e: return f"Error al recibir los correos: {str(e)}" def delete_email(self, email_address, password, email_id): - """ - 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. - """ + """Elimina un correo utilizando IMAP sin SSL.""" try: - # Conectar al servidor IMAP mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) mail.login(email_address, password) mail.select("inbox") - # Marcar el correo como eliminado - mail.store(email_id, "+FLAGS", "\\Deleted") - mail.expunge() # Eliminar definitivamente los correos marcados + mail.store(email_id, "+FLAGS", "\\Deleted") # Marca el correo como eliminado + mail.expunge() # Borra permanentemente los correos marcados como eliminados mail.logout() return f"Correo con ID {email_id} eliminado correctamente." - except imaplib.IMAP4.error: return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña." except Exception as e: - return f"Error al eliminar el correo: {str(e)}" + return f"Error al eliminar el correo: {str(e)}" \ No newline at end of file diff --git a/hilos/ApplicationLauncher.py b/hilos/ApplicationLauncher.py index 32ace0f..d831832 100644 --- a/hilos/ApplicationLauncher.py +++ b/hilos/ApplicationLauncher.py @@ -3,40 +3,93 @@ import threading import subprocess import os + class ApplicationLauncher: 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 - # Detectar rutas de instalación - self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe"]) - self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe"]) - self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe"]) + # 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"]) - # Crear botones de lanzamiento - self.create_button("Visual Code", self.launch_vscode) - self.create_button("Eclipse", self.launch_eclipse) - self.create_button("PyCharm", self.launch_pycharm) + # 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 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: + path = os.path.expandvars(path) # Expande variables como %USERNAME% if os.path.exists(path): return path 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): - """Lanza Visual Studio Code.""" + """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): - """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: - threading.Thread(target=subprocess.run, args=([path],), daemon=True).start() + threading.Thread(target=self.run_command, args=([path],), daemon=True).start() 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}") diff --git a/hilos/ChatWidget.py b/hilos/ChatWidget.py index 96d2fda..42be5e5 100644 --- a/hilos/ChatWidget.py +++ b/hilos/ChatWidget.py @@ -3,55 +3,71 @@ from tkinter import scrolledtext import socket import threading + class ChatWidget: def __init__(self, parent): - """Widget de chat con conexión a un servidor por sockets.""" self.parent = parent - self.frame = tk.Frame(self.parent, bg="lightgreen") - self.frame.pack(fill="x", padx=10, pady=10) + 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) - # Área de texto para mostrar mensajes - self.chat_display = scrolledtext.ScrolledText(self.frame, wrap=tk.WORD, state="disabled", width=40, height=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 de mensajes - self.message_entry = tk.Entry(self.frame, width=35) + # Campo de entrada para escribir mensajes + self.message_entry = tk.Entry(self.frame, width=35) # Reducir ancho self.message_entry.pack(pady=5) self.message_entry.bind("", self.send_message) - # Botón de enviar mensaje - self.send_button = tk.Button(self.frame, text="Enviar", command=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 socket + # Configuración del cliente socket 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: self.client_socket.connect(self.server_address) threading.Thread(target=self.receive_messages, daemon=True).start() - except: - self.display_message("[ERROR] No se pudo conectar al servidor.") + except Exception as e: + self.display_message(f"[ERROR] No se pudo conectar al servidor: {e}") def send_message(self, event=None): - """Envía un mensaje al servidor.""" message = self.message_entry.get() if message: - self.client_socket.send(message.encode("utf-8")) - self.message_entry.delete(0, tk.END) + 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 la interfaz.""" while True: try: message = self.client_socket.recv(1024).decode("utf-8") - self.display_message(message) + if message: + self.display_message(message) + else: + break except: + self.display_message("[DESCONECTADO] Conexión perdida con el servidor.") break def display_message(self, message): - """Muestra un mensaje en la ventana de chat.""" self.chat_display.config(state="normal") self.chat_display.insert(tk.END, message + "\n") self.chat_display.config(state="disabled") self.chat_display.see(tk.END) + + def close_connection(self): + try: + self.client_socket.close() + except: + pass \ No newline at end of file diff --git a/hilos/LanguageChart.py b/hilos/LanguageChart.py index e18c5fc..91fb6d0 100644 --- a/hilos/LanguageChart.py +++ b/hilos/LanguageChart.py @@ -7,44 +7,45 @@ import time class LanguageChart: 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: parent (tk.Frame): Frame donde se colocará el gráfico. """ 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.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.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") - # Incrustar el gráfico en Tkinter + # 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 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() 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: 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 un retraso en la actualización + time.sleep(5) # Simular retraso de actualización 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: self.fetch_data() @@ -53,4 +54,4 @@ class LanguageChart: 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 + time.sleep(5) # Actualizar cada 5 segundos \ No newline at end of file diff --git a/hilos/MusicPlayer.py b/hilos/MusicPlayer.py index 92003d2..039a8b6 100644 --- a/hilos/MusicPlayer.py +++ b/hilos/MusicPlayer.py @@ -4,90 +4,98 @@ from tkinter import filedialog import threading 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" class MusicPlayer: def __init__(self, parent): - """Inicializa el reproductor de música con botones de control y lista de canciones.""" self.parent = parent self.is_playing = False - # Inicializar el motor de audio + # Inicializar el reproductor de música 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.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 = tk.Label( + self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen" + ) self.title_label.pack(pady=5) # Lista de canciones descargadas self.song_listbox = tk.Listbox(self.frame, width=50, height=10) self.song_listbox.pack(pady=5) - self.load_songs() # Cargar canciones almacenadas + self.load_songs() - # Botón para seleccionar archivo manualmente - 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 + # Crear un marco para los botones de control self.controls_frame = tk.Frame(self.frame, bg="lightgreen") self.controls_frame.pack(pady=10) - # Botón de reproducción - self.play_button = tk.Button(self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12) + # Botón para seleccionar un archivo manualmente + 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) - # Botón para detener la reproducción - self.stop_button = tk.Button(self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12) + 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 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): - 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): - if file.endswith(".mp3"): # Filtrar solo archivos MP3 + if file.endswith(".mp3"): self.song_listbox.insert(tk.END, file) def select_file(self): - """Permite al usuario seleccionar un archivo de audio manualmente.""" - self.music_file = filedialog.askopenfilename(filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")]) + """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", "*.*")] + ) 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): - """Reproduce la canción seleccionada en la lista.""" + """Reproducir la canción seleccionada desde la lista de descargas.""" selected_index = self.song_listbox.curselection() if not selected_index: - return # Si no hay selección, no hacer nada + return selected_song = self.song_listbox.get(selected_index) self.music_file = os.path.join(DOWNLOAD_FOLDER, selected_song) self.is_playing = True - self.play_button.config(state="disabled") # Deshabilitar botón de reproducción - self.stop_button.config(state="normal") # Habilitar botón de detener + self.play_button.config(state="disabled") + 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): - """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.play() while pygame.mixer.music.get_busy(): if not self.is_playing: pygame.mixer.music.stop() - break # Salir del bucle si la música se detiene + break def stop_music(self): - """Detiene la reproducción de música.""" + """Detener la reproducción de música.""" self.is_playing = False self.play_button.config(state="normal") self.stop_button.config(state="disabled") diff --git a/hilos/SystemMonitor.py b/hilos/SystemMonitor.py index 4ad545e..3ba1e35 100644 --- a/hilos/SystemMonitor.py +++ b/hilos/SystemMonitor.py @@ -4,30 +4,66 @@ import tkinter as tk class SystemMonitor: def __init__(self, parent, stop_event): - """Muestra el uso del CPU, RAM y red en tiempo real.""" self.parent = parent self.stop_event = stop_event - # Etiquetas para mostrar métricas - self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen") - self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral") - self.cpu_label.pack(side="left", expand=True) - self.ram_label.pack(side="left", expand=True) + # 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") - # 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_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): - """Actualiza la etiqueta del uso de CPU.""" + """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): - """Actualiza la etiqueta del uso de RAM.""" + """Actualizar el uso de RAM.""" while not self.stop_event.is_set(): ram_usage = psutil.virtual_memory().percent self.ram_label.config(text=f"RAM: {ram_usage}%") + self.ram_label.after(1000, lambda: None) # Evitar bloqueo + self.stop_event.wait(1) + + def update_battery(self): + """Actualizar el estado de la batería.""" + while not self.stop_event.is_set(): + battery = psutil.sensors_battery() + if battery: + percent = battery.percent + time_left = battery.secsleft // 3600 if battery.secsleft > 0 else "N/A" + self.battery_label.config(text=f"Battery: {percent}%, ({time_left}h left)") + else: + self.battery_label.config(text="Battery: N/A") + self.battery_label.after(1000, lambda: None) # Evitar bloqueo + self.stop_event.wait(5) + + def update_network(self): + """Actualizar el uso de red.""" + old_sent = psutil.net_io_counters().bytes_sent + old_recv = psutil.net_io_counters().bytes_recv + while not self.stop_event.is_set(): + new_sent = psutil.net_io_counters().bytes_sent + new_recv = psutil.net_io_counters().bytes_recv + sent_mb = (new_sent - old_sent) / (1024 * 1024) + recv_mb = (new_recv - old_recv) / (1024 * 1024) + self.network_label.config(text=f"Net: {sent_mb:.2f} MB sent, {recv_mb:.2f} MB recv") + old_sent, old_recv = new_sent, new_recv + self.network_label.after(1000, lambda: None) # Evitar bloqueo self.stop_event.wait(1) diff --git a/hilos/WeatherWidget.py b/hilos/WeatherWidget.py index d87d1e6..4a3f306 100644 --- a/hilos/WeatherWidget.py +++ b/hilos/WeatherWidget.py @@ -3,45 +3,127 @@ import threading import requests import time + class WeatherWidget: 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: - 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. """ self.parent = parent 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.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 actualización automática del clima + # 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 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: lat (float): Latitud. lon (float): Longitud. """ - # Llamada a la API de OpenWeatherMap para obtener datos meteorológicos - pass + 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 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() diff --git a/solapas/EconomyBitcoinChart.py b/solapas/EconomyBitcoinChart.py index ba4e583..1de10f9 100644 --- a/solapas/EconomyBitcoinChart.py +++ b/solapas/EconomyBitcoinChart.py @@ -23,22 +23,21 @@ class EconomyBitcoinChart: 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)] # Índice económico en meses - self.bitcoin_data = [random.randint(20000, 60000) for _ in range(10)] # Precio del Bitcoin en días + 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 - # Dibujar los gráficos iniciales self.update_economy_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.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() 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.plot(self.economy_data, marker="o", color="blue") self.ax_economy.set_title("Economía Mundial") @@ -46,25 +45,25 @@ class EconomyBitcoinChart: self.ax_economy.grid(True) 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.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 del eje X + 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 con nuevos datos periódicamente.""" + """Actualiza ambos gráficos periódicamente.""" while True: - # Generar nuevos datos simulados - self.economy_data = self.economy_data[1:] + [random.randint(50, 100)] - self.bitcoin_data = self.bitcoin_data[1:] + [random.randint(20000, 60000)] + # 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 - # Redibujar gráficos con los nuevos datos + # 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) + time.sleep(5) \ No newline at end of file diff --git a/solapas/MusicDownloader.py b/solapas/MusicDownloader.py index f332260..3dfe309 100644 --- a/solapas/MusicDownloader.py +++ b/solapas/MusicDownloader.py @@ -10,7 +10,7 @@ DOWNLOAD_FOLDER = "musicplayer" class MusicDownloader: 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 @@ -22,7 +22,7 @@ class MusicDownloader: title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold")) 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.pack(pady=5) 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.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.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.pack(pady=5) 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() if not url: 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() 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: self.status_label.config(text="Descargando...", fg="blue") output_template = os.path.join(DOWNLOAD_FOLDER, "%(title)s.%(ext)s") - # Opciones para descargar solo el audio y convertirlo a MP3 ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': output_template, @@ -72,7 +71,6 @@ class MusicDownloader: 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 diff --git a/solapas/SQLQueryExecutor.py b/solapas/SQLQueryExecutor.py index e849464..a7f37a5 100644 --- a/solapas/SQLQueryExecutor.py +++ b/solapas/SQLQueryExecutor.py @@ -4,32 +4,33 @@ import threading import mysql.connector from mysql.connector import Error + class SQLQueryExecutor: 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: - 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 - # 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.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") # Valor por defecto + 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") # Usuario por defecto + 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="*") # Campo oculto + 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") @@ -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.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.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.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.pack(pady=5) - # Área para mostrar los resultados + # Área para mostrar resultados self.result_frame = tk.Frame(self.parent) 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) 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.user = self.user_entry.get() self.password = self.password_entry.get() @@ -80,7 +81,7 @@ class SQLQueryExecutor: messagebox.showerror("Error de Conexión", str(e)) 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() if not query: 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() 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: cursor = self.connection.cursor() cursor.execute(query) diff --git a/solapas/TicTacToe.py b/solapas/TicTacToe.py index eb21fdc..7504754 100644 --- a/solapas/TicTacToe.py +++ b/solapas/TicTacToe.py @@ -4,6 +4,7 @@ from tkinter import messagebox import threading import random + class TicTacToe: def __init__(self, parent): """ @@ -13,19 +14,19 @@ class TicTacToe: parent (tk.Frame): Frame donde se colocará el juego. """ self.parent = parent - self.board = [""] * 9 # Representación del tablero 3x3 - self.current_player = "X" # Jugador que inicia la partida + 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 - # Título del juego + # 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 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.pack(pady=5) - # Crear la cuadrícula del tablero + # Crear el tablero self.buttons = [] self.board_frame = tk.Frame(self.parent) self.board_frame.pack() @@ -37,12 +38,12 @@ class TicTacToe: font=("Helvetica", 20), width=5, 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) 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.pack(pady=5) @@ -54,7 +55,7 @@ class TicTacToe: self.reset_game() def reset_game(self): - """Reinicia el juego a su estado inicial.""" + """Reinicia el tablero y el estado del juego.""" self.board = [""] * 9 self.current_player = "X" for button in self.buttons: @@ -62,11 +63,12 @@ class TicTacToe: self.status_label.config(text="Turno: X") def make_move(self, index): - """Registra un movimiento y actualiza el tablero.""" + """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}!") @@ -75,11 +77,28 @@ class TicTacToe: 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 en el juego.""" + """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 @@ -89,3 +108,16 @@ class TicTacToe: if self.board[a] == self.board[b] == self.board[c] and self.board[a] != "": return self.board[a] return None + + def end_game(self, message): + """Finaliza el juego mostrando un mensaje.""" + messagebox.showinfo("Fin del Juego", message) + self.reset_game() + + def create_button_command(self, index): + """Crea un comando para un botón con un índice específico.""" + + def command(): + self.make_move(index) + + return command \ No newline at end of file diff --git a/solapas/WebScraperToDB.py b/solapas/WebScraperToDB.py index 3f14068..f9fb5c2 100644 --- a/solapas/WebScraperToDB.py +++ b/solapas/WebScraperToDB.py @@ -9,59 +9,52 @@ from tkinter import messagebox class WebScraperToDB: def __init__(self, parent): """ - Inicializa la interfaz gráfica para scraping web con integración a base de datos. - - Args: - parent (tk.Frame): Contenedor en el que se mostrará la interfaz gráfica. + Inicializa el widget de scraping con integración a base de datos. """ self.parent = parent - self.scraping_thread = None # Hilo que ejecutará el scraping - self.stop_event = threading.Event() # Evento para detener el scraping + self.scraping_thread = None + 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.pack(pady=5) - # Campos de entrada para la conexión a MySQL tk.Label(db_frame, text="Host:").grid(row=0, column=0) 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) tk.Label(db_frame, text="Usuario:").grid(row=1, column=0) 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) 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) 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") # Base de datos predeterminada + self.database_entry.insert(0, "scraping_db") 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) - # Sección de controles para scraping + # Área para URL y botones de control control_frame = tk.Frame(self.parent) 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) 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) - # Campo para ingresar el selector HTML + # 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") # Selector predeterminado + self.selector_entry.insert(0, "h1") # Valor predeterminado 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.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.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.pack(pady=5) @@ -85,7 +78,7 @@ class WebScraperToDB: self.scraped_data_text.pack(fill="both", expand=True) 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: connection = mysql.connector.connect( host=self.host_entry.get(), @@ -126,7 +119,9 @@ class WebScraperToDB: selector = self.selector_entry.get() 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( host=self.host_entry.get(), user=self.user_entry.get(), @@ -139,7 +134,7 @@ class WebScraperToDB: response = requests.get(url, headers=headers) 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) if not elements: self.status_label.config(text="Estado: Sin datos encontrados.", fg="orange") @@ -148,9 +143,9 @@ class WebScraperToDB: for element in elements: 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 - # 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)) connection.commit() @@ -162,7 +157,8 @@ class WebScraperToDB: 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() self.status_label.config(text="Estado: Inactivo", fg="red") @@ -177,7 +173,7 @@ class WebScraperToDB: self.stop_button.config(state="disabled") def reset_database(self): - """Elimina todos los datos de la tabla scraped_data.""" + """Elimina todos los datos de la tabla.""" try: connection = mysql.connector.connect( host=self.host_entry.get(), @@ -196,4 +192,4 @@ class WebScraperToDB: self.scraped_data_text.delete("1.0", "end") self.scraped_data_text.config(state="disabled") except Exception as e: - messagebox.showerror("Error", str(e)) + messagebox.showerror("Error", str(e)) \ No newline at end of file