Actualiza barra de estado y README

This commit is contained in:
marcos 2025-12-05 22:21:17 +01:00
parent 9f7d1e247c
commit 2d62bf2530
2 changed files with 45 additions and 84 deletions

View File

@ -1,67 +1,68 @@
# Proyecto Global Dashboard # 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. Laboratorio interactivo construido con Python 3.13 + Tkinter. Reúne scraping, monitorización, chat TCP, alarmas, reproductor musical y utilidades varias en una única aplicación de escritorio.
## 🚀 Características principales ## 🎬 Demo en video
- **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. - YouTube: https://youtu.be/HgJwU_HagD8
- **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. ## 🚀 Características clave
- **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. - **Layout triple panel**: accesos rápidos a la izquierda, notebook central con pestañas temáticas y panel derecho para chat y utilidades.
- **Servidor de mensajería**: `servidor.py` permite broadcast TCP para pruebas de chat local. - **Scraping Wallapop + genérico**: asistentes emergentes, validaciones y guardado de resultados.
- **Monitor de sistema**: gráficas en vivo de CPU, RAM e hilos gracias a psutil y matplotlib embebido.
- **Productividad integrada**: bloc de notas, gestor de alarmas, reproductor musical con pygame y lanzadores de procesos.
- **Popup meteorológico**: consulta OpenWeather para Jávea con caché y resumen formateado.
- **Servidor TCP incluido**: `servidor.py` permite pruebas de chat broadcast desde la propia app.
## ⚙️ Requisitos ## ⚙️ Requisitos
- Python 3.8 o superior (desarrollado con 3.14) - Python 3.8 o superior (desarrollado en 3.13)
- Dependencias listadas en `requirements.txt` - Dependencias listadas en `requirements.txt`
```sh ```sh
pip install -r requirements.txt pip install -r requirements.txt
``` ```
## ▶️ Puesta en marcha rápida ## ▶️ Puesta en marcha
1. (Opcional) Arranca el servidor de mensajería: 1. (Opcional) Inicia el servidor de chat:
```sh ```sh
python3 servidor.py python3 servidor.py
``` ```
Verás `Servidor escuchando en 0.0.0.0:3333` en consola. 2. Lanza el panel principal:
2. Lanza la interfaz gráfica:
```sh ```sh
python3 app.py 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. 3. Usa el panel izquierdo para abrir scraping, notas, alarmas o el popup del clima; el panel derecho gestiona el chat y el reproductor.
## 🧱 Arquitectura de carpetas ## 🧱 Estructura del proyecto
``` ```
app.py # GUI principal y lógica de scraping, clima, monitorización, alarmas... app.py # GUI principal y lógica de negocio
servidor.py # Servidor TCP broadcast para el chat de pruebas servidor.py # Servidor TCP broadcast para el chat
requirements.txt # Dependencias del proyecto requirements.txt # Lista de dependencias
README.md # Este archivo README.md # Documentación
``` ```
## 🛠️ Funcionalidades destacadas ## 🛠️ Flujos destacados
- **Scraping Wallapop y genérico**: ventanas emergentes, peticiones HTTP con Requests + BeautifulSoup, mensajes de éxito/error. - **Scraping**: workers en segundo plano con colas y retroalimentación visual en la pestaña de resultados.
- **Weather popup “API Tiempo”**: botón dedicado que consulta OpenWeather (con clave fallback), muestra iconos, temperaturas y caché temporal. - **Copias de seguridad**: selección interactiva de carpetas y reporte final con totales copiados/omitidos.
- **Copias de seguridad guiadas**: barra de progreso y notificaciones durante la duplicación de directorios. - **Gestor de alarmas**: creación, cancelación y popups dedicados con recordatorio sonoro.
- **Editor y bloc de notas**: pestañas separadas para notas rápidas y bloc organizado. - **Panel de recursos**: mezcla de gráficas lineales, de área y de barras para CPU/RAM/hilos.
- **Gestor de alarmas**: UI modernizada con tarjetas, botones primarios y feedback visual. - **Popup “API Tiempo”**: resumen meteorológico en ventana modal con refresco bajo demanda.
- **Música y utilidades**: reproductor basado en pygame y accesos a herramientas externas (“Camellos”, lanzadores, etc.).
## 🌤️ Servicios externos ## ⚙️ Configuración opcional
- **OpenWeatherMap**: usado para el popup del clima (coordenadas de Jávea). Define `OPENWEATHER_API_KEY` en el entorno para usar tu propia clave. - `OPENWEATHER_API_KEY` / `OPENWEATHER_FALLBACK_API_KEY`: claves para OpenWeather.
- **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. - Variables `WALLAPOP_*`: encabezados y parámetros usados por el scraper.
- Ajusta el host/puerto del chat en el panel derecho o modificando `SERVER_HOST_DEFAULT` y `SERVER_PORT_DEFAULT` en `app.py`.
## 📌 Próximos pasos sugeridos ## 📌 Próximos pasos sugeridos
1. Añadir pruebas unitarias para la lógica no gráfica (scraping, backups, parsers). 1. Añadir almacenamiento persistente (SQLite) para chats y notas.
2. Persistir chats y notas en SQLite para mantener el historial. 2. Incorporar pruebas unitarias para scraping y rutinas de backup.
3. Integrar reproductor completo dentro de la app (playlist, carátulas). 3. Extender el reproductor musical con colas y visualizaciones.
--- ¿Quieres ampliar alguna sección (scraping extra, nuevos paneles, automatización de tareas)? Adelante, la base está lista para seguir creciendo.
¿Necesitas extender alguna funcionalidad? Abre un issue o comenta qué módulo quieres potenciar (más scraping, dashboards adicionales, automatización de backups, etc.).

46
app.py
View File

@ -68,7 +68,6 @@ OPENWEATHER_FALLBACK_API_KEY = os.environ.get(
).strip() ).strip()
_OPENWEATHER_ENV_KEY = os.environ.get('OPENWEATHER_API_KEY', '').strip() _OPENWEATHER_ENV_KEY = os.environ.get('OPENWEATHER_API_KEY', '').strip()
OPENWEATHER_API_KEY = _OPENWEATHER_ENV_KEY or OPENWEATHER_FALLBACK_API_KEY 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_LATITUDE = 38.789166
JAVEA_LONGITUDE = 0.163055 JAVEA_LONGITUDE = 0.163055
@ -232,16 +231,12 @@ class DashboardApp(tk.Tk):
self.game_camel_count = 3 self.game_camel_count = 3
self.game_speed_factor = 1.0 self.game_speed_factor = 1.0
self.music_temp_file: str | None = None self.music_temp_file: str | None = None
self.weather_city = OPENWEATHER_CITY
self.weather_api_key = OPENWEATHER_API_KEY self.weather_api_key = OPENWEATHER_API_KEY
self.results_area: tk.Frame | None = None self.results_area: tk.Frame | None = None
self.results_title: tk.Label | None = None self.results_title: tk.Label | None = None
self.scraping_popup: tk.Toplevel | None = None self.scraping_popup: tk.Toplevel | None = None
self.simple_scraping_popup: tk.Toplevel | None = None self.simple_scraping_popup: tk.Toplevel | None = None
self.weather_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.chart_canvas = None
self.ax_cpu = None self.ax_cpu = None
self.ax_mem = None self.ax_mem = None
@ -263,8 +258,8 @@ class DashboardApp(tk.Tk):
self._build_center_panel() self._build_center_panel()
self._build_right_panel() self._build_right_panel()
self._build_status_bar() self._build_status_bar()
self._update_clock() self._update_clock()
if psutil: if psutil:
self.after(1000, self._update_traffic) self.after(1000, self._update_traffic)
try: try:
@ -275,8 +270,6 @@ class DashboardApp(tk.Tk):
threading.Thread(target=self._chat_loop, daemon=True).start() threading.Thread(target=self._chat_loop, daemon=True).start()
self.after(100, self._process_scraping_queue) self.after(100, self._process_scraping_queue)
self.after(1000, self._refresh_alarms_loop) self.after(1000, self._refresh_alarms_loop)
if REQUESTS_AVAILABLE and self.weather_api_key:
self.after(2000, self._update_weather)
# ------------------------ UI ------------------------ # ------------------------ UI ------------------------
def _maximize_with_borders(self) -> None: def _maximize_with_borders(self) -> None:
@ -765,7 +758,7 @@ class DashboardApp(tk.Tk):
def _build_status_bar(self) -> None: def _build_status_bar(self) -> None:
status = tk.Frame(self, bg='#f1f1f1', bd=2, relief='ridge') status = tk.Frame(self, bg='#f1f1f1', bd=2, relief='ridge')
status.grid(row=2, column=0, columnspan=3, sticky='ew') status.grid(row=2, column=0, columnspan=3, sticky='ew')
for idx in range(4): for idx in range(3):
status.columnconfigure(idx, weight=1) 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') tk.Label(status, text='Correos sin leer', font=('Arial', 11, 'bold'), bg='#f1f1f1').grid(row=0, column=0, padx=16, pady=6, sticky='w')
@ -778,11 +771,8 @@ class DashboardApp(tk.Tk):
) )
self.traffic_label.grid(row=0, column=1, padx=16, pady=6) 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 = tk.Label(status, text='--:--:--', font=('Arial', 12, 'bold'), bg='#f1f1f1')
self.clock_label.grid(row=0, column=3, padx=16, pady=6, sticky='e') self.clock_label.grid(row=0, column=2, padx=16, pady=6, sticky='e')
# ------------------------ acciones ------------------------ # ------------------------ acciones ------------------------
def _open_web(self, url: str) -> None: def _open_web(self, url: str) -> None:
@ -2020,36 +2010,6 @@ class DashboardApp(tk.Tk):
def _fetch_javea_weather(self) -> dict[str, Any]: def _fetch_javea_weather(self) -> dict[str, Any]:
return self._fetch_weather_by_coordinates(JAVEA_LATITUDE, JAVEA_LONGITUDE) 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 ------------------------ # ------------------------ chat ------------------------
def _connect_chat(self) -> None: def _connect_chat(self) -> None:
host = self.host_entry.get().strip() or SERVER_HOST_DEFAULT host = self.host_entry.get().strip() or SERVER_HOST_DEFAULT