diff --git a/main.py b/main.py index ee9b52d..1e7a586 100644 --- a/main.py +++ b/main.py @@ -3,8 +3,8 @@ from tkinter import Menu, ttk, messagebox, filedialog from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import os import threading +from PIL import Image, ImageTk -# Importaciones de módulos de lógica import utility_thread as ut import monitor_logic as ml import alarm_logic as al @@ -15,6 +15,8 @@ import camel_game_logic as cgl import audio_player_logic as apl import external_launcher as el import scraping_logic as sl +import email_logic as eml +import chat_logic as cl # --- CLASE ALARMLISTFRAME --- class AlarmListFrame(tk.Frame): @@ -88,11 +90,14 @@ class MainApplication(tk.Tk): self.selected_alarm_is_active = False self.camel_threads = [] self.winner_name = None - # Variables para el scraping + self.scraping_search_term_var = None self.scraping_results_text = None self.DEBUG_HTML_FILE = "amazon_debugging_output.html" + self.chat_display = None + self.chat_net = cl.ChatNetwork(self.receive_chat_message) + self.chat_net.start_server() self.configure_layout() self.create_widgets() @@ -102,14 +107,16 @@ class MainApplication(tk.Tk): self.start_alarm_checker() nm.start_network_monitoring_thread(self) - # INICIALIZAR EL REPRODUCTOR DE MÚSICA self.music_player = apl.MusicPlayer(self) - self.protocol("WM_DELETE_WINDOW", self.on_closing) + self.protocol("WM_DELETE_WINDOW", self.on_closing) + def on_closing(self): """Detiene la música y destruye la ventana al cerrar la app.""" self.music_player.stop_music() self.destroy() + if hasattr(self, 'chat_net'): + self.chat_net.stop() def configure_layout(self): self.columnconfigure(0, weight=0) @@ -133,9 +140,11 @@ class MainApplication(tk.Tk): self.create_audio_player_tab() self.create_external_launcher_tab() self.create_scraping_tab() + self.create_email_tab() self.notebook.bind('<>', self.on_tab_change) self.update_activity_status("Inicio de la aplicación") + self.create_chat_tab() def create_menu(self): @@ -393,7 +402,6 @@ class MainApplication(tk.Tk): tab_scraping.columnconfigure(0, weight=1) tab_scraping.rowconfigure(1, weight=1) - # 1. Controles Superiores (Término y Botón) frame_controls = ttk.Frame(tab_scraping, padding=10) frame_controls.grid(row=0, column=0, sticky='ew', pady=(0, 10)) frame_controls.columnconfigure(1, weight=1) @@ -405,7 +413,6 @@ class MainApplication(tk.Tk): ttk.Button(frame_controls, text="START SCRAPING", command=self.start_scraping_callback).grid(row=0, column=2, padx=10, sticky='e') - # 2. Área de Resultados (Texto) frame_results = ttk.Frame(tab_scraping, relief=tk.SUNKEN, borderwidth=2) frame_results.grid(row=1, column=0, sticky='nsew', padx=5, pady=5) frame_results.columnconfigure(0, weight=1) @@ -440,7 +447,6 @@ class MainApplication(tk.Tk): self.scraping_results_text.delete("1.0", tk.END) self.scraping_results_text.insert("1.0", f"Buscando productos en Amazon para '{search_term}' usando Playwright. Esto puede tardar unos segundos...\n") - # LLAMADA CLAVE: Inicia el proceso asíncrono en un hilo de trabajo sl.start_playwright_scraper(search_term, self) def _display_scraping_results(self, results, search_term): @@ -682,6 +688,205 @@ class MainApplication(tk.Tk): def start_alarm_checker(self): self.after(100, al.check_alarms, self.root, self.alarm_list_widget, self.show_alarm_popup) + + + # --- GESTIÓN DE EMAIL --- + + def create_email_tab(self): + """Inicializa la pestaña de correo.""" + self.tab_email = ttk.Frame(self.notebook) + self.notebook.add(self.tab_email, text="Correo ✉️") + self.current_user = "" + self.current_pass = "" + self.show_email_login_screen() + + def logout_email(self): + """Limpia la sesión y vuelve al login.""" + self.current_user = "" + self.current_pass = "" + self.show_email_login_screen() + self.update_activity_status("Sesión de correo cerrada.") + + def show_email_inbox(self, email_list): + """Bandeja de entrada responsive.""" + for widget in self.tab_email.winfo_children(): widget.destroy() + + navbar = tk.Frame(self.tab_email, bg="white", height=50) + navbar.pack(fill="x") + ttk.Button(navbar, text="📝 Redactar", command=self.open_compose_window).pack(side="left", padx=10) + ttk.Button(navbar, text="🔄 Actualizar", command=self.refresh_inbox).pack(side="left") + ttk.Button(navbar, text="🚪 Cerrar Sesión", command=self.logout_email).pack(side="right", padx=10) + + container = tk.Frame(self.tab_email, bg="#f0f2f5") + container.pack(fill="both", expand=True) + + canvas = tk.Canvas(container, bg="#f0f2f5", highlightthickness=0) + sb = ttk.Scrollbar(container, command=canvas.yview) + self.mail_frame = tk.Frame(canvas, bg="#f0f2f5") + + self.mail_frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) + c_win = canvas.create_window((0,0), window=self.mail_frame, anchor="nw") + canvas.bind("", lambda e: canvas.itemconfig(c_win, width=e.width)) + + canvas.configure(yscrollcommand=sb.set) + canvas.pack(side="left", fill="both", expand=True) + sb.pack(side="right", fill="y") + + for m in email_list: self.create_mail_card(m) + + def show_email_login_screen(self): + for widget in self.tab_email.winfo_children(): widget.destroy() + + container = tk.Frame(self.tab_email, bg="#f5f7fa") + container.place(relx=0, rely=0, relwidth=1, relheight=1) + + card = tk.Frame(container, bg="white", padx=40, pady=40, highlightbackground="#d1d9e6", highlightthickness=1) + card.place(relx=0.5, rely=0.5, anchor="center") + + tk.Label(card, text="Acceso Correo", font=("Arial", 16, "bold"), bg="white").pack(pady=(0, 20)) + + tk.Label(card, text="Usuario", bg="white").pack(anchor="w") + self.ent_user = tk.Entry(card, width=30, highlightthickness=1) + self.ent_user.insert(0, "alumno@10.10.0.101") + self.ent_user.pack(pady=5) + + tk.Label(card, text="Contraseña", bg="white").pack(anchor="w") + self.ent_pass = tk.Entry(card, show="*", width=30, highlightthickness=1) + self.ent_pass.pack(pady=5) + + tk.Button(card, text="ENTRAR", bg="#3498db", fg="white", font=("Arial", 10, "bold"), + command=self.do_email_login, padx=20, pady=10).pack(pady=20, fill="x") + + def do_email_login(self): + """Realiza la conexión inicial.""" + u, p = self.ent_user.get(), self.ent_pass.get() + success, data = eml.fetch_emails_logic(u, p) + if success: + self.current_user, self.current_pass = u, p + self.show_email_inbox(data) + else: + messagebox.showerror("Error", "Credenciales incorrectas o error de red") + + def refresh_inbox(self): + """Actualiza la bandeja.""" + success, data = eml.fetch_emails_logic(self.current_user, self.current_pass) + if success: self.show_email_inbox(data) + + def create_mail_card(self, mail): + """Tarjetas expandibles de correo.""" + card = tk.Frame(self.mail_frame, bg="white", highlightbackground="#ddd", highlightthickness=1) + card.pack(fill="x", pady=5, padx=15) + + header = tk.Label(card, text=f"De: {mail['sender']}\nAsunto: {mail['subject']}", + font=("Arial", 10, "bold"), bg="white", justify="left", anchor="w", pady=10, padx=10) + header.pack(fill="x") + + body_box = tk.Frame(card, bg="#f9f9f9") + self.img_refs = [] + + def expand(event): + if body_box.winfo_ismapped(): body_box.pack_forget() + else: + success, data = eml.fetch_email_full_data(self.current_user, self.current_pass, mail['id']) + if success: + for w in body_box.winfo_children(): w.destroy() + txt = tk.Text(body_box, height=8, bg="#f9f9f9", bd=0, padx=10, pady=10) + txt.insert("1.0", data['body']) + txt.config(state="disabled") + txt.pack(fill="x") + + for img in data['images']: + try: + photo = ImageTk.PhotoImage(Image.open(img).convert("RGB").resize((300, 300))) + self.img_refs.append(photo) + tk.Label(body_box, image=photo, bg="#f9f9f9").pack(pady=5) + except: pass + + for f_path in data['files']: + tk.Button(body_box, text=f"📄 Abrir: {os.path.basename(f_path)}", + fg="blue", command=lambda p=f_path: os.system(f'xdg-open "{p}"'), + bg="#f9f9f9", bd=0).pack(anchor="w", padx=20, pady=5) + + body_box.pack(fill="x") + + header.bind("", expand) + + def open_compose_window(self): + comp = tk.Toplevel(self); comp.title("Redactar"); comp.geometry("400x500") + tk.Label(comp, text="Para:").pack(padx=10, anchor="w") + e_to = tk.Entry(comp); e_to.pack(fill="x", padx=10) + tk.Label(comp, text="Asunto:").pack(padx=10, anchor="w") + e_sub = tk.Entry(comp); e_sub.pack(fill="x", padx=10) + + self.attachment_path = None + lbl_file = tk.Label(comp, text="Sin archivo") + def attach(): + self.attachment_path = filedialog.askopenfilename() + if self.attachment_path: lbl_file.config(text=os.path.basename(self.attachment_path), fg="blue") + + tk.Button(comp, text="📎 Adjuntar", command=attach).pack(pady=5) + lbl_file.pack() + txt = tk.Text(comp, height=10); txt.pack(fill="both", expand=True, padx=10, pady=10) + + def send(): + s, m = eml.send_email_logic(self.current_user, e_to.get(), e_sub.get(), txt.get("1.0", "end"), self.current_pass, self.attachment_path) + if s: messagebox.showinfo("OK", "Enviado"); comp.destroy() + else: messagebox.showerror("Error", m) + + tk.Button(comp, text="ENVIAR 🚀", bg="#2ecc71", fg="white", command=send).pack(pady=10) + + + + # --- GESTIÓN DEL CHAT --- + + def create_chat_tab(self): + import os + import tkinter as tk + from tkinter import ttk + + tab_chat = ttk.Frame(self.notebook) + self.notebook.add(tab_chat, text="Chat Grupal 💬", padding=15) + + tab_chat.columnconfigure(0, weight=1) + tab_chat.rowconfigure(2, weight=1) + + mi_pid = os.getpid() + + lbl_pid = tk.Label(tab_chat, text=f"📍 TU NÚMERO DE PROCESO (PID) ES: {mi_pid}", + font=("Arial", 14, "bold"), fg="#2c3e50", bg="#ecf0f1", pady=10, relief="groove") + lbl_pid.grid(row=0, column=0, sticky="ew", pady=(0, 10)) + + tk.Label(tab_chat, text="Sala general (todos los procesos leen y escriben aquí):", + font=("Arial", 10, "bold"), anchor="w").grid(row=1, column=0, sticky="ew") + + self.chat_display = tk.Text(tab_chat, state='disabled', height=15, bg="#fdfdfd", font=("Consolas", 11)) + self.chat_display.grid(row=2, column=0, sticky="nsew", pady=5) + + frame_input = ttk.Frame(tab_chat) + frame_input.grid(row=3, column=0, sticky="ew", pady=(5, 0)) + + self.chat_entry = ttk.Entry(frame_input, font=("Arial", 11)) + self.chat_entry.pack(side="left", fill="x", expand=True, padx=(0, 5)) + self.chat_entry.bind("", lambda e: self.send_chat_message()) + + ttk.Button(frame_input, text="Enviar 🚀", command=self.send_chat_message).pack(side="right") + + def send_chat_message(self): + msg = self.chat_entry.get() + if msg.strip(): + if self.chat_net.send_message(msg): + self.chat_entry.delete(0, tk.END) + + def receive_chat_message(self, message): + import tkinter as tk + def _actualizar_ui(): + if self.chat_display and self.chat_display.winfo_exists(): + self.chat_display.config(state='normal') + self.chat_display.insert(tk.END, message + "\n") + self.chat_display.see(tk.END) + self.chat_display.config(state='disabled') + + self.after(0, _actualizar_ui) if __name__ == "__main__": app = MainApplication()