From 9f7d1e247c229d9a76419d3ae6990ca139733fb7 Mon Sep 17 00:00:00 2001 From: marcos Date: Fri, 5 Dec 2025 18:50:57 +0100 Subject: [PATCH] Proyecto Global inicial --- README.md | 67 + __pycache__/app.cpython-313.pyc | Bin 0 -> 141314 bytes __pycache__/app.cpython-314.pyc | Bin 0 -> 160109 bytes app.py | 2228 +++++++++++++++++++++++++++++++ requirements.txt | 7 + servidor.py | 84 ++ 6 files changed, 2386 insertions(+) create mode 100644 README.md create mode 100644 __pycache__/app.cpython-313.pyc create mode 100644 __pycache__/app.cpython-314.pyc create mode 100644 app.py create mode 100644 requirements.txt create mode 100644 servidor.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c3069e --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Proyecto Global Dashboard + +Panel de control escrito en Python 3.14 + Tkinter que reúne las prácticas solicitadas (scraping, monitorización, alarmas, notas, música y más) con una estética cuidada y paneles diferenciados. + +## 🚀 Características principales + +- **Dashboard modular**: panel izquierdo con accesos rápidos (scraping, clima de Jávea, Camellos, copias de seguridad, etc.), cuaderno central por pestañas y panel derecho con chat y listado de alumnos. +- **Scraping integrado**: workflows para Wallapop y scraping genérico con popups dedicados y avisos de estado. +- **Monitor de sistema**: gráficas PSUtil actualizadas mediante `after` de Tk, evitando bloqueos y mostrando CPU/RAM/Net de forma fluida. +- **Bloc de notas y backups reales**: edición rápida de texto con copias automáticas a una carpeta de respaldo mostrando progreso. +- **Widgets temáticos**: reproductor musical con tarjetas, gestor de alarmas rediseñado y popup meteorológico (OpenWeather, coordenadas de Jávea) cacheado para reducir llamadas. +- **Servidor de mensajería**: `servidor.py` permite broadcast TCP para pruebas de chat local. + +## ⚙️ Requisitos + +- Python 3.8 o superior (desarrollado con 3.14) +- Dependencias listadas en `requirements.txt` + +```sh +pip install -r requirements.txt +``` + +## ▶️ Puesta en marcha rápida + +1. (Opcional) Arranca el servidor de mensajería: + ```sh + python3 servidor.py + ``` + Verás `Servidor escuchando en 0.0.0.0:3333` en consola. +2. Lanza la interfaz gráfica: + ```sh + python3 app.py + ``` +3. Desde el panel derecho ajusta host/puerto y pulsa `Conectar` para chatear. Explora el resto de pestañas (scraping, notas, alarmas, música, clima) desde los botones laterales. + +## 🧱 Arquitectura de carpetas + +``` +app.py # GUI principal y lógica de scraping, clima, monitorización, alarmas... +servidor.py # Servidor TCP broadcast para el chat de pruebas +requirements.txt # Dependencias del proyecto +README.md # Este archivo +``` + +## 🛠️ Funcionalidades destacadas + +- **Scraping Wallapop y genérico**: ventanas emergentes, peticiones HTTP con Requests + BeautifulSoup, mensajes de éxito/error. +- **Weather popup “API Tiempo”**: botón dedicado que consulta OpenWeather (con clave fallback), muestra iconos, temperaturas y caché temporal. +- **Copias de seguridad guiadas**: barra de progreso y notificaciones durante la duplicación de directorios. +- **Editor y bloc de notas**: pestañas separadas para notas rápidas y bloc organizado. +- **Gestor de alarmas**: UI modernizada con tarjetas, botones primarios y feedback visual. +- **Música y utilidades**: reproductor basado en pygame y accesos a herramientas externas (“Camellos”, lanzadores, etc.). + +## 🌤️ Servicios externos + +- **OpenWeatherMap**: usado para el popup del clima (coordenadas de Jávea). Define `OPENWEATHER_API_KEY` en el entorno para usar tu propia clave. +- **Wallapop / sitios objetivo**: las rutinas de scraping respetan temporizadores y headers básicos; ajusta las URLs o parámetros dentro de `app.py` para nuevos escenarios. + +## 📌 Próximos pasos sugeridos + +1. Añadir pruebas unitarias para la lógica no gráfica (scraping, backups, parsers). +2. Persistir chats y notas en SQLite para mantener el historial. +3. Integrar reproductor completo dentro de la app (playlist, carátulas). + +--- + +¿Necesitas extender alguna funcionalidad? Abre un issue o comenta qué módulo quieres potenciar (más scraping, dashboards adicionales, automatización de backups, etc.). diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee377de5d37e7b4208c3216be4e20db82ae9a972 GIT binary patch literal 141314 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 literal 0 HcmV?d00001 diff --git a/app.py b/app.py new file mode 100644 index 0000000..f66faee --- /dev/null +++ b/app.py @@ -0,0 +1,2228 @@ +#!/usr/bin/env python3 +"""Dashboard principal del Proyecto Global.""" +from __future__ import annotations + +import datetime +import json +import os +import shutil +import queue +import random +import socket +import subprocess +import sys +import tempfile +import threading +import time +import webbrowser +from dataclasses import dataclass +from typing import Any, Callable + +import tkinter as tk +from tkinter import filedialog, messagebox, scrolledtext, ttk +from tkinter import font as tkfont + +# -------------------- dependencias opcionales -------------------- +try: + import psutil # type: ignore +except Exception: # pragma: no cover + psutil = None # type: ignore + +try: + import matplotlib + matplotlib.use('TkAgg') + from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + from matplotlib.figure import Figure + MATPLOTLIB_AVAILABLE = True +except Exception: # pragma: no cover + MATPLOTLIB_AVAILABLE = False + Figure = None + FigureCanvasTkAgg = None + +try: + import pygame # type: ignore + pygame_available = True +except Exception: # pragma: no cover + pygame_available = False + +try: + import requests # type: ignore + REQUESTS_AVAILABLE = True +except Exception: # pragma: no cover + requests = None # type: ignore + REQUESTS_AVAILABLE = False + +try: + from bs4 import BeautifulSoup + BEAUTIFULSOUP_AVAILABLE = True +except Exception: # pragma: no cover + BEAUTIFULSOUP_AVAILABLE = False + +SERVER_HOST_DEFAULT = '127.0.0.1' +SERVER_PORT_DEFAULT = 3333 +# Clave predeterminada facilitada por el usuario para OpenWeather. +# Puede sobrescribirse exportando OPENWEATHER_API_KEY en el entorno. +OPENWEATHER_FALLBACK_API_KEY = os.environ.get( + 'OPENWEATHER_FALLBACK_API_KEY', + '431239407e3628578c83e67180cf720f' +).strip() +_OPENWEATHER_ENV_KEY = os.environ.get('OPENWEATHER_API_KEY', '').strip() +OPENWEATHER_API_KEY = _OPENWEATHER_ENV_KEY or OPENWEATHER_FALLBACK_API_KEY +OPENWEATHER_CITY = os.environ.get('OPENWEATHER_CITY', 'Madrid,ES') +JAVEA_LATITUDE = 38.789166 +JAVEA_LONGITUDE = 0.163055 + +# -------------------- paleta visual -------------------- +PRIMARY_BG = '#f4f5fb' +PANEL_BG = '#ffffff' +SECONDARY_BG = '#eef2ff' +ACCENT_COLOR = '#ff6b6b' +ACCENT_DARK = '#c44569' +TEXT_COLOR = '#1f2d3d' +SUBTEXT_COLOR = '#67708a' +BUTTON_BG = '#e7ecff' +BUTTON_ACTIVE_BG = '#ffd6d1' + + +def _env_float(name: str, default: float) -> float: + value = os.environ.get(name) + if value is None: + return default + try: + return float(value) + except ValueError: + return default +WALLAPOP_DEVICE_ID = os.environ.get('WALLAPOP_DEVICE_ID', '48ca24ec-2e8c-4dbb-9f0b-6b4f99194626').strip() +WALLAPOP_APP_VERSION = os.environ.get('WALLAPOP_APP_VERSION', '814060').strip() +WALLAPOP_MPID = os.environ.get('WALLAPOP_MPID', '6088013701866267655').strip() +WALLAPOP_DEVICE_OS = os.environ.get('WALLAPOP_DEVICE_OS', '0').strip() +WALLAPOP_USER_AGENT = os.environ.get( + 'WALLAPOP_USER_AGENT', + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Mobile Safari/537.36' +).strip() +WALLAPOP_SEC_CH_UA = os.environ.get('WALLAPOP_SEC_CH_UA', '"Not_A Brand";v="99", "Chromium";v="142"') +WALLAPOP_SEC_CH_UA_MOBILE = os.environ.get('WALLAPOP_SEC_CH_UA_MOBILE', '?1') +WALLAPOP_SEC_CH_UA_PLATFORM = os.environ.get('WALLAPOP_SEC_CH_UA_PLATFORM', '"Android"') +WALLAPOP_LATITUDE = _env_float('WALLAPOP_LATITUDE', 40.416775) +WALLAPOP_LONGITUDE = _env_float('WALLAPOP_LONGITUDE', -3.70379) +WALLAPOP_COUNTRY_CODE = os.environ.get('WALLAPOP_COUNTRY_CODE', 'ES').strip() or 'ES' +WALLAPOP_LANGUAGE = os.environ.get('WALLAPOP_LANGUAGE', 'es').strip() or 'es' +WALLAPOP_CATEGORY_ID = os.environ.get('WALLAPOP_CATEGORY_ID', '').strip() +WALLAPOP_REFERER = os.environ.get('WALLAPOP_REFERER', 'https://es.wallapop.com/').strip() + +WALLAPOP_HEADERS = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'es,es-ES;q=0.9', + 'Referer': WALLAPOP_REFERER, + 'User-Agent': WALLAPOP_USER_AGENT, + 'x-deviceid': WALLAPOP_DEVICE_ID, + 'x-deviceos': WALLAPOP_DEVICE_OS, + 'deviceos': WALLAPOP_DEVICE_OS, + 'x-appversion': WALLAPOP_APP_VERSION, + 'mpid': WALLAPOP_MPID, + 'sec-ch-ua': WALLAPOP_SEC_CH_UA, + 'sec-ch-ua-mobile': WALLAPOP_SEC_CH_UA_MOBILE, + 'sec-ch-ua-platform': WALLAPOP_SEC_CH_UA_PLATFORM +} + + + +class ChatClient: + """Cliente TCP básico.""" + + def __init__(self, on_message): + self._on_message = on_message + self._sock: socket.socket | None = None + self._lock = threading.Lock() + self._connected = False + + def connect(self, host: str, port: int) -> bool: + with self._lock: + if self._connected: + return True + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + self._sock = sock + self._connected = True + threading.Thread(target=self._recv_loop, daemon=True).start() + return True + except Exception as exc: + self._on_message(f'[ERROR] Conexión fallida: {exc}') + return False + + def _recv_loop(self): + try: + while self._connected and self._sock: + data = self._sock.recv(4096) + if not data: + break + text = data.decode('utf-8', errors='replace').strip() + self._on_message(text) + except Exception as exc: + self._on_message(f'[ERROR] {exc}') + finally: + with self._lock: + self._connected = False + if self._sock: + try: + self._sock.close() + except Exception: + pass + self._sock = None + self._on_message('[INFO] Conexión cerrada') + + def send(self, text: str) -> bool: + with self._lock: + if not self._connected or not self._sock: + return False + try: + self._sock.sendall(text.encode('utf-8')) + return True + except Exception: + self._connected = False + return False + + def close(self): + with self._lock: + self._connected = False + if self._sock: + try: + self._sock.close() + except Exception: + pass + self._sock = None + + +class DashboardApp(tk.Tk): + def __init__(self) -> None: + super().__init__() + self.title('Panel de laboratorio - Proyecto Global') + self.geometry('1220x740') + self.minsize(1024, 660) + self.configure(bg=PRIMARY_BG) + self._setup_fonts() + self._maximize_with_borders() + + self.columnconfigure(0, weight=0) + self.columnconfigure(1, weight=1) + self.columnconfigure(2, weight=0) + self.rowconfigure(0, weight=0) + self.rowconfigure(1, weight=1) + self.rowconfigure(2, weight=0) + + self._running = True + self._chat_queue: 'queue.Queue[str]' = queue.Queue() + self._scraping_queue: 'queue.Queue[tuple[str, ...]]' = queue.Queue() + self._traffic_last = psutil.net_io_counters() if psutil else None + self._resource_history = {'cpu': [], 'mem': [], 'threads': []} + self._resource_poll_job: str | None = None + + self.chat_client = ChatClient(self._enqueue_chat_message) + self.alarm_counter = 1 + self.active_alarms: list[dict[str, datetime.datetime | str]] = [] + self.game_window_canvas = None + self.game_window_status = None + self.game_move_queue: queue.Queue | None = None + self.game_queue_processor_active = False + self.game_done_event: threading.Event | None = None + self.game_finish_line = 0 + self.game_racer_names: list[str] = [] + self.game_camel_count = 3 + self.game_speed_factor = 1.0 + self.music_temp_file: str | None = None + self.weather_city = OPENWEATHER_CITY + self.weather_api_key = OPENWEATHER_API_KEY + self.results_area: tk.Frame | None = None + self.results_title: tk.Label | None = None + self.scraping_popup: tk.Toplevel | None = None + self.simple_scraping_popup: tk.Toplevel | None = None + self.weather_popup: tk.Toplevel | None = None + self._last_weather_data: dict[str, Any] | None = None + self._last_weather_error: str | None = None + self._last_weather_timestamp: datetime.datetime | None = None + self.chart_canvas = None + self.ax_cpu = None + self.ax_mem = None + self.ax_threads = None + self.line_cpu = None + self.line_mem = None + self.mem_fill = None + self.thread_bars = None + if psutil: + try: + self._ps_process = psutil.Process(os.getpid()) + except Exception: + self._ps_process = None + else: + self._ps_process = None + + self._build_header() + self._build_left_panel() + self._build_center_panel() + self._build_right_panel() + self._build_status_bar() + + self._update_clock() + if psutil: + self.after(1000, self._update_traffic) + try: + psutil.cpu_percent(interval=None) + except Exception: + pass + self._resource_poll_job = self.after(1000, self._resource_poll_tick) + threading.Thread(target=self._chat_loop, daemon=True).start() + self.after(100, self._process_scraping_queue) + self.after(1000, self._refresh_alarms_loop) + if REQUESTS_AVAILABLE and self.weather_api_key: + self.after(2000, self._update_weather) + + # ------------------------ UI ------------------------ + def _maximize_with_borders(self) -> None: + def _apply_zoom(): + # Intentar diferentes atributos para maximizar/pantalla completa en diferentes OS + for attr in ('-zoomed',): + try: + self.attributes(attr, True) + return + except tk.TclError: + continue + try: + self.state('zoomed') + except tk.TclError: + pass + + self.after(0, _apply_zoom) + + def _setup_fonts(self) -> None: + base_family = 'Segoe UI' + self.font_title = tkfont.Font(family=base_family, size=16, weight='bold') + self.font_header = tkfont.Font(family=base_family, size=13, weight='bold') + self.font_body = tkfont.Font(family=base_family, size=11) + self.font_small = tkfont.Font(family=base_family, size=10) + + def _build_header(self) -> None: + header = tk.Frame(self, bg=PANEL_BG, bd=0, highlightthickness=0) + header.grid(row=0, column=0, columnspan=3, sticky='new') + header.grid_columnconfigure(tuple(range(7)), weight=1) + tabs = [ + ('T1. Procesos', '#4c6ef5'), + ('T2. Threads', '#ff6b6b'), + ('T3. Sockets', '#ffa94d'), + ('T4. Servicios', '#51cf66'), + ('T5. Seguridad', '#845ef7'), + ('Configuración', '#2f3545') + ] + for idx, (text, color) in enumerate(tabs): + badge = tk.Label( + header, + text=text, + fg=color, + bg=PANEL_BG, + font=self.font_title, + padx=16, + pady=8 + ) + badge.grid(row=0, column=idx, padx=6, pady=8) + + def _build_left_panel(self) -> None: + panel = tk.Frame(self, width=220, bg=SECONDARY_BG, bd=0, highlightthickness=0) + panel.grid(row=1, column=0, sticky='nsw', padx=6, pady=(50,6)) + panel.grid_propagate(False) + self.left_panel = panel + + self._left_section('Acciones') + self._left_button('Analizar Wallapop', self._open_wallapop_popup) + self._left_button('Scraping simple', self._open_simple_scraping_popup) + self._left_button('Navegar', lambda: self._open_web('https://www.google.com')) + self._left_button('API Tiempo', self._show_weather_popup) + + self._left_section('Aplicaciones') + self._left_button('Visual Code', lambda: self._launch_process(['code'])) + self._left_button('Camellos', self._open_game_window) + self._left_button('App3', lambda: self._launch_process(['firefox'])) + + self._left_section('Procesos batch') + self._left_button('Copias de seguridad', self._run_backup_script) + + def _left_section(self, text: str) -> None: + tk.Label(self.left_panel, text=text.upper(), bg=SECONDARY_BG, fg=SUBTEXT_COLOR, font=self.font_small).pack(anchor='w', padx=16, pady=(16,4)) + + def _left_button(self, text: str, command) -> None: + btn = tk.Button( + self.left_panel, + text=text, + width=20, + bg=BUTTON_BG, + activebackground=BUTTON_ACTIVE_BG, + relief='flat', + command=command + ) + btn.pack(fill='x', padx=16, pady=4) + + def _build_center_panel(self) -> None: + center = tk.Frame(self, bg=PANEL_BG, bd=0) + center.grid(row=1, column=1, sticky='nsew', padx=6, pady=(50,6)) + center.rowconfigure(1, weight=1) + center.columnconfigure(0, weight=1) + self.center_panel = center + + self._build_notebook() + self._build_notes_panel() + + def _build_notebook(self) -> None: + style = ttk.Style() + style.configure('Custom.TNotebook', background=PANEL_BG, borderwidth=0) + style.configure('Custom.TNotebook.Tab', padding=(16, 8), font=self.font_body) + style.map('Custom.TNotebook.Tab', background=[('selected', '#dde2ff')]) + + notebook = ttk.Notebook(self.center_panel, style='Custom.TNotebook') + notebook.grid(row=0, column=0, sticky='nsew', padx=4, pady=4) + self.notebook = notebook + + self.tab_resultados = tk.Frame(notebook, bg='white') + self.tab_navegador = tk.Frame(notebook, bg='white') + self.tab_correos = tk.Frame(notebook, bg='white') + self.tab_tareas = tk.Frame(notebook, bg='white') + self.tab_alarmas = tk.Frame(notebook, bg='white') + self.tab_enlaces = tk.Frame(notebook, bg='white') + self.tab_bloc = tk.Frame(notebook, bg='white') + + notebook.add(self.tab_resultados, text='Resultados') + notebook.add(self.tab_navegador, text='Navegador') + notebook.add(self.tab_correos, text='Correos') + notebook.add(self.tab_bloc, text='Bloc de notas') + notebook.add(self.tab_tareas, text='Tareas') + notebook.add(self.tab_alarmas, text='Alarmas') + notebook.add(self.tab_enlaces, text='Enlaces') + + self._build_tab_resultados() + self._build_tab_navegador() + self._build_tab_correos() + self._build_tab_bloc_notas() + self._build_tab_tareas() + self._build_tab_alarmas() + self._build_tab_enlaces() + + def _build_tab_resultados(self) -> None: + wrapper = tk.Frame(self.tab_resultados, bg=PANEL_BG) + wrapper.pack(fill='both', expand=True) + self.results_title = tk.Label(wrapper, text='Resultados de actividades', font=self.font_header, bg=PANEL_BG, fg=TEXT_COLOR) + self.results_title.pack(pady=(12, 4)) + + toolbar = tk.Frame(wrapper, bg=PANEL_BG) + toolbar.pack(fill='x', padx=12, pady=(0, 4)) + tk.Button(toolbar, text='Ver monitor del sistema', command=self._show_resource_monitor, bg=BUTTON_BG, relief='flat').pack(side='left', padx=(0, 8)) + tk.Button(toolbar, text='Limpiar resultados', command=self._reset_results_view, bg=BUTTON_BG, relief='flat').pack(side='left') + + self.results_area = tk.Frame(wrapper, bg='#f4f7ff', bd=1, relief='solid') + self.results_area.pack(fill='both', expand=True, padx=12, pady=(0, 12)) + + self._reset_results_view() + + def _prepare_results_area(self) -> None: + self._stop_game_queue_processor() + if not self.results_area: + return + for child in self.results_area.winfo_children(): + child.destroy() + self.chart_canvas = None + self.ax_cpu = None + self.ax_mem = None + self.ax_threads = None + self.line_cpu = None + self.line_mem = None + self.mem_fill = None + self.thread_bars = None + self.game_window_canvas = None + self.game_window_status = None + + def _reset_results_view(self) -> None: + self._prepare_results_area() + if self.results_title: + self.results_title.config(text='Resultados de actividades') + if self.results_area: + tk.Label( + self.results_area, + text='Ejecute una actividad para ver aquí sus resultados más recientes.', + bg='#f4f7ff', + fg=SUBTEXT_COLOR, + font=self.font_body, + wraplength=520 + ).pack(expand=True, padx=12, pady=12) + + def _render_results_view(self, title: str, builder: Callable[[tk.Frame], None]) -> None: + if self.results_title: + self.results_title.config(text=f'Resultados • {title}') + self._prepare_results_area() + if not self.results_area: + return + builder(self.results_area) + + def _show_resource_monitor(self) -> None: + def builder(parent: tk.Frame) -> None: + if not (MATPLOTLIB_AVAILABLE and psutil): + tk.Label( + parent, + text='Instale matplotlib + psutil para habilitar el monitor del sistema.', + fg=ACCENT_COLOR, + bg='#f4f7ff', + font=self.font_body + ).pack(fill='both', expand=True, padx=20, pady=20) + return + chart_frame = tk.Frame(parent, bg=PANEL_BG) + chart_frame.pack(fill='both', expand=True, padx=12, pady=12) + fig = Figure(figsize=(7.2, 5.6), dpi=100) + self.ax_cpu = fig.add_subplot(311) + self.ax_mem = fig.add_subplot(312) + self.ax_threads = fig.add_subplot(313) + + self.ax_cpu.set_title('CPU (línea)') + self.ax_cpu.set_ylim(0, 100) + self.ax_cpu.set_ylabel('%') + self.ax_cpu.grid(True, alpha=0.25) + self.line_cpu, = self.ax_cpu.plot([], [], label='CPU', color='#ff6b6b', linewidth=2) + + self.ax_mem.set_title('Memoria (área)') + self.ax_mem.set_ylim(0, 100) + self.ax_mem.set_ylabel('%') + self.ax_mem.grid(True, alpha=0.2) + self.line_mem, = self.ax_mem.plot([], [], color='#4ecdc4', linewidth=1.5) + + self.ax_threads.set_title('Hilos del proceso (barras)') + self.ax_threads.set_ylabel('Hilos') + self.ax_threads.grid(True, axis='y', alpha=0.2) + + fig.tight_layout(pad=2.2) + self.chart_canvas = FigureCanvasTkAgg(fig, master=chart_frame) + self.chart_canvas.get_tk_widget().pack(fill='both', expand=True) + self.ax_cpu.set_xlim(0, 40) + self.ax_mem.set_xlim(0, 40) + self.ax_threads.set_xlim(-0.5, 39.5) + self.chart_canvas.draw_idle() + + self._render_results_view('Monitoreo del sistema', builder) + + def _show_text_result(self, title: str, text: str) -> None: + def builder(parent: tk.Frame) -> None: + frame = tk.Frame(parent, bg='white') + frame.pack(fill='both', expand=True, padx=12, pady=12) + box = scrolledtext.ScrolledText(frame, height=12) + box.pack(fill='both', expand=True) + box.insert('1.0', text) + box.config(state='disabled') + + self._render_results_view(title, builder) + + def _format_weather_summary(self, data: dict[str, Any]) -> str: + main = data.get('main', {}) + weather = data.get('weather', [{}])[0] + wind = data.get('wind', {}) + temp = main.get('temp') + feels = main.get('feels_like') + humidity = main.get('humidity') + desc = weather.get('description', '').capitalize() + wind_speed = wind.get('speed') + timestamp = data.get('dt') + updated = datetime.datetime.fromtimestamp(timestamp).strftime('%d/%m %H:%M') if timestamp else 'N/D' + summary = [ + f'Descripción : {desc or "N/D"}', + f'Temperatura : {temp:.1f}°C' if temp is not None else 'Temperatura : N/D', + f'Sensación : {feels:.1f}°C' if feels is not None else 'Sensación : N/D', + f'Humedad : {humidity}%' if humidity is not None else 'Humedad : N/D', + f'Veloc. viento: {wind_speed:.1f} m/s' if wind_speed is not None else 'Veloc. viento: N/D', + f'Actualizado : {updated}' + ] + return '\n'.join(summary) + + def _get_javea_weather_snapshot(self) -> tuple[str, str, bool]: + try: + data = self._fetch_javea_weather() + summary = self._format_weather_summary(data) + timestamp = data.get('dt') + status = 'Última lectura: ' + status += datetime.datetime.fromtimestamp(timestamp).strftime('%d/%m %H:%M') if timestamp else 'N/D' + return summary, status, False + except Exception as exc: + return str(exc), 'No se pudo obtener el clima', True + + def _show_weather_popup(self) -> None: + self._log('Abriendo ventana del tiempo para Jávea') + text, status, is_error = self._get_javea_weather_snapshot() + + if self.weather_popup and self.weather_popup.winfo_exists(): + self.weather_popup.destroy() + + popup = tk.Toplevel(self) + popup.title('Tiempo en Jávea') + popup.geometry('760x500') + popup.minsize(760, 500) + popup.resizable(False, False) + popup.configure(bg='white') + popup.transient(self) + popup.grab_set() + self.weather_popup = popup + + header = tk.Label(popup, text='OpenWeather • Jávea, España', font=self.font_header, bg='white', fg=TEXT_COLOR) + header.pack(pady=(12, 6)) + info_label = tk.Label( + popup, + text=text, + justify='left', + font=('Consolas', 12), + anchor='nw', + bg='white', + fg='red' if is_error else TEXT_COLOR, + padx=6, + pady=6 + ) + info_label.pack(fill='both', expand=True, padx=18, pady=8) + status_label = tk.Label(popup, text=status, font=self.font_small, bg='white', fg=SUBTEXT_COLOR) + status_label.pack(pady=(0, 8)) + + def close_popup() -> None: + if popup.winfo_exists(): + popup.destroy() + if self.weather_popup is popup: + self.weather_popup = None + + def refresh() -> None: + new_text, new_status, new_is_error = self._get_javea_weather_snapshot() + info_label.config(text=new_text, fg='red' if new_is_error else '#1f2d3d') + status_label.config(text=new_status) + + buttons = tk.Frame(popup, bg='white') + buttons.pack(pady=(4, 14)) + tk.Button(buttons, text='Actualizar', command=refresh, bg=BUTTON_BG, relief='flat').pack(side='left', padx=10) + tk.Button(buttons, text='Cerrar', command=close_popup, bg=BUTTON_BG, relief='flat').pack(side='left', padx=10) + + popup.protocol('WM_DELETE_WINDOW', close_popup) + + def _build_tab_navegador(self) -> None: + tk.Label(self.tab_navegador, text='Abrir URL en navegador externo', bg='white').pack(pady=6) + frame = tk.Frame(self.tab_navegador, bg='white') + frame.pack(pady=6) + self.url_entry = tk.Entry(frame, width=48) + self.url_entry.insert(0, 'https://www.python.org') + self.url_entry.pack(side='left', padx=4) + tk.Button(frame, text='Abrir', command=lambda: self._open_web(self.url_entry.get())).pack(side='left', padx=4) + + def _build_tab_correos(self) -> None: + tk.Label( + self.tab_correos, + text='Área futura para gestionar correos.' + '\nDe momento, utiliza la pestaña "Bloc de notas" para escribir.', + bg='white', + font=('Arial', 12), + justify='left' + ).pack(pady=20) + + def _build_tab_bloc_notas(self) -> None: + tk.Label(self.tab_bloc, text='Bloc de notas', bg='white', font=('Arial', 12, 'bold')).pack(pady=6) + toolbar = tk.Frame(self.tab_bloc, bg='white') + toolbar.pack(fill='x') + tk.Button(toolbar, text='Abrir', command=self._open_text).pack(side='left', padx=4, pady=4) + tk.Button(toolbar, text='Guardar', command=self._save_text).pack(side='left', padx=4, pady=4) + self.editor = scrolledtext.ScrolledText(self.tab_bloc, wrap='word') + self.editor.pack(fill='both', expand=True, padx=6, pady=6) + + def _build_tab_tareas(self) -> None: + container = tk.Frame(self.tab_tareas, bg='white') + container.pack(fill='both', expand=True) + + scraping_frame = tk.Frame(container, bg='white', padx=10, pady=10) + scraping_frame.pack(side='left', fill='both', expand=True) + tk.Label(scraping_frame, text='Análisis de enlaces Wallapop', bg='white', font=('Arial', 12, 'bold')).pack(pady=(12,4)) + + if not (REQUESTS_AVAILABLE and BEAUTIFULSOUP_AVAILABLE): + tk.Label( + scraping_frame, + text='Instala requests y beautifulsoup4 para habilitar el análisis de enlaces.', + bg='white', + fg='red', + wraplength=260, + justify='left' + ).pack(fill='x', padx=10, pady=10) + else: + tk.Label( + scraping_frame, + text='Pulsa el botón "Analizar Wallapop" en el panel izquierdo para introducir la URL de un anuncio. ' + 'Aquí verás notas generales o recordatorios.', + bg='white', + wraplength=260, + justify='left' + ).pack(fill='x', padx=10, pady=10) + + game_frame = tk.Frame(container, bg='white') + game_frame.pack(side='left', fill='both', expand=True, padx=10) + tk.Label(game_frame, text='Juego de camellos (ventana dedicada)', bg='white', font=('Arial', 12, 'bold')).pack(pady=(6,2)) + tk.Label( + game_frame, + text='Abre el simulador en una ventana independiente para ver la carrera a pantalla completa.', + wraplength=260, + justify='left', + bg='white' + ).pack(padx=8, pady=8) + tk.Button(game_frame, text='Abrir juego', command=self._open_game_window).pack(pady=6) + + def _build_tab_alarmas(self) -> None: + self.tab_alarmas.configure(bg=PANEL_BG) + + card = tk.Frame(self.tab_alarmas, bg='#fef9f4', bd=0, padx=18, pady=18) + card.pack(pady=16, padx=20, fill='both', expand=True) + tk.Label(card, text='Programar nuevas alarmas', font=self.font_header, bg='#fef9f4', fg=TEXT_COLOR).pack(pady=(0,8)) + + form = tk.Frame(card, bg='#fef9f4') + form.pack(pady=6) + tk.Label(form, text='Minutos', bg='#fef9f4', font=self.font_body).grid(row=0, column=0, padx=6) + self.alarm_minutes = tk.Spinbox(form, from_=1, to=240, width=6, justify='center') + self.alarm_minutes.grid(row=0, column=1, padx=6) + tk.Label(form, text='Título', bg='#fef9f4', font=self.font_body).grid(row=0, column=2, padx=6) + self.alarm_title = tk.Entry(form, width=24) + self.alarm_title.grid(row=0, column=3, padx=6) + tk.Button(form, text='Agregar alarma', bg=BUTTON_BG, relief='flat', command=self._start_alarm).grid(row=0, column=4, padx=10) + + tk.Label(card, text='Alarmas activas', font=self.font_body, bg='#fef9f4', fg=SUBTEXT_COLOR).pack(pady=(18,6)) + self.alarm_list = tk.Listbox(card, height=8, width=50, bd=0, highlightthickness=1, highlightbackground='#e3e5f0') + self.alarm_list.pack(pady=4, fill='x') + tk.Button(card, text='Cancelar seleccionada', command=self._cancel_selected_alarm, bg=BUTTON_BG, relief='flat').pack(pady=(10,4)) + self.alarm_status = tk.Label(card, text='Sin alarmas programadas', bg='#fef9f4', fg=SUBTEXT_COLOR, font=self.font_small) + self.alarm_status.pack(pady=4) + + def _build_tab_enlaces(self) -> None: + tk.Label(self.tab_enlaces, text='Enlaces útiles', bg='white', font=('Arial', 12, 'bold')).pack(pady=6) + for text, url in [ + ('Documentación Tkinter', 'https://docs.python.org/3/library/tk.html'), + ('psutil', 'https://psutil.readthedocs.io'), + ('Matplotlib', 'https://matplotlib.org') + ]: + tk.Button(self.tab_enlaces, text=text, command=lambda u=url: self._open_web(u)).pack(pady=3) + + def _build_notes_panel(self) -> None: + notes = tk.LabelFrame(self.center_panel, text='Panel de notas e hilos', bg='#eefee8') + notes.grid(row=1, column=0, sticky='nsew', padx=6, pady=(4,6)) + self.notes = scrolledtext.ScrolledText(notes, height=5) + self.notes.pack(fill='both', expand=True, padx=6, pady=6) + + def _build_right_panel(self) -> None: + right = tk.Frame(self, width=280, bg=PANEL_BG, bd=0) + right.grid(row=1, column=2, sticky='nse', padx=6, pady=(50,6)) + right.grid_propagate(False) + + tk.Label(right, text='Chat', font=('Arial', 20, 'bold'), fg='red', bg='white').pack(pady=(6,4)) + self.chat_display = scrolledtext.ScrolledText(right, width=30, height=12, state='disabled') + self.chat_display.pack(padx=6, pady=4) + + tk.Label(right, text='Mensaje', bg='white').pack(anchor='w', padx=6) + self.message_entry = tk.Text(right, height=4) + self.message_entry.pack(padx=6, pady=4) + tk.Button(right, text='enviar', bg='#d6f2ce', command=self._send_chat).pack(pady=4) + + conn = tk.Frame(right, bg='white') + conn.pack(pady=4) + tk.Label(conn, text='Host:', bg='white').grid(row=0, column=0) + self.host_entry = tk.Entry(conn, width=12) + self.host_entry.insert(0, SERVER_HOST_DEFAULT) + self.host_entry.grid(row=0, column=1) + tk.Label(conn, text='Puerto:', bg='white').grid(row=1, column=0) + self.port_entry = tk.Entry(conn, width=6) + self.port_entry.insert(0, str(SERVER_PORT_DEFAULT)) + self.port_entry.grid(row=1, column=1) + tk.Button(conn, text='Conectar', command=self._connect_chat).grid(row=0, column=2, rowspan=2, padx=4) + + tk.Label(right, text='Alumnos', font=('Arial', 14, 'bold'), bg='white').pack(pady=(10,4)) + for name in ('Alumno 1', 'Alumno 2', 'Alumno 3'): + frame = tk.Frame(right, bg='white', bd=1, relief='groove') + frame.pack(fill='x', padx=6, pady=4) + tk.Label(frame, text=name, bg='white', font=('Arial', 11, 'bold')).pack(anchor='w') + tk.Label(frame, text='Lorem ipsum dolor sit amet, consectetur adipiscing elit.', wraplength=220, justify='left', bg='white').pack(anchor='w') + + player = tk.Frame(right, bg='#fdf5f5', bd=1, relief='flat', padx=8, pady=8) + player.pack(fill='x', padx=8, pady=(10,6)) + tk.Label(player, text='Reproductor música', font=self.font_header, bg='#fdf5f5', fg=ACCENT_DARK).pack(pady=(0,6)) + + def styled_btn(parent, text, command): + return tk.Button( + parent, + text=text, + command=command, + bg='#ffe3df', + activebackground='#ffd2ca', + fg=TEXT_COLOR, + relief='flat', + width=20, + pady=4 + ) + + styled_btn(player, 'Seleccionar archivo', self._select_music).pack(pady=3) + action_row = tk.Frame(player, bg='#fdf5f5') + action_row.pack(pady=3) + styled_btn(action_row, 'Pausa', self._pause_music).pack(side='left', padx=4) + styled_btn(action_row, 'Reanudar', self._resume_music).pack(side='left', padx=4) + styled_btn(player, 'Quitar', self._stop_music).pack(pady=3) + + def _build_status_bar(self) -> None: + status = tk.Frame(self, bg='#f1f1f1', bd=2, relief='ridge') + status.grid(row=2, column=0, columnspan=3, sticky='ew') + for idx in range(4): + status.columnconfigure(idx, weight=1) + + tk.Label(status, text='Correos sin leer', font=('Arial', 11, 'bold'), bg='#f1f1f1').grid(row=0, column=0, padx=16, pady=6, sticky='w') + + self.traffic_label = tk.Label( + status, + text='Red: ↑ --.- KB/s ↓ --.- KB/s', + font=('Arial', 11, 'bold'), + bg='#f1f1f1' + ) + self.traffic_label.grid(row=0, column=1, padx=16, pady=6) + + self.weather_label = tk.Label(status, text='Clima: configure API', font=('Arial', 11, 'bold'), bg='#f1f1f1') + self.weather_label.grid(row=0, column=2, padx=16, pady=6, sticky='n') + + self.clock_label = tk.Label(status, text='--:--:--', font=('Arial', 12, 'bold'), bg='#f1f1f1') + self.clock_label.grid(row=0, column=3, padx=16, pady=6, sticky='e') + + # ------------------------ acciones ------------------------ + def _open_web(self, url: str) -> None: + webbrowser.open(url) + + def _launch_process(self, cmd: list[str]) -> None: + try: + subprocess.Popen(cmd) + self._log(f'Proceso lanzado: {cmd}') + self._show_text_result('Aplicaciones', f'Se lanzó el proceso: {" ".join(cmd)}') + except Exception as exc: + messagebox.showerror('Error', f'No se pudo lanzar {cmd}: {exc}') + + def _run_backup_script(self) -> None: + source = filedialog.askdirectory(title='Selecciona la carpeta a respaldar') + if not source: + return + destination = filedialog.askdirectory(title='Selecciona la carpeta destino para la copia') + if not destination: + return + + ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + src_name = os.path.basename(os.path.normpath(source)) or 'backup' + target = os.path.join(destination, f'{src_name}_{ts}') + + if os.path.exists(target): + messagebox.showerror('Backup', f'Ya existe la carpeta destino:\n{target}') + return + + self._log(f'Iniciando copia de seguridad de {source} a {target}') + self._show_text_result('Copias de seguridad', f'Copiando archivos...\nOrigen: {source}\nDestino: {target}') + self.notebook.select(self.tab_resultados) + + def worker() -> None: + try: + copied_files, copied_bytes, skipped = self._copy_directory_with_progress(source, target) + except Exception as exc: + self._scraping_queue.put(('error', f'Backup: {exc}')) + return + summary = ( + f'Copia completada correctamente.\n\n' + f'Origen: {source}\nDestino: {target}\n' + f'Archivos copiados: {copied_files}\n' + f'Tamaño total: {copied_bytes / (1024*1024):.2f} MB' + ) + if skipped: + summary += f"\nArchivos omitidos: {skipped} (consulta el panel de notas)" + self._scraping_queue.put(('backup_success', summary, target)) + + threading.Thread(target=worker, daemon=True).start() + + def _copy_directory_with_progress(self, source: str, target: str) -> tuple[int, int, int]: + copied_files = 0 + copied_bytes = 0 + skipped = 0 + for root, dirs, files in os.walk(source): + rel = os.path.relpath(root, source) + dest_dir = os.path.join(target, rel) if rel != '.' else target + os.makedirs(dest_dir, exist_ok=True) + for file in files: + src_path = os.path.join(root, file) + dest_path = os.path.join(dest_dir, file) + try: + shutil.copy2(src_path, dest_path) + copied_files += 1 + try: + copied_bytes += os.path.getsize(dest_path) + except OSError: + pass + except Exception as exc: + skipped += 1 + self._log(f'Archivo omitido ({src_path}): {exc}') + continue + if copied_files % 20 == 0: + self._log(f'Copia en progreso: {copied_files} archivos...') + return copied_files, copied_bytes, skipped + + def _open_saved_file(self, path: str) -> None: + if not path or not os.path.exists(path): + messagebox.showwarning('Archivo', 'El archivo indicado ya no existe.') + return + try: + if sys.platform.startswith('win'): + os.startfile(path) # type: ignore[attr-defined] + elif sys.platform == 'darwin': + subprocess.Popen(['open', path]) + else: + subprocess.Popen(['xdg-open', path]) + except Exception as exc: + messagebox.showerror('Archivo', f'No se pudo abrir el archivo: {exc}') + + def _show_file_popup(self, path: str) -> None: + popup = tk.Toplevel(self) + popup.title('Archivo generado') + popup.configure(bg='white', padx=16, pady=16) + popup.resizable(False, False) + popup.transient(self) + popup.grab_set() + tk.Label(popup, text='Se creó el archivo con los datos:', font=('Arial', 12, 'bold'), bg='white').pack(anchor='w') + entry = tk.Entry(popup, width=60) + entry.pack(fill='x', pady=8) + entry.insert(0, path) + entry.config(state='readonly') + buttons = tk.Frame(popup, bg='white') + buttons.pack(fill='x', pady=(8,0)) + tk.Button(buttons, text='Abrir archivo', command=lambda p=path: self._open_saved_file(p)).pack(side='left', padx=4) + tk.Button(buttons, text='Cerrar', command=popup.destroy).pack(side='right', padx=4) + + def _open_text(self) -> None: + path = filedialog.askopenfilename(filetypes=[('Texto', '*.txt'), ('Todos', '*.*')]) + if not path: + return + with open(path, 'r', encoding='utf-8', errors='ignore') as fh: + self.editor.delete('1.0', 'end') + self.editor.insert('1.0', fh.read()) + self._log(f'Abriste {path}') + + def _save_text(self) -> None: + path = filedialog.asksaveasfilename(defaultextension='.txt') + if not path: + return + with open(path, 'w', encoding='utf-8') as fh: + fh.write(self.editor.get('1.0', 'end')) + self._log(f'Guardado en {path}') + + def _open_simple_scraping_popup(self) -> None: + if not (REQUESTS_AVAILABLE and BEAUTIFULSOUP_AVAILABLE): + messagebox.showerror('Scraping', 'Instala requests y beautifulsoup4 para usar esta función.') + return + if self.simple_scraping_popup and tk.Toplevel.winfo_exists(self.simple_scraping_popup): + self.simple_scraping_popup.lift() + return + + popup = tk.Toplevel(self) + popup.title('Scraping simple') + popup.configure(bg='white', padx=18, pady=18) + popup.resizable(False, False) + popup.transient(self) + popup.grab_set() + + tk.Label( + popup, + text='Introduce una o varias URL completas (una por línea):', + bg='white', + font=('Arial', 11, 'bold'), + justify='left' + ).pack(anchor='w') + + url_box = scrolledtext.ScrolledText(popup, width=60, height=5) + url_box.pack(pady=(8, 4)) + url_box.insert('1.0', 'https://es.wallapop.com\nhttps://www.wikipedia.org') + + error_label = tk.Label(popup, text='', fg='red', bg='white', wraplength=420, justify='left') + error_label.pack(anchor='w') + + btn_frame = tk.Frame(popup, bg='white') + btn_frame.pack(fill='x', pady=(10, 0)) + + def _close_popup() -> None: + if self.simple_scraping_popup: + try: + self.simple_scraping_popup.destroy() + except Exception: + pass + self.simple_scraping_popup = None + + def _start(_: object | None = None) -> None: + raw = url_box.get('1.0', 'end').strip() + urls = [line.strip() for line in raw.splitlines() if line.strip()] + if not urls: + error_label.config(text='Añade al menos una URL completa.') + return + invalid = [u for u in urls if not u.startswith(('http://', 'https://'))] + if invalid: + error_label.config(text=f'Las URL deben empezar por http:// o https:// (revisa: {invalid[0]})') + return + _close_popup() + self._start_simple_scraping(urls) + + tk.Button(btn_frame, text='Iniciar', command=_start, bg='#d6f2ce').pack(side='left', padx=4) + tk.Button(btn_frame, text='Cancelar', command=_close_popup).pack(side='right', padx=4) + url_box.bind('', _start) + popup.bind('', _start) + popup.protocol('WM_DELETE_WINDOW', _close_popup) + self.simple_scraping_popup = popup + url_box.focus_set() + + def _start_simple_scraping(self, urls: list[str]) -> None: + if not (REQUESTS_AVAILABLE and BEAUTIFULSOUP_AVAILABLE): + messagebox.showerror('Scraping', 'Instala requests y beautifulsoup4 para usar esta función.') + return + cleaned = [url.strip() for url in urls if url.strip()] + if not cleaned: + messagebox.showinfo('Scraping', 'No hay URL válidas para analizar.') + return + self._log(f'Scraping simple para {len(cleaned)} URL(s)') + preview = 'Analizando las siguientes URL:\n' + '\n'.join(f'• {url}' for url in cleaned) + self._show_text_result('Scraping simple', preview) + self.notebook.select(self.tab_resultados) + threading.Thread(target=self._simple_scraping_runner, args=(cleaned,), daemon=True).start() + + def _simple_scraping_runner(self, urls: list[str]) -> None: + results: list[dict[str, str]] = [] + lock = threading.Lock() + threads: list[threading.Thread] = [] + for url in urls: + t = threading.Thread(target=self._simple_scraping_worker, args=(url, results, lock), daemon=True) + t.start() + threads.append(t) + for thread in threads: + thread.join() + self._scraping_queue.put(('generic_success', urls, results)) + + def _simple_scraping_worker(self, url: str, results: list[dict[str, str]], lock: threading.Lock) -> None: + timestamp = datetime.datetime.now().strftime('%H:%M:%S') + if not (requests and BEAUTIFULSOUP_AVAILABLE): + data = { + 'url': url, + 'error': 'Dependencias de scraping no disponibles', + 'timestamp': timestamp + } + else: + try: + time.sleep(1) + headers = { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + } + response = requests.get(url, headers=headers, timeout=10) + response.raise_for_status() + soup = BeautifulSoup(response.text, 'html.parser') + title = soup.title.string.strip() if soup.title and soup.title.string else 'Sin título' + num_links = len(soup.find_all('a')) + num_images = len(soup.find_all('img')) + num_paragraphs = len(soup.find_all('p')) + meta_desc = '' + meta_tag = soup.find('meta', attrs={'name': 'description'}) + if meta_tag and meta_tag.get('content'): + content = meta_tag['content'] + meta_desc = content[:100] + '...' if len(content) > 100 else content + data = { + 'url': url, + 'titulo': title, + 'descripcion': meta_desc, + 'num_links': num_links, + 'num_imagenes': num_images, + 'num_parrafos': num_paragraphs, + 'longitud': len(response.text), + 'status_code': response.status_code, + 'timestamp': timestamp + } + except Exception as exc: + data = { + 'url': url, + 'error': str(exc), + 'timestamp': timestamp + } + with lock: + results.append(data) + + # --- Funcionalidad de Web Scraping (Wallapop) --- + def _open_wallapop_popup(self) -> None: + if not (REQUESTS_AVAILABLE and BEAUTIFULSOUP_AVAILABLE): + messagebox.showerror('Wallapop', 'Instala requests y beautifulsoup4 para analizar enlaces.') + return + + if self.scraping_popup and tk.Toplevel.winfo_exists(self.scraping_popup): + self.scraping_popup.lift() + return + + popup = tk.Toplevel(self) + popup.title('Búsqueda Wallapop') + popup.configure(bg='white', padx=18, pady=18) + popup.resizable(False, False) + popup.transient(self) + popup.grab_set() + + tk.Label( + popup, + text='Escribe el producto que quieres buscar en Wallapop:', + bg='white', + font=('Arial', 11, 'bold') + ).pack(anchor='w') + entry = tk.Entry(popup, width=60) + entry.pack(pady=(8, 4)) + entry.insert(0, 'PlayStation 5') + + controls = tk.Frame(popup, bg='white') + controls.pack(fill='x', pady=(2, 0)) + tk.Label(controls, text='Resultados a mostrar:', bg='white').pack(side='left') + results_spin = tk.Spinbox(controls, from_=1, to=20, width=5, justify='center') + results_spin.pack(side='left', padx=(6, 0)) + results_spin.delete(0, 'end') + results_spin.insert(0, '5') + + error_label = tk.Label(popup, text='', fg='red', bg='white', wraplength=380, justify='left') + error_label.pack(anchor='w') + + btn_frame = tk.Frame(popup, bg='white') + btn_frame.pack(fill='x', pady=(10, 0)) + + def _close_popup() -> None: + if self.scraping_popup: + try: + self.scraping_popup.destroy() + except Exception: + pass + self.scraping_popup = None + + def _start(_: object | None = None) -> None: + query = entry.get().strip() + if not query: + error_label.config(text='Necesitas introducir un producto a buscar.') + return + try: + limit = max(1, min(20, int(results_spin.get()))) + except ValueError: + error_label.config(text='Selecciona un número válido de resultados.') + return + _close_popup() + self._start_wallapop_search(query, limit) + + tk.Button(btn_frame, text='Buscar', command=_start, bg='#d6f2ce').pack(side='left', padx=4) + tk.Button(btn_frame, text='Cancelar', command=_close_popup).pack(side='right', padx=4) + entry.bind('', _start) + popup.protocol('WM_DELETE_WINDOW', _close_popup) + self.scraping_popup = popup + entry.focus_set() + + def _start_wallapop_search(self, query: str, limit: int) -> None: + if not (REQUESTS_AVAILABLE and BEAUTIFULSOUP_AVAILABLE): + messagebox.showerror('Wallapop', 'Instala requests y beautifulsoup4 para usar esta función.') + return + clean_query = query.strip() + if not clean_query: + messagebox.showerror('Wallapop', 'Escribe el producto que quieres buscar.') + return + max_results = max(1, min(20, int(limit))) + self._log(f'Buscando en Wallapop: "{clean_query}" (máx. {max_results})') + self._show_text_result( + 'Wallapop', + f'Buscando "{clean_query}" en Wallapop...\nMostrando hasta {max_results} resultados.' + ) + self.notebook.select(self.tab_resultados) + threading.Thread( + target=self._wallapop_search_worker, + args=(clean_query, max_results), + daemon=True + ).start() + + def _wallapop_search_worker(self, query: str, limit: int) -> None: + try: + results = self._fetch_wallapop_search_results(query, limit) + if not results: + raise ValueError('No se encontraron anuncios para tu búsqueda.') + self._scraping_queue.put(('search_success', query, results)) + except Exception as exc: + self._scraping_queue.put(('search_error', str(exc))) + + def _fetch_wallapop_search_results(self, query: str, limit: int) -> list[dict[str, str]]: + errors: list[str] = [] + for fetcher in (self._fetch_wallapop_search_via_api, self._fetch_wallapop_search_via_html): + try: + results = fetcher(query, limit) + if results: + return results[:limit] + except Exception as exc: + errors.append(str(exc)) + if errors: + raise ValueError('No se pudieron obtener resultados de Wallapop.\n' + '\n'.join(errors)) + return [] + + def _fetch_wallapop_search_via_api(self, query: str, limit: int) -> list[dict[str, str]]: + if not requests: + return [] + + def _base_params() -> dict[str, object]: + params: dict[str, object] = { + 'keywords': query, + 'source': 'search_box', + 'filters_source': 'quick_filters', + 'order_by': 'most_relevance', + 'latitude': f'{WALLAPOP_LATITUDE:.6f}', + 'longitude': f'{WALLAPOP_LONGITUDE:.6f}', + 'country_code': WALLAPOP_COUNTRY_CODE, + 'language': WALLAPOP_LANGUAGE + } + if WALLAPOP_CATEGORY_ID: + params['category_id'] = WALLAPOP_CATEGORY_ID + return params + + results: list[dict[str, str]] = [] + seen_ids: set[str] = set() + next_page: str | None = None + attempts = 0 + while len(results) < limit and attempts < 6: + params = {'next_page': next_page} if next_page else _base_params() + resp = requests.get( + 'https://api.wallapop.com/api/v3/search', + params=params, + headers=WALLAPOP_HEADERS, + timeout=20 + ) + try: + resp.raise_for_status() + except requests.HTTPError as exc: # type: ignore[attr-defined] + if resp.status_code == 403: + raise ValueError( + 'Wallapop devolvió 403. Asegúrate de definir WALLAPOP_DEVICE_ID/WALLAPOP_MPID ' + 'con valores obtenidos desde tu navegador.' + ) from exc + raise + payload = resp.json() + items = self._extract_wallapop_items(payload) + if not items: + break + for entry in items: + if not isinstance(entry, dict): + continue + item_id = entry.get('id') or entry.get('itemId') or entry.get('item_id') + if not item_id: + continue + item_id = str(item_id) + if item_id in seen_ids: + continue + seen_ids.add(item_id) + slug = entry.get('web_slug') or entry.get('slug') or item_id + seller_info = entry.get('seller') if isinstance(entry.get('seller'), dict) else None + detail = self._format_wallapop_item(entry, self._build_wallapop_url(str(slug)), seller_info) + results.append(detail) + if len(results) >= limit: + break + next_page = self._extract_wallapop_next_page(payload) + if not next_page: + break + attempts += 1 + return results + + def _fetch_wallapop_search_via_html(self, query: str, limit: int) -> list[dict[str, str]]: + if not (requests and BEAUTIFULSOUP_AVAILABLE): + return [] + params = {'keywords': query} + resp = requests.get( + 'https://es.wallapop.com/app/search', + params=params, + headers=WALLAPOP_HEADERS, + timeout=20 + ) + resp.raise_for_status() + soup = BeautifulSoup(resp.text, 'html.parser') + script = soup.find('script', id='__NEXT_DATA__') + if not script or not script.string: + return [] + data = json.loads(script.string) + page_props = data.get('props', {}).get('pageProps', {}) + items: list[dict[str, object]] = [] + for key in ('searchResults', 'results', 'items'): + candidate = page_props.get(key) + if isinstance(candidate, list): + items = candidate + break + if not items: + initial_state = page_props.get('initialState', {}) + if isinstance(initial_state, dict): + for key in ('items', 'results'): + candidate = initial_state.get(key) + if isinstance(candidate, list): + items = candidate + break + if not items: + search_state = initial_state.get('search') + if isinstance(search_state, dict): + candidate = search_state.get('items') + if isinstance(candidate, list): + items = candidate + elif isinstance(search_state.get('search'), dict): + nested = search_state['search'].get('items') + if isinstance(nested, list): + items = nested + if not items: + return [] + results: list[dict[str, str]] = [] + seen: set[str] = set() + for entry in items: + if not isinstance(entry, dict): + continue + slug = ( + entry.get('slug') + or entry.get('webSlug') + or entry.get('web_slug') + or entry.get('shareLink') + or entry.get('share_link') + or entry.get('url') + ) + url = self._build_wallapop_url(str(slug)) if slug else '' + item_id = entry.get('id') or entry.get('itemId') or entry.get('item_id') + entry_key = str(item_id or url) + if not entry_key or entry_key in seen: + continue + seen.add(entry_key) + detail: dict[str, str] | None = None + if url: + try: + detail = self._fetch_wallapop_item(url) + except Exception: + detail = None + if not detail and item_id: + try: + detail = self._fetch_wallapop_item_from_api(str(item_id)) + except Exception: + detail = None + if detail: + if url and not detail.get('link'): + detail['link'] = url + results.append(detail) + if len(results) >= limit: + break + return results + + def _extract_wallapop_items(self, payload: dict[str, object]) -> list[dict[str, object]]: + items: list[dict[str, object]] = [] + data_block = payload.get('data') + if isinstance(data_block, dict): + section = data_block.get('section') + if isinstance(section, dict): + section_payload = section.get('payload') + if isinstance(section_payload, dict): + raw_items = section_payload.get('items') or section_payload.get('section_items') + if isinstance(raw_items, list): + items.extend(item for item in raw_items if isinstance(item, dict)) + elif isinstance(data_block.get('items'), list): + items.extend(item for item in data_block['items'] if isinstance(item, dict)) + if not items: + web_results = payload.get('web_search_results') + if isinstance(web_results, dict): + sections = web_results.get('sections') + if isinstance(sections, list): + for section in sections: + if not isinstance(section, dict): + continue + raw_items = section.get('items') or section.get('section_items') + if isinstance(raw_items, list): + items.extend(item for item in raw_items if isinstance(item, dict)) + return items + + def _extract_wallapop_next_page(self, payload: dict[str, object]) -> str | None: + meta = payload.get('meta') + if isinstance(meta, dict): + token = meta.get('next_page') + if isinstance(token, str) and token: + return token + headers = payload.get('headers') + if isinstance(headers, dict): + token = headers.get('X-NextPage') or headers.get('x-nextpage') + if isinstance(token, str) and token: + return token + return None + + def _fetch_wallapop_item(self, url: str) -> dict[str, str]: + if not requests or not BEAUTIFULSOUP_AVAILABLE: + raise RuntimeError('Dependencias de scraping no disponibles') + resp = requests.get(url, headers=WALLAPOP_HEADERS, timeout=20) + resp.raise_for_status() + soup = BeautifulSoup(resp.text, 'html.parser') + script = soup.find('script', id='__NEXT_DATA__') + if not script or not script.string: + raise ValueError('No se pudo encontrar la información del anuncio (sin datos Next.js).') + data = json.loads(script.string) + page_props = data.get('props', {}).get('pageProps', {}) + item = page_props.get('item') or page_props.get('itemInfo', {}).get('item') + if not item: + raise ValueError('El formato de la página cambió y no se pudo leer el anuncio.') + seller_info = page_props.get('itemSeller') if isinstance(page_props.get('itemSeller'), dict) else None + return self._format_wallapop_item(item, url, seller_info) + + def _fetch_wallapop_item_from_api(self, item_id: str) -> dict[str, str]: + if not requests: + raise RuntimeError('Dependencias de scraping no disponibles') + api_url = f'https://api.wallapop.com/api/v3/items/{item_id}' + resp = requests.get(api_url, headers=WALLAPOP_HEADERS, timeout=15) + resp.raise_for_status() + data = resp.json() + item = data.get('item') if isinstance(data.get('item'), dict) else data + if not isinstance(item, dict): + raise ValueError('Respuesta del API sin datos del artículo.') + slug = item.get('web_slug') or item.get('slug') or item.get('share_link') + link = self._build_wallapop_url(str(slug)) if slug else f'https://es.wallapop.com/item/{item_id}' + seller_info = data.get('seller') if isinstance(data.get('seller'), dict) else item.get('seller') + seller_dict = seller_info if isinstance(seller_info, dict) else None + return self._format_wallapop_item(item, link, seller_dict) + + def _build_wallapop_url(self, slug_or_url: str) -> str: + if not slug_or_url: + return '' + text = slug_or_url.strip() + if text.startswith('http://') or text.startswith('https://'): + return text + text = text.strip('/') + if not text.startswith('item/'): + text = f'item/{text}' + return f'https://es.wallapop.com/{text}' + + def _format_wallapop_item( + self, + item: dict[str, object], + url: str, + seller_info: dict[str, object] | None = None + ) -> dict[str, str]: + title_field = item.get('title') + if isinstance(title_field, dict): + title = title_field.get('original') or title_field.get('translated') or '(Sin título)' + elif isinstance(title_field, str): + title = title_field or '(Sin título)' + else: + title = '(Sin título)' + + price_text = 'Precio no disponible' + price_data = item.get('price') + price_cash = None + if isinstance(price_data, dict): + if isinstance(price_data.get('cash'), dict): + price_cash = price_data['cash'] + else: + price_cash = price_data + if isinstance(price_cash, dict): + amount = price_cash.get('amount') + currency = price_cash.get('currency') or 'EUR' + if amount is not None: + price_text = f'{amount} {currency}'.strip() + elif isinstance(price_data, (int, float, str)): + price_text = f'{price_data} EUR' + + desc_field = item.get('description') + if isinstance(desc_field, dict): + description = (desc_field.get('original') or desc_field.get('translated') or '').strip() + elif isinstance(desc_field, str): + description = desc_field.strip() + else: + description = '' + + location = item.get('location') if isinstance(item.get('location'), dict) else {} + location_parts = [] + if isinstance(location, dict): + for part in (location.get('city'), location.get('postalCode'), location.get('regionName')): + if part: + location_parts.append(part) + location_text = ', '.join(location_parts) if location_parts else 'Ubicación no disponible' + + seller_name = '' + for candidate in (seller_info, item.get('seller'), item.get('user'), item.get('userInfo')): + if isinstance(candidate, dict): + seller_name = ( + candidate.get('microName') + or candidate.get('name') + or candidate.get('userName') + or '' + ) + if seller_name: + break + if not seller_name: + seller_name = 'Vendedor no disponible' + + categories = ', '.join( + cat.get('name') + for cat in item.get('taxonomies', []) + if isinstance(cat, dict) and cat.get('name') + ) or 'Sin categoría' + + car_info = item.get('carInfo') if isinstance(item.get('carInfo'), dict) else {} + car_bits: list[str] = [] + if isinstance(car_info, dict): + for key in ('year', 'km', 'engine', 'gearBox'): + field = car_info.get(key) + text = None + if isinstance(field, dict): + text = field.get('text') or field.get('iconText') + elif isinstance(field, str): + text = field + if text: + car_bits.append(text) + extra = ', '.join(car_bits) + + images = item.get('images') if isinstance(item.get('images'), list) else [] + image_url = '' + for img in images: + if not isinstance(img, dict): + continue + urls = img.get('urls') if isinstance(img.get('urls'), dict) else {} + if isinstance(urls, dict): + image_url = urls.get('medium') or urls.get('big') or urls.get('small') or '' + else: + image_url = img.get('url', '') if isinstance(img.get('url'), str) else '' + if image_url: + break + + return { + 'title': str(title), + 'price': price_text, + 'description': description, + 'link': url, + 'location': location_text, + 'seller': seller_name, + 'category': categories, + 'extra': extra, + 'image': image_url + } + + def _process_scraping_queue(self) -> None: + if not self._running: + return + try: + while True: + status, *payload = self._scraping_queue.get_nowait() + if status == 'error': + message = payload[0] if payload else 'Error desconocido' + self._handle_scraping_error(str(message)) + elif status == 'search_success': + query, data = payload + self._handle_wallapop_search_success(str(query), list(data)) + elif status == 'search_error': + message = payload[0] if payload else 'Error desconocido' + self._handle_scraping_error(str(message)) + elif status == 'generic_success': + urls, data = payload + self._handle_generic_scraping_success(list(urls), list(data)) + elif status == 'backup_success': + summary, target = payload + self._handle_backup_success(str(summary), str(target)) + except queue.Empty: + pass + finally: + if self._running: + self.after(100, self._process_scraping_queue) + + def _handle_wallapop_search_success(self, query: str, results: list[dict[str, str]]) -> None: + count = len(results) + self._log(f'Se encontraron {count} anuncio(s) para "{query}" en Wallapop') + + def builder(parent: tk.Frame) -> None: + frame = tk.Frame(parent, bg='white') + frame.pack(fill='both', expand=True, padx=12, pady=12) + summary = tk.Label( + frame, + text=f'{count} resultado(s) para "{query}"', + font=('Arial', 14, 'bold'), + bg='white', + fg='#2c3e50' + ) + summary.pack(anchor='w', pady=(0, 6)) + box = scrolledtext.ScrolledText(frame, wrap='word') + box.pack(fill='both', expand=True) + blocks: list[str] = [] + for idx, item in enumerate(results, start=1): + block_lines = [ + f'{idx}. {item.get("title", "Sin título")}', + f'Precio: {item.get("price", "N/D")}', + f'Vendedor: {item.get("seller", "N/D")}', + f'Ubicación: {item.get("location", "N/D")}', + f'Categoría: {item.get("category", "N/D")}' + ] + extra = item.get('extra', '').strip() + if extra: + block_lines.append(f'Detalles: {extra}') + description = (item.get('description') or '').strip() + if description: + block_lines.append('Descripción:') + block_lines.append(description) + block_lines.append(f'Enlace: {item.get("link", "N/D")}') + block_lines.append('-' * 60) + blocks.append('\n'.join(block_lines)) + box.insert('1.0', '\n\n'.join(blocks)) + box.config(state='disabled') + + self._render_results_view('Resultados Wallapop', builder) + self.notebook.select(self.tab_resultados) + + try: + path = self._save_wallapop_results_to_file(query, results) + except Exception as exc: + self._log(f'[ERROR] No se pudo guardar el archivo: {exc}') + messagebox.showerror('Wallapop', f'No se pudo guardar el archivo: {exc}') + return + + message = ( + f'Se guardó el archivo con los resultados en:\n{path}\n\n' + '¿Quieres abrirlo ahora?' + ) + if messagebox.askyesno('Wallapop', message): + self._open_saved_file(path) + else: + self._log(f'Archivo de resultados guardado en {path}') + + def _handle_generic_scraping_success(self, urls: list[str], results: list[dict[str, str]]) -> None: + self._log(f'Scraping simple completado para {len(results)} URL(s)') + order = {url: idx for idx, url in enumerate(urls)} + ordered = sorted(results, key=lambda item: order.get(item.get('url', ''), len(order))) + + def builder(parent: tk.Frame) -> None: + frame = tk.Frame(parent, bg='white') + frame.pack(fill='both', expand=True, padx=12, pady=12) + summary = tk.Label( + frame, + text='Resultados de scraping simple', + font=('Arial', 14, 'bold'), + bg='white' + ) + summary.pack(anchor='w', pady=(0, 6)) + box = scrolledtext.ScrolledText(frame, wrap='word') + box.pack(fill='both', expand=True) + blocks: list[str] = [] + for idx, item in enumerate(ordered, start=1): + lines = [f'{idx}. {item.get("url", "URL desconocida")}', f"Hora: {item.get('timestamp', 'N/D')}" ] + if 'error' in item: + lines.append(f"Error: {item.get('error')}") + else: + lines.extend([ + f"Título: {item.get('titulo', 'Sin título')}", + f"Descripción: {item.get('descripcion', '')}", + f"Estado HTTP: {item.get('status_code', 'N/D')}", + f"Longitud HTML: {item.get('longitud', 0)} caracteres", + f"Links: {item.get('num_links', 0)} | Imágenes: {item.get('num_imagenes', 0)} | Párrafos: {item.get('num_parrafos', 0)}" + ]) + lines.append('-' * 60) + blocks.append('\n'.join(lines)) + box.insert('1.0', '\n\n'.join(blocks) if blocks else 'No se generaron resultados.') + box.config(state='disabled') + + self._render_results_view('Scraping simple', builder) + self.notebook.select(self.tab_resultados) + + def _handle_backup_success(self, summary: str, target: str) -> None: + self._log('Copia de seguridad finalizada') + self._show_text_result('Copias de seguridad', summary) + self.notebook.select(self.tab_resultados) + if messagebox.askyesno('Backup', f'{summary}\n\n¿Quieres abrir la carpeta destino?'): + self._open_saved_file(target) + + def _save_wallapop_results_to_file(self, query: str, results: list[dict[str, str]]) -> str: + timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f'wallapop_search_{timestamp}.txt' + path = os.path.join(tempfile.gettempdir(), filename) + lines = [ + f'Consulta: {query}', + f'Resultados: {len(results)}', + f'Generado: {datetime.datetime.now():%Y-%m-%d %H:%M:%S}', + '' + ] + for idx, item in enumerate(results, start=1): + lines.append(f'Resultado {idx}') + lines.append(f'Título: {item.get("title", "Sin título")}') + lines.append(f'Precio: {item.get("price", "N/D")}') + lines.append(f'Vendedor: {item.get("seller", "N/D")}') + lines.append(f'Ubicación: {item.get("location", "N/D")}') + lines.append(f'Categoría: {item.get("category", "N/D")}') + if item.get('extra'): + lines.append(f'Detalles: {item.get("extra")}') + description = (item.get('description') or '').strip() + if description: + lines.append('Descripción:') + lines.append(description) + lines.append(f'Enlace: {item.get("link", "N/D")}') + if item.get('image'): + lines.append(f'Imagen: {item.get("image")}') + lines.append('-' * 60) + with open(path, 'w', encoding='utf-8') as fh: + fh.write('\n'.join(lines)) + return path + + def _handle_scraping_error(self, message: str) -> None: + self._log(f'[ERROR] Wallapop: {message}') + messagebox.showerror('Wallapop', message) + + # --- Fin Web Scraping --- + + + + def _open_game_window(self) -> None: + self._render_results_view('Carrera de camellos', self._build_camel_race_view) + + def _build_camel_race_view(self, parent: tk.Frame) -> None: + container = tk.Frame(parent, bg='white') + container.pack(fill='both', expand=True) + tk.Label( + container, + text='Juego de camellos (sincronización de hilos)', + font=('Arial', 18, 'bold'), + bg='white' + ).pack(pady=(16, 8)) + canvas = tk.Canvas(container, height=380, bg='#fdfdfd', bd=2, relief='sunken') + canvas.pack(fill='both', expand=True, padx=16, pady=12) + status = tk.Label(container, text='Listo para correr', font=('Arial', 14, 'bold'), bg='white', fg='#2c3e50') + status.pack(pady=(0, 10)) + controls = tk.Frame(container, bg='white') + controls.pack(pady=(0, 10)) + + tk.Button( + controls, + text='Iniciar carrera', + font=('Arial', 13, 'bold'), + width=16, + command=lambda: self._start_race(canvas, status) + ).grid(row=0, column=0, padx=12, pady=4) + tk.Button( + controls, + text='Volver a resultados', + font=('Arial', 13), + width=16, + command=self._reset_results_view + ).grid(row=0, column=1, padx=12, pady=4) + + tk.Label(controls, text='Número de camellos:', bg='white', font=('Arial', 12)).grid(row=1, column=0, pady=6) + camel_spin = tk.Spinbox( + controls, + from_=3, + to=8, + width=5, + justify='center', + font=('Arial', 12), + command=lambda: self._set_camel_count(int(camel_spin.get())) + ) + camel_spin.grid(row=1, column=1, pady=6) + camel_spin.delete(0, 'end') + camel_spin.insert(0, str(self.game_camel_count)) + + tk.Label(controls, text='Velocidad (1-5):', bg='white', font=('Arial', 12)).grid(row=2, column=0, pady=6) + speed_scale = tk.Scale( + controls, + from_=1, + to=5, + orient='horizontal', + resolution=0.5, + length=180, + bg='white', + command=lambda value: self._set_speed_factor(float(value)) + ) + speed_scale.grid(row=2, column=1, pady=6) + speed_scale.set(self.game_speed_factor) + + self.game_window_canvas = canvas + self.game_window_status = status + + def _set_camel_count(self, value: int) -> None: + self.game_camel_count = max(3, min(8, int(value))) + + def _set_speed_factor(self, value: float) -> None: + self.game_speed_factor = max(1.0, min(5.0, float(value))) + + def _start_race(self, canvas=None, status_label=None) -> None: + target_canvas = canvas or self.game_window_canvas + if target_canvas is None: + messagebox.showinfo('Juego', 'Abre el juego para iniciar la carrera.') + return + self._stop_game_queue_processor() + target_canvas.delete('all') + target_canvas.update_idletasks() + width = target_canvas.winfo_width() or int(target_canvas.cget('width')) + height = target_canvas.winfo_height() or int(target_canvas.cget('height')) + finish = width - 40 + camel_count = max(3, min(8, int(self.game_camel_count))) + spacing = height / (camel_count + 1) + palette = ['#ff7675', '#74b9ff', '#55efc4', '#f1c40f', '#9b59b6', '#1abc9c', '#e67e22', '#95a5a6'] + racers = [] + for idx in range(camel_count): + y = spacing * (idx + 1) + target_canvas.create_line(20, y, finish, y, dash=(3,4)) + color = palette[idx % len(palette)] + rect = target_canvas.create_rectangle(30, y-22, 130, y+22, fill=color) + racers.append(rect) + + status = status_label or self.game_window_status + if status: + status.config(text='¡Carrera en curso!') + + self.game_move_queue = queue.Queue() + self.game_done_event = threading.Event() + self.game_finish_line = finish + self.game_racer_names = [f'Camello {i+1}' for i in range(camel_count)] + self.game_queue_processor_active = True + self.after(30, self._process_game_queue) + + indices = list(range(camel_count)) + random.shuffle(indices) + for idx in indices: + rect = racers[idx] + threading.Thread(target=self._race_worker, args=(idx, rect), daemon=True).start() + + def _race_worker(self, index: int, rect_id: int) -> None: + while True: + if not self.game_queue_processor_active or not self.game_move_queue or not self.game_done_event: + return + if self.game_done_event.is_set(): + return + base_min = 3 + base_max = 9 + speed = max(1.0, float(self.game_speed_factor)) + step = random.randint(int(base_min * speed), max(int(base_max * speed), int(base_min * speed) + 1)) + self.game_move_queue.put(('move', (rect_id, step, index))) + sleep_time = max(0.02, 0.08 / speed) + time.sleep(random.uniform(sleep_time * 0.5, sleep_time * 1.2)) + + def _process_game_queue(self) -> None: + if not self.game_queue_processor_active or not self.game_move_queue: + return + canvas = self.game_window_canvas + if not canvas: + return + try: + while True: + action, payload = self.game_move_queue.get_nowait() + if action == 'move': + rect_id, step, idx = payload + try: + canvas.move(rect_id, step, 0) + except Exception: + continue + coords = canvas.coords(rect_id) + if coords and coords[2] >= self.game_finish_line and self.game_done_event and not self.game_done_event.is_set(): + self.game_done_event.set() + if self.game_window_status: + self.game_window_status.config(text=f'{self.game_racer_names[idx]} ganó la carrera') + else: + continue + except queue.Empty: + pass + if not self.game_done_event or not self.game_done_event.is_set(): + if self.game_queue_processor_active: + self.after(30, self._process_game_queue) + else: + self.game_queue_processor_active = False + + def _stop_game_queue_processor(self) -> None: + self.game_queue_processor_active = False + self.game_move_queue = None + if self.game_done_event: + try: + self.game_done_event.set() + except Exception: + pass + self.game_done_event = None + + def _start_alarm(self) -> None: + try: + minutes = max(1, int(self.alarm_minutes.get())) + except ValueError: + messagebox.showwarning('Alarmas', 'Ingresa un número válido de minutos.') + return + title = self.alarm_title.get().strip() or f'Alarma {self.alarm_counter}' + end_time = datetime.datetime.now() + datetime.timedelta(minutes=minutes) + alarm = {'id': self.alarm_counter, 'title': title, 'end': end_time} + self.alarm_counter += 1 + self.active_alarms.append(alarm) + self.alarm_title.delete(0, 'end') + self._update_alarm_list() + self._log(f'Alarma "{title}" programada para las {end_time.strftime("%H:%M:%S")}') + + def _refresh_alarms_loop(self) -> None: + if not self._running: + return + now = datetime.datetime.now() + triggered: list[dict[str, datetime.datetime | str]] = [] + for alarm in list(self.active_alarms): + remaining = (alarm['end'] - now).total_seconds() # type: ignore[arg-type] + if remaining <= 0: + triggered.append(alarm) + for alarm in triggered: + self.active_alarms.remove(alarm) + self._trigger_alarm(alarm) + self._update_alarm_list() + self.after(1000, self._refresh_alarms_loop) + + def _update_alarm_list(self) -> None: + if not hasattr(self, 'alarm_list'): + return + selection = self.alarm_list.curselection() + selected_id = None + if selection: + idx = selection[0] + if idx < len(self.active_alarms): + selected_id = self.active_alarms[idx]['id'] + + self.alarm_list.delete(0, 'end') + if not self.active_alarms: + self.alarm_status.config(text='Sin alarmas programadas') + return + now = datetime.datetime.now() + for index, alarm in enumerate(self.active_alarms): + remaining = max(0, int((alarm['end'] - now).total_seconds())) # type: ignore[arg-type] + minutes, seconds = divmod(remaining, 60) + self.alarm_list.insert('end', f"{alarm['title']} - {minutes:02d}:{seconds:02d}") + if selected_id is not None and alarm['id'] == selected_id: + self.alarm_list.selection_set(index) + self.alarm_status.config(text=f"{len(self.active_alarms)} alarma(s) activas") + + def _trigger_alarm(self, alarm: dict[str, datetime.datetime | str]) -> None: + self._log(f"Alarma '{alarm['title']}' activada") + try: + self.bell() + except Exception: + pass + self._show_alarm_popup(str(alarm['title'])) + + def _show_alarm_popup(self, title: str) -> None: + popup = tk.Toplevel(self) + popup.title('Alarma activa') + popup.configure(bg='#ffeaea', padx=20, pady=20) + popup.transient(self) + popup.grab_set() + tk.Label(popup, text='¡Tiempo cumplido!', font=('Arial', 16, 'bold'), fg='#c0392b', bg='#ffeaea').pack(pady=6) + tk.Label(popup, text=title, font=('Arial', 13), bg='#ffeaea').pack(pady=4) + tk.Button(popup, text='Detener alarma', command=popup.destroy, bg='#f9dede').pack(pady=10) + + def _cancel_selected_alarm(self) -> None: + if not self.active_alarms: + messagebox.showinfo('Alarmas', 'No hay alarmas para cancelar.') + return + selection = self.alarm_list.curselection() + if not selection: + messagebox.showinfo('Alarmas', 'Selecciona una alarma en la lista.') + return + idx = selection[0] + if idx >= len(self.active_alarms): + messagebox.showwarning('Alarmas', 'La selección ya no es válida.') + return + alarm = self.active_alarms.pop(idx) + self._update_alarm_list() + self._log(f"Alarma '{alarm['title']}' cancelada") + + def _select_music(self) -> None: + if not pygame_available: + messagebox.showwarning('Música', 'Instale pygame para reproducir audio.') + return + path = filedialog.askopenfilename( + filetypes=[ + ('Audio/Video', '*.mp3 *.wav *.ogg *.mp4 *.mkv *.mov *.avi *.m4a *.webm'), + ('Todos', '*.*') + ] + ) + if not path: + return + + self._cleanup_temp_audio() + + ext = os.path.splitext(path)[1].lower() + direct_audio = {'.mp3', '.wav', '.ogg', '.m4a'} + playable_path = path + temp_path = None + + if ext not in direct_audio: + tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.wav') + temp_path = tmp.name + tmp.close() + cmd = ['ffmpeg', '-y', '-i', path, '-vn', '-acodec', 'pcm_s16le', '-ar', '44100', '-ac', '2', temp_path] + try: + # Usar subprocess.run para esperar la finalización de ffmpeg + subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + playable_path = temp_path + except FileNotFoundError: + if os.path.exists(temp_path): + os.unlink(temp_path) + messagebox.showerror('Música', 'ffmpeg no está instalado. Instálalo para extraer el audio de videos.') + return + except subprocess.CalledProcessError as exc: + if os.path.exists(temp_path): + os.unlink(temp_path) + messagebox.showerror('Música', f'No se pudo extraer el audio: {exc}') + return + + pygame.mixer.init() + pygame.mixer.music.load(playable_path) + pygame.mixer.music.play(-1) + self.music_temp_file = temp_path + self._log(f'Reproduciendo {os.path.basename(path)}') + + def _stop_music(self) -> None: + if pygame_available and pygame.mixer.get_init(): + pygame.mixer.music.stop() + self._cleanup_temp_audio() + + def _pause_music(self) -> None: + if pygame_available and pygame.mixer.get_init(): + pygame.mixer.music.pause() + + def _resume_music(self) -> None: + if pygame_available and pygame.mixer.get_init(): + pygame.mixer.music.unpause() + + def _cleanup_temp_audio(self) -> None: + if self.music_temp_file and os.path.exists(self.music_temp_file): + try: + os.remove(self.music_temp_file) + except OSError: + pass + self.music_temp_file = None + + def _resolve_openweather_key(self) -> str: + key = (self.weather_api_key or '').strip() + if not key: + raise RuntimeError('Configure OPENWEATHER_API_KEY u OPENWEATHER_FALLBACK_API_KEY') + return key + + def _fetch_weather_by_coordinates(self, lat: float, lon: float) -> dict[str, Any]: + if not REQUESTS_AVAILABLE: + raise RuntimeError('Instale requests para consultar el clima') + key = self._resolve_openweather_key() + params = { + 'lat': lat, + 'lon': lon, + 'appid': key, + 'units': 'metric', + 'lang': 'es' + } + resp = requests.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=8) + resp.raise_for_status() + data = resp.json() + if 'main' not in data or 'weather' not in data: + raise ValueError('Respuesta incompleta de OpenWeatherMap') + return data + + def _fetch_weather_data(self, city_query: str) -> dict[str, Any]: + if not REQUESTS_AVAILABLE: + raise RuntimeError('Instale requests para consultar el clima') + key = self._resolve_openweather_key() + params = { + 'q': city_query, + 'appid': key, + 'units': 'metric', + 'lang': 'es' + } + resp = requests.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=8) + resp.raise_for_status() + data = resp.json() + if 'main' not in data or 'weather' not in data: + raise ValueError('Respuesta incompleta de OpenWeatherMap') + return data + + def _fetch_javea_weather(self) -> dict[str, Any]: + return self._fetch_weather_by_coordinates(JAVEA_LATITUDE, JAVEA_LONGITUDE) + + def _update_weather(self) -> None: + if not self._running: + return + try: + data = self._fetch_weather_data(self.weather_city) + temp = data.get('main', {}).get('temp') + desc = data.get('weather', [{}])[0].get('description', '').capitalize() + city = data.get('name') or self.weather_city + if temp is None: + raise ValueError('Respuesta sin temperatura') + self.weather_label.config(text=f'{city}: {temp:.1f}°C, {desc}') + self._last_weather_data = data + self._last_weather_error = None + self._last_weather_timestamp = datetime.datetime.now() + except RuntimeError as exc: + self.weather_label.config(text='Clima: configure API') + self._log(f'Clima: {exc}') + self._last_weather_data = None + self._last_weather_error = str(exc) + self._last_weather_timestamp = datetime.datetime.now() + except Exception as exc: + self.weather_label.config(text='Clima: N/D') + self._log(f'Clima: {exc}') + self._last_weather_data = None + self._last_weather_error = str(exc) + self._last_weather_timestamp = datetime.datetime.now() + finally: + if self._running: + self.after(300000, self._update_weather) + + # ------------------------ chat ------------------------ + def _connect_chat(self) -> None: + host = self.host_entry.get().strip() or SERVER_HOST_DEFAULT + try: + port = int(self.port_entry.get().strip()) + except ValueError: + port = SERVER_PORT_DEFAULT + if self.chat_client.connect(host, port): + self._enqueue_chat_message('[INFO] Conectado al servidor') + + def _send_chat(self) -> None: + text = self.message_entry.get('1.0', 'end').strip() + if not text: + return + if self.chat_client.send(text + '\n'): + self._enqueue_chat_message('Yo: ' + text) + self.message_entry.delete('1.0', 'end') + else: + messagebox.showwarning('Chat', 'No hay conexión activa.') + + def _enqueue_chat_message(self, text: str) -> None: + self._chat_queue.put(text) + + def _chat_loop(self) -> None: + while self._running: + try: + msg = self._chat_queue.get(timeout=0.5) + except queue.Empty: + continue + self.chat_display.after(0, lambda m=msg: self._append_chat(m)) + + def _append_chat(self, text: str) -> None: + self.chat_display.config(state='normal') + self.chat_display.insert('end', text + '\n') + self.chat_display.see('end') + self.chat_display.config(state='disabled') + + # ------------------------ hilos auxiliares ------------------------ + def _update_clock(self) -> None: + if not self._running: + return + now = datetime.datetime.now().strftime('%d/%m/%Y %H:%M:%S') + self.clock_label.config(text=now) + self.after(1000, self._update_clock) + + def _update_traffic(self) -> None: + if not (self._running and psutil): + return + if self._traffic_last is None: + try: + self._traffic_last = psutil.net_io_counters() + except Exception as exc: + self._log(f'Tráfico: psutil no disponible ({exc})') + self.traffic_label.config(text='Red: N/D') + return + try: + current = psutil.net_io_counters() + sent = (current.bytes_sent - self._traffic_last.bytes_sent) / 1024 + recv = (current.bytes_recv - self._traffic_last.bytes_recv) / 1024 + self._traffic_last = current + self.traffic_label.config(text=f'Red: ↑ {sent:.1f} KB/s ↓ {recv:.1f} KB/s') + except Exception as exc: + self._log(f'Tráfico: {exc}') + self.traffic_label.config(text='Red: N/D') + finally: + if self._running: + self.after(1000, self._update_traffic) + + def _resource_poll_tick(self) -> None: + if not (self._running and psutil): + return + try: + cpu = psutil.cpu_percent(interval=None) + mem = psutil.virtual_memory().percent + except Exception as exc: + self._log(f'Recursos: {exc}') + if self._running: + self._resource_poll_job = self.after(2000, self._resource_poll_tick) + return + threads = 0 + if self._ps_process: + try: + threads = self._ps_process.num_threads() + except Exception: + threads = 0 + self._resource_history['cpu'].append(cpu) + self._resource_history['mem'].append(mem) + self._resource_history['threads'].append(threads) + self._resource_history['cpu'] = self._resource_history['cpu'][-40:] + self._resource_history['mem'] = self._resource_history['mem'][-40:] + self._resource_history['threads'] = self._resource_history['threads'][-40:] + self._update_chart() + if self._running: + self._resource_poll_job = self.after(1000, self._resource_poll_tick) + + def _update_chart(self) -> None: + if not ( + MATPLOTLIB_AVAILABLE + and self.chart_canvas + and self.line_cpu + and self.line_mem + and self.ax_cpu + and self.ax_mem + ): + return + + x = list(range(len(self._resource_history['cpu']))) + self.line_cpu.set_data(x, self._resource_history['cpu']) + self.line_mem.set_data(x, self._resource_history['mem']) + + max_x = max(40, len(x)) + self.ax_cpu.set_xlim(0, max_x) + self.ax_mem.set_xlim(0, max_x) + self.ax_cpu.set_ylim(0, 100) + self.ax_mem.set_ylim(0, 100) + + if self.mem_fill: + try: + self.mem_fill.remove() + except Exception: + pass + if self.ax_mem and x: + self.mem_fill = self.ax_mem.fill_between( + x, + self._resource_history['mem'], + color='#4ecdc4', + alpha=0.3 + ) + + if self.ax_threads: + threads = self._resource_history['threads'] + if threads: + if self.thread_bars and len(self.thread_bars) == len(threads): + for rect, height in zip(self.thread_bars, threads): + rect.set_height(height) + else: + if self.thread_bars: + for rect in self.thread_bars: + rect.remove() + self.thread_bars = self.ax_threads.bar(x, threads, color='#ffa726') if x else None + if threads: + max_threads = max(threads) + self.ax_threads.set_ylim(0, max(max_threads * 1.2, 1)) + self.ax_threads.set_xlim(-0.5, max(39.5, len(x) - 0.5)) + + if self.chart_canvas: + self.chart_canvas.draw_idle() + + def _log(self, text: str) -> None: + if threading.current_thread() is not threading.main_thread(): + self.after(0, lambda t=text: self._log(t)) + return + timestamp = datetime.datetime.now().strftime('%H:%M:%S') + self.notes.insert('end', f'[{timestamp}] {text}\n') + self.notes.see('end') + + def on_close(self) -> None: + self._running = False + if self._resource_poll_job is not None: + try: + self.after_cancel(self._resource_poll_job) + except Exception: + pass + self._resource_poll_job = None + self.chat_client.close() + self.destroy() + + +def main() -> None: + app = DashboardApp() + app.protocol('WM_DELETE_WINDOW', app.on_close) + app.mainloop() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fd62718 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +psutil>=5.9.0 +matplotlib>=3.5.0 +pillow>=9.0.0 +# pygame optional for direct mp3 playback; we use system player as fallback +pygame>=2.1.0 +requests>=2.32.0 +beautifulsoup4>=4.12.0 \ No newline at end of file diff --git a/servidor.py b/servidor.py new file mode 100644 index 0000000..a7d89c4 --- /dev/null +++ b/servidor.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +""" +Servidor de mensajería simple (broadcast) - puerto 3333 + +Ejecutar en un terminal separado: + python3 servidor.py + +""" +import socket +import threading + +HOST = '0.0.0.0' +PORT = 3333 + +clients = [] +clients_lock = threading.Lock() + +def broadcast(message: bytes, sender: socket.socket): + with clients_lock: + for client in list(clients): + if client is sender: + continue + try: + client.sendall(message) + except Exception: + try: + client.close() + except Exception: + pass + clients.remove(client) + +def handle_client(client_socket: socket.socket, client_address): + print(f"[NUEVO CLIENTE] {client_address} conectado.") + try: + while True: + data = client_socket.recv(4096) + if not data: + break + text = data.decode('utf-8', errors='replace') + print(f"[{client_address}] {text}") + # Re-enviar a los demás + broadcast(data, client_socket) + except Exception as e: + print(f"[ERROR] {client_address}:", e) + finally: + with clients_lock: + if client_socket in clients: + clients.remove(client_socket) + try: + client_socket.close() + except Exception: + pass + print(f"[DESCONECTADO] {client_address} cerrado.") + +def start_server(): + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((HOST, PORT)) + server.listen(10) + print(f"[INICIO] Servidor escuchando en {HOST}:{PORT}") + + try: + while True: + client_socket, client_address = server.accept() + with clients_lock: + clients.append(client_socket) + t = threading.Thread(target=handle_client, args=(client_socket, client_address), daemon=True) + t.start() + except KeyboardInterrupt: + print('\n[APAGANDO] Servidor detenido por el usuario') + finally: + with clients_lock: + for c in clients: + try: + c.close() + except Exception: + pass + try: + server.close() + except Exception: + pass + +if __name__ == '__main__': + start_server()