Python Tkinter Sockets License

๐Ÿ’ฃ Minesweeper Multiplayer + Dashboard

Un proyecto completo de programaciรณn de servicios y procesos
Juego de buscaminas competitivo en red + Panel de control integral

Caracterรญsticas โ€ข Arquitectura โ€ข Instalaciรณn โ€ข Uso โ€ข Mecรกnicas โ€ข Tecnologรญas

--- ## ๐Ÿ“ธ Vista Previa ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ๐ŸŽฎ MINESWEEPER MULTIPLAYER DASHBOARD โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ ACCIONES โ”‚ รREA DE RESULTADOS โ”‚ โ”‚ ๐Ÿ’ฃ MINESWEEPER โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ MULTIPLAYER โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ > Wallapop โ”‚ โ”‚ ๐Ÿ“Š Monitor Sistema โ”‚ โ”‚ โ”‚ Ronda: 3 โ”‚ โ”‚ โ”‚ > Scraping โ”‚ โ”‚ ๐Ÿ“ˆ CPU: 45% โ”‚ โ”‚ โ”‚ ๐Ÿ’ฃ Bombas: 9 โ”‚ โ”‚ โ”‚ > API Tiempo โ”‚ โ”‚ ๐Ÿ’พ RAM: 2.1GB โ”‚ โ”‚ โ”‚ โค๏ธ Vidas: 2 โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ APPS โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ Tabs: [Resultados][Navegador] โ”‚ โ”‚ โ”‚ โ–ข โ”‚ โ–ข โ”‚ โœ“ โ”‚ โ”‚ โ”‚ โ”‚ > VS Code โ”‚ [Correos][Bloc][Tareas] โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ > Camellos โ”‚ [Alarmas][Enlaces] โ”‚ โ”‚ โ”‚ ๐Ÿ’ฅโ”‚ โ–ข โ”‚ โ–ข โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ BATCH โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ ๐Ÿ“ Panel de Notas โ”‚ โ”‚ โ”‚ [Iniciar Juego] โ”‚ โ”‚ โ”‚ > Backups โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ [Zona Despejada]โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ## โœจ Caracterรญsticas ### ๐ŸŽฎ **Juego Minesweeper Multijugador** | Caracterรญstica | Descripciรณn | |----------------|-------------| | ๐Ÿ”ฅ **Competitivo** | 2+ jugadores compiten en tiempo real | | ๐Ÿ’ฃ **Colocaciรณn estratรฉgica** | Cada jugador coloca bombas para el rival | | ๐Ÿ”„ **Por turnos** | Sistema de turnos para colocar y buscar | | ๐Ÿ“ˆ **Dificultad progresiva** | 5 rondas con grids y bombas crecientes | | โค๏ธ **Sistema de vidas** | 3 vidas por partida, ยกno las pierdas! | ### ๐Ÿ“Š **Dashboard Integral** - ๐Ÿ“ก **Monitor del sistema** en tiempo real (CPU, RAM, hilos) - ๐ŸŒค๏ธ **API del tiempo** para Jรกvea (OpenWeather) - ๐Ÿ›’ **Anรกlisis de Wallapop** - Scraping de anuncios - โฐ **Sistema de alarmas** programables - ๐Ÿ“ **Bloc de notas** integrado - ๐Ÿ”— **Gestor de enlaces** rรกpidos - ๐ŸŽฒ **Minijuego de camellos** con animaciones ### ๐Ÿ“ง **Cliente de Correo Electrรณnico Completo** | Caracterรญstica | Descripciรณn | |----------------|-------------| | ๐Ÿ“ฌ **IMAP** | Lectura de correos desde servidor Webmin (Puerto 143) | | ๐Ÿ“ค **SMTP** | Envรญo de correos con autenticaciรณn (Puerto 25) | | ๐Ÿ’พ **Auto-guardado** | Credenciales guardadas con Base64 | | ๐Ÿ‘ฅ **Mรบltiples destinatarios** | Envรญo a varios correos simultรกneamente | | ๐Ÿ“Ž **Adjuntos** | Soporte para imรกgenes, PDFs, docs, Excel, ZIP | | ๐Ÿ–ผ๏ธ **Imรกgenes inline** | Visualizaciรณn de imรกgenes dentro del correo | | ๐Ÿ—‚๏ธ **Carpetas** | Bandeja de entrada y enviados sincronizados | | ๐Ÿ”ต **Indicadores** | Correos leรญdos/no leรญdos con marcadores visuales | --- ## ๐Ÿ—๏ธ Arquitectura ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ARQUITECTURA DEL SISTEMA โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ SERVIDOR TCP โ”‚ โ”‚ servidor.py โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ GameServer โ”‚ โ”‚ โ”‚ โ”‚ - Estado juego โ”‚ โ”‚ โ”‚ โ”‚ - Broadcast โ”‚ โ”‚ โ”‚ โ”‚ - Turnos โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ Puerto 3333 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Protocolo JSON โ”‚ โ”‚ sobre TCP/IP โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ–ผ โ–ผ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ CLIENTE 1 โ”‚ โ”‚ CLIENTE 2 โ”‚ โ”‚ CLIENTE N โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ app.py โ”‚ โ”‚ โ”‚ โ”‚ app.py โ”‚ โ”‚ โ”‚ โ”‚ cliente_ โ”‚ โ”‚ โ”‚ โ”‚ Dashboard โ”‚ โ”‚ โ”‚ โ”‚ Dashboard โ”‚ โ”‚ โ”‚ โ”‚ juego.py โ”‚ โ”‚ โ”‚ โ”‚ + Juego โ”‚ โ”‚ โ”‚ โ”‚ + Juego โ”‚ โ”‚ โ”‚ โ”‚ Standalone โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### ๐Ÿ“ Estructura del Proyecto ``` Proyecto1AVApsp/ โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ servidor.py # Servidor TCP del juego (371 lรญneas) โ”‚ โ””โ”€โ”€ GameServer # Gestiona estado, turnos y broadcasts โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ app.py # Dashboard principal (2566 lรญneas) โ”‚ โ”œโ”€โ”€ DashboardApp # Aplicaciรณn Tkinter completa โ”‚ โ””โ”€โ”€ GameClient # Cliente TCP integrado โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ cliente_juego.py # Cliente standalone (220 lรญneas) โ”‚ โ””โ”€โ”€ GameClient # Versiรณn ligera para jugar โ”‚ โ”œโ”€โ”€ ๐Ÿ“„ requirements.txt # Dependencias Python โ””โ”€โ”€ ๐Ÿ“„ README.md # Este archivo ``` --- ## ๐Ÿš€ Instalaciรณn ### Requisitos Previos - **Python 3.8+** - **pip** (gestor de paquetes) ### Paso 1: Clonar el Repositorio ```bash git clone https://github.com/MarcosFerrandiz/Proyecto1AVApsp.git cd Proyecto1AVApsp ``` ### Paso 2: Crear Entorno Virtual (Recomendado) ```bash python -m venv .venv # Linux/macOS source .venv/bin/activate # Windows .venv\Scripts\activate ``` ### Paso 3: Instalar Dependencias ```bash pip install -r requirements.txt ```
๐Ÿ“ฆ Ver dependencias detalladas | Paquete | Versiรณn | Uso | |---------|---------|-----| | `psutil` | โ‰ฅ5.9.0 | Monitor de recursos del sistema | | `matplotlib` | โ‰ฅ3.5.0 | Grรกficos en tiempo real | | `pillow` | โ‰ฅ9.0.0 | Procesamiento de imรกgenes | | `pygame` | โ‰ฅ2.1.0 | Reproducciรณn de audio (opcional) | | `requests` | โ‰ฅ2.32.0 | Peticiones HTTP (API, Scraping) | | `beautifulsoup4` | โ‰ฅ4.12.0 | Parsing HTML (Scraping) |
--- ## ๐ŸŽฏ Uso ### ๐Ÿ–ฅ๏ธ Iniciar el Servidor ```bash python servidor.py ``` > El servidor escucharรก en `0.0.0.0:3333` ### ๐ŸŽฎ Opciรณn A: Dashboard Completo ```bash python app.py ``` Incluye el juego integrado + todas las funcionalidades del panel. ### ๐ŸŽฎ Opciรณn B: Cliente Standalone ```bash python cliente_juego.py ``` Cliente ligero solo para jugar al Minesweeper. ### ๐Ÿ”Œ Conectar al Juego 1. Introduce el **Host** del servidor (por defecto: `127.0.0.1`) 2. Verifica el **Puerto** (por defecto: `3333`) 3. Pulsa **"Conectar"** 4. ยกEspera a otro jugador y pulsa **"Iniciar Juego"**! --- ## ๐Ÿ’ฃ Mecรกnicas del Juego ### ๐Ÿ”„ Flujo de una Partida ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ FLUJO DEL JUEGO โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ LOBBY โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ COLOCACIร“N โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ BรšSQUEDA โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ DE BOMBAS โ”‚ โ”‚ (PLAYING) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ ยฟBOMBA? โ”‚โ”€โ”€โ”€โ–ถโ”‚ EXPLOSIร“Nโ”‚โ”€โ”€โ”€โ–ถโ”‚ ยฟVIDAS=0? โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ NO โ”‚ โ”‚ โ”‚ โ”‚ โ–ผ Sร โ–ผ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ SAFE โ”‚ โ”‚ GAME OVERโ”‚ โ”‚ โ”‚ โ”‚ โ”‚(casilla โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ segura) โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ โ”‚ โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”“ โ”‚ โ”‚ โ”‚ โ”ƒ SIGUIENTE TURNO โ”ƒ โ”‚ โ”‚ โ”‚ โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ (Nueva Partida) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### ๐Ÿ“Š Progresiรณn de Dificultad | Ronda | Tamaรฑo Grid | Bombas/Jugador | Total Bombas* | |:-----:|:-----------:|:--------------:|:-------------:| | 1๏ธโƒฃ | 3ร—3 | 3 | 6 | | 2๏ธโƒฃ | 5ร—5 | 5 | 10 | | 3๏ธโƒฃ | 8ร—8 | 9 | 18 | | 4๏ธโƒฃ | 11ร—11 | 12 | 24 | | 5๏ธโƒฃ+ | 14ร—14 | 15 | 30 | *\*Para 2 jugadores* ### ๐ŸŽฏ Fases del Juego #### 1. ๐Ÿ’ฃ Fase de Colocaciรณn (`PLACING`) - Cada jugador coloca bombas **por turnos** - Las bombas se muestran brevemente (1 segundo) a todos - ยกMemoriza dรณnde pones TUS bombas y las del rival! #### 2. ๐Ÿ” Fase de Bรบsqueda (`PLAYING`) - Excava casillas por turnos - **Casilla segura** โ†’ Se marca en verde โœ… - **Bomba** โ†’ ยกEXPLOSIร“N! Pierdes 1 vida ๐Ÿ’” ### ๐Ÿ† Condiciones de Victoria | Condiciรณn | Resultado | |-----------|-----------| | Rival pierde todas las vidas | **ยกGANASTE!** ๐ŸŽ‰ | | Todas las casillas seguras reveladas | **ยกZona despejada!** | | Superas la ronda 5 | **ยกVictoria total!** ๐Ÿ† | --- ## ๐Ÿ“ก Protocolo de Comunicaciรณn ### Mensajes JSON Cliente โ†’ Servidor ```javascript // Iniciar partida { "type": "START_GAME" } // Colocar bomba (fase PLACING) { "type": "PLACE_BOMB", "x": 2, "y": 1 } // Excavar casilla (fase PLAYING) { "type": "CLICK_CELL", "x": 3, "y": 4 } // Verificar zona despejada { "type": "CHECK_DUNGEON_CLEARED" } ``` ### Mensajes JSON Servidor โ†’ Clientes ```javascript // Nueva ronda { "type": "NEW_ROUND", "round": 1, "grid_size": 3, "total_bombs_per_player": 3 } // Notificaciรณn de turno { "type": "TURN_NOTIFY", "active_player": "('127.0.0.1', 54321)", "msg": "Turno de ... para poner bombas." } // Flash de bomba (visible 1 segundo) { "type": "BOMB_flash", "x": 1, "y": 2, "who": "('127.0.0.1', 54321)" } // Explosiรณn { "type": "EXPLOSION", "x": 1, "y": 2, "who": "...", "lives": 2 } // Casilla segura { "type": "SAFE", "x": 0, "y": 0 } // Game Over { "type": "GAME_OVER", "loser": "...", "msg": "๐Ÿ’€ ยก... ha perdido todas sus vidas!" } ``` --- ## ๐Ÿ”ง Documentaciรณn Tรฉcnica Detallada ### ๐Ÿ†” Sistema de Identificaciรณn de Jugadores El servidor **identifica a cada jugador mediante su direcciรณn de socket**, que es una tupla รบnica `(IP, Puerto)`: ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ IDENTIFICACIร“N รšNICA DE JUGADORES โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ Cuando un cliente se conecta al servidor: โ”‚ โ”‚ โ”‚ โ”‚ Cliente 1 โ”€โ”€โ”€โ”€โ”€โ”€โ–บ sock.accept() โ”€โ”€โ”€โ”€โ”€โ”€โ–บ ('127.0.0.1', 49956) โ”‚ โ”‚ Cliente 2 โ”€โ”€โ”€โ”€โ”€โ”€โ–บ sock.accept() โ”€โ”€โ”€โ”€โ”€โ”€โ–บ ('127.0.0.1', 49968) โ”‚ โ”‚ โ”‚ โ”‚ Aunque ambos clientes estรฉn en el MISMO ordenador (127.0.0.1) โ”‚ โ”‚ el PUERTO es diferente y รบnico para cada conexiรณn. โ”‚ โ”‚ โ”‚ โ”‚ Esta tupla (IP, Puerto) actรบa como IDENTIFICADOR รšNICO. โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` #### ยฟCรณmo funciona en el mismo ordenador? ```python # servidor.py - Al aceptar conexiรณn conn, addr = s.accept() # addr = ('127.0.0.1', 49956) # El sistema operativo asigna un puerto efรญmero รšNICO # a cada nueva conexiรณn de socket del cliente ``` | Cliente | IP | Puerto (asignado por SO) | Identificador Completo | |---------|----|--------------------------|-----------------------| | Dashboard 1 | 127.0.0.1 | 49956 | `('127.0.0.1', 49956)` | | Dashboard 2 | 127.0.0.1 | 49968 | `('127.0.0.1', 49968)` | | Cliente remoto | 192.168.1.50 | 52341 | `('192.168.1.50', 52341)` | > **๐Ÿ’ก Clave:** El puerto del cliente es asignado automรกticamente por el sistema operativo y es **siempre diferente** para cada nueva conexiรณn, incluso desde el mismo ordenador. --- ### ๐Ÿ”„ Gestiรณn de Turnos El servidor mantiene **dos รญndices de turno** para controlar quiรฉn juega: ```python # servidor.py - Variables de control de turnos self.placing_turn_index = 0 # รndice en fase PLACING self.playing_turn_index = 0 # รndice en fase PLAYING self.client_list = [] # Lista ordenada de direcciones ``` #### Flujo de Verificaciรณn de Turno ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ VERIFICACIร“N DE TURNO โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ client_list = [('127.0.0.1', 49956), ('127.0.0.1', 49968)] โ”‚ โ”‚ โ†‘ โ†‘ โ”‚ โ”‚ รญndice 0 รญndice 1 โ”‚ โ”‚ โ”‚ โ”‚ Si placing_turn_index = 0: โ”‚ โ”‚ โ†’ Solo ('127.0.0.1', 49956) puede poner bombas โ”‚ โ”‚ โ”‚ โ”‚ Cuando recibe PLACE_BOMB de un cliente: โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ if str(addr) != str(current_turn_addr): โ”‚ โ”‚ โ”‚ โ”‚ return # No es su turno, ignorar โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` #### Cรณdigo de Verificaciรณn (servidor.py) ```python elif msg_type == 'PLACE_BOMB': if self.state == STATE_PLACING: # Obtener quiรฉn tiene el turno actual current_turn_addr = self.client_list[self.placing_turn_index] # Comparar con quiรฉn enviรณ el mensaje if str(addr) != str(current_turn_addr): return # ยกNo es tu turno! Ignorar mensaje # Procesar la bomba... ``` --- ### ๐Ÿ‘ค Cรณmo el Cliente Sabe si es su Turno El cliente guarda su propia direcciรณn al conectarse y la compara con los mensajes del servidor: ```python # app.py - Al conectarse self.my_address = str(sock.getsockname()) # Ej: "('127.0.0.1', 49956)" # Al recibir TURN_NOTIFY del servidor def handle_message(self, msg): if mtype == 'TURN_NOTIFY': active_player = msg.get('active_player') # "('127.0.0.1', 49956)" # Comparar con mi direcciรณn if active_player == self.my_address: self._log_game("๐ŸŽฏ ยกES TU TURNO!") # Habilitar controles... else: self._log_game(f"โณ Turno de {active_player}") # Deshabilitar controles... ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ FLUJO DE NOTIFICACIร“N DE TURNO โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ SERVIDOR CLIENTES โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ”‚ broadcast({ Cliente 1 (49956): โ”‚ โ”‚ "type": "TURN_NOTIFY", โ”œโ”€ my_address = 49956 โ”‚ โ”‚ "active_player": โ”œโ”€ active_player = 49956 โ”‚ โ”‚ "('127.0.0.1', 49956)" โ””โ”€ โœ“ ยกES MI TURNO! โ”‚ โ”‚ }) โ”‚ โ”‚ โ”‚ Cliente 2 (49968): โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”œโ”€ my_address = 49968 โ”‚ โ”‚ โ”œโ”€ active_player = 49956 โ”‚ โ”‚ โ””โ”€ โœ— No es mi turno โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ”’ Sincronizaciรณn con Threading Lock El servidor usa un `threading.Lock` para evitar condiciones de carrera cuando mรบltiples clientes envรญan mensajes simultรกneamente: ```python class GameServer: def __init__(self): self.lock = threading.Lock() # Mutex para sincronizaciรณn self.clients = {} # Diccionario compartido self.state = STATE_LOBBY # Estado compartido def process_message(self, client, addr, msg): with self.lock: # Adquirir lock antes de modificar estado if msg_type == 'PLACE_BOMB': # Solo un hilo puede ejecutar esto a la vez self.bombs.add((x, y)) self._broadcast_unlocked(...) ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ SINCRONIZACIร“N CON LOCK โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ Hilo Cliente 1 โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”œโ”€โ”€โ–บ with self.lock: โ”€โ”€โ–บ Ejecuta primero โ”‚ โ”‚ Hilo Cliente 2 โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ (espera...) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ Hilo 2 ejecuta despuรฉs โ”‚ โ”‚ โ”‚ โ”‚ Esto evita que dos jugadores modifiquen el estado โ”‚ โ”‚ del juego al mismo tiempo (condiciones de carrera). โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ“ก Arquitectura de Hilos ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ARQUITECTURA DE HILOS โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ SERVIDOR (servidor.py) โ”‚ โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Hilo Main โ”‚ โ—„โ”€โ”€ Acepta conexiones (s.accept()) โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ–บ Hilo Cliente 1 โ”€โ”€โ–บ handle_client(sock1) โ”‚ โ”‚ โ”œโ”€โ”€โ–บ Hilo Cliente 2 โ”€โ”€โ–บ handle_client(sock2) โ”‚ โ”‚ โ””โ”€โ”€โ–บ Hilo Cliente N โ”€โ”€โ–บ handle_client(sockN) โ”‚ โ”‚ โ”‚ โ”‚ Cada cliente tiene su propio hilo daemon que: โ”‚ โ”‚ 1. Lee mensajes del socket (recv) โ”‚ โ”‚ 2. Procesa el mensaje (process_message) โ”‚ โ”‚ 3. Puede hacer broadcast a todos los clientes โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ”‚ CLIENTE (app.py) โ”‚ โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Hilo Main โ”‚ โ”‚ Hilo Recv โ”‚ โ”‚ โ”‚ โ”‚ (Tkinter UI) โ”‚โ—„โ”€โ”€โ”€โ”‚ (_recv_loop) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ–ฒ โ”‚ โ”‚ โ”‚ msg_queue.put() โ”‚ sock.recv() โ”‚ โ”‚ โ–ผ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ Cola de msgs โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ (thread-safe) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ El hilo de recepciรณn pone mensajes en una cola. โ”‚ โ”‚ El hilo principal (UI) los procesa con after(). โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐ŸŽฎ Resumen: Jugar en el Mismo Ordenador | Paso | Quรฉ Sucede | |------|------------| | 1. Iniciar servidor | `python servidor.py` escucha en puerto 3333 | | 2. Abrir Cliente 1 | Conecta โ†’ SO asigna puerto 49956 | | 3. Abrir Cliente 2 | Conecta โ†’ SO asigna puerto 49968 | | 4. Servidor registra | `clients = {sock1: ('127.0.0.1', 49956), sock2: ('127.0.0.1', 49968)}` | | 5. Iniciar juego | `client_list = [addr1, addr2]` define orden de turnos | | 6. Turno jugador 1 | Servidor envรญa `TURN_NOTIFY` con `active_player = addr1` | | 7. Cliente 1 compara | `my_address == active_player` โ†’ ยกEs mi turno! | | 8. Cliente 2 compara | `my_address != active_player` โ†’ Esperar | | 9. Jugador 1 actรบa | Envรญa `PLACE_BOMB` o `CLICK_CELL` | | 10. Servidor valida | `addr == current_turn_addr` โ†’ Vรกlido, procesar | | 11. Avanzar turno | `placing_turn_index += 1` โ†’ Turno del siguiente | --- ## ๐Ÿ› ๏ธ Tecnologรญas
Python
Python 3
Lenguaje base
Tkinter
Tkinter
Interfaz grรกfica
Sockets
TCP Sockets
Comunicaciรณn en red
Matplotlib
Matplotlib
Grรกficos
JSON
JSON
Protocolo mensajes
Requests
Requests
APIs HTTP
### ๐Ÿงต Conceptos de PSP Aplicados | Concepto | Implementaciรณn | |----------|----------------| | **Procesos** | Lanzamiento de aplicaciones externas (VS Code, Firefox) | | **Threads** | Servidor multihilo, cliente con hilos de recepciรณn | | **Sockets TCP** | Comunicaciรณn cliente-servidor en red (Juego y Correo) | | **Servicios** | API OpenWeather, scraping de Wallapop, IMAP/SMTP | | **Sincronizaciรณn** | Locks para acceso concurrente a estado compartido | --- ## ๐Ÿ“ง CLIENTE DE CORREO ELECTRร“NICO: ARQUITECTURA Y FUNCIONAMIENTO ### ๐Ÿ—๏ธ Arquitectura del Sistema de Correo ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ARQUITECTURA DEL CLIENTE DE CORREO โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ SERVIDOR WEBMIN โ”‚ โ”‚ 10.10.0.101 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ PUERTO 143 โ”‚ โ”‚ PUERTO 25 โ”‚ โ”‚ IMAP โ”‚ โ”‚ SMTP โ”‚ โ”‚ (Lectura) โ”‚ โ”‚ (Envรญo) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ app.py โ”‚ โ”‚ DashboardApp โ”‚ โ”‚ โ”‚ โ”‚ Tab "Correos" โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ _connect_ โ”‚ โ”‚ _refresh_ โ”‚ โ”‚ _send_mail_ โ”‚ โ”‚ mail_server() โ”‚ โ”‚ mail_list() โ”‚ โ”‚ with_attach() โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ IMAP Login โ”‚ โ”‚ IMAP FETCH โ”‚ โ”‚ SMTP Send โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### ๐Ÿ“ก Protocolos Utilizados #### **IMAP (Internet Message Access Protocol)** Protocolo para **leer correos** del servidor. Puerto: **143** (sin TLS). ```python # Conexiรณn IMAP (app.py lรญneas 1578-1582) import imaplib self.imap_connection = imaplib.IMAP4(host, port_num) # Puerto 143 self.imap_connection.login(username, password) ``` **Comandos IMAP utilizados:** - `login(user, pass)` โ†’ Autenticaciรณn - `select(mailbox)` โ†’ Seleccionar carpeta (INBOX, Sent, etc.) - `search(None, 'ALL')` โ†’ Buscar todos los correos - `fetch(id, '(BODY.PEEK[] FLAGS)')` โ†’ Obtener correo sin marcarlo como leรญdo - `store(id, '+FLAGS', '\\Seen')` โ†’ Marcar como leรญdo - `append(folder, flags, date, msg)` โ†’ Guardar correo en carpeta #### **SMTP (Simple Mail Transfer Protocol)** Protocolo para **enviar correos**. Puerto: **25** (sin TLS). ```python # Conexiรณn SMTP (app.py lรญneas 2949-2955) import smtplib with smtplib.SMTP(smtp_host, smtp_port_num, timeout=15) as server: server.send_message(msg, to_addrs=recipients) ``` --- ### ๐Ÿ” Sistema de Credenciales #### **Guardado Automรกtico con Base64** ```python # GUARDAR (app.py lรญneas 1540-1557) import base64 config = { 'imap_host': '10.10.0.101', 'imap_port': '143', 'smtp_host': '10.10.0.101', 'smtp_port': '25', 'username': 'marcos@psp.es', 'password': base64.b64encode(password.encode()).decode() # Codificar } json.dump(config, open('.mail_config.json', 'w'), indent=2) ``` ```python # CARGAR (app.py lรญneas 1512-1520) config = json.load(open('.mail_config.json', 'r')) password = base64.b64decode(config['password']).decode() # Decodificar ``` **Flujo de guardado:** ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ FLUJO DE GUARDADO DE CREDENCIALES โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1. Usuario ingresa credenciales โ”‚ โ”‚ โ””โ”€โ–บ Usuario: marcos@psp.es โ”‚ โ”‚ โ””โ”€โ–บ Password: 1234 โ”‚ โ”‚ โ”‚ โ”‚ 2. Marca checkbox "๐Ÿ’พ Recordar credenciales" โ”‚ โ”‚ โ””โ”€โ–บ mail_remember_var.get() = True โ”‚ โ”‚ โ”‚ โ”‚ 3. Al conectar exitosamente, se llama _save_mail_credentials() โ”‚ โ”‚ โ”‚ โ”‚ 4. Base64 encoding: โ”‚ โ”‚ Password "1234" โ†’ Bytes b'1234' โ”‚ โ”‚ โ†’ Base64 b'MTIzNA==' โ”‚ โ”‚ โ†’ String "MTIzNA==" โ”‚ โ”‚ โ”‚ โ”‚ 5. Se guarda en .mail_config.json: โ”‚ โ”‚ { โ”‚ โ”‚ "username": "marcos@psp.es", โ”‚ โ”‚ "password": "MTIzNA==" โ”‚ โ”‚ } โ”‚ โ”‚ โ”‚ โ”‚ 6. Al reiniciar app.py: โ”‚ โ”‚ โ””โ”€โ–บ _load_mail_credentials() lee el archivo โ”‚ โ”‚ โ””โ”€โ–บ Decodifica Base64: "MTIzNA==" โ†’ "1234" โ”‚ โ”‚ โ””โ”€โ–บ Precarga los campos automรกticamente โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` **โš ๏ธ IMPORTANTE:** Base64 NO es encriptaciรณn, solo ofuscaciรณn. El archivo `.mail_config.json` estรก protegido en `.gitignore` para no subirlo a Git. --- ### ๐Ÿ“ฌ Lectura de Correos (IMAP) #### **Funciรณn Principal: `_refresh_mail_list()` (lรญneas 1635-1801)** ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ FLUJO DE LECTURA DE CORREOS โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1. SELECT 'INBOX' โ”‚ โ”‚ โ””โ”€โ–บ imap_connection.select('INBOX') โ”‚ โ”‚ โ”‚ โ”‚ 2. SEARCH ALL โ”‚ โ”‚ โ””โ”€โ–บ status, messages = imap_connection.search(None, 'ALL') โ”‚ โ”‚ โ””โ”€โ–บ mail_ids = messages[0].split() # [b'1', b'2', b'3', ...] โ”‚ โ”‚ โ”‚ โ”‚ 3. FETCH con BODY.PEEK[] (no marca como leรญdo) โ”‚ โ”‚ for mail_id in mail_ids: โ”‚ โ”‚ status, msg_data = imap_connection.fetch( โ”‚ โ”‚ mail_id, โ”‚ โ”‚ '(BODY.PEEK[] FLAGS)' # โ† PEEK es clave โ”‚ โ”‚ ) โ”‚ โ”‚ โ”‚ โ”‚ 4. EXTRAER FLAGS (\Seen) โ”‚ โ”‚ โ””โ”€โ–บ Busca en respuesta IMAP: "FLAGS (\\Seen ...)" โ”‚ โ”‚ โ””โ”€โ–บ is_seen = True si contiene "\\Seen" โ”‚ โ”‚ โ”‚ โ”‚ 5. PARSEAR EMAIL โ”‚ โ”‚ โ””โ”€โ–บ msg = email.message_from_bytes(msg_data) โ”‚ โ”‚ โ””โ”€โ–บ from_addr = decode_header(msg['From'])[0][0] โ”‚ โ”‚ โ””โ”€โ–บ subject = decode_header(msg['Subject'])[0][0] โ”‚ โ”‚ โ”‚ โ”‚ 6. GUARDAR EN LISTA LOCAL โ”‚ โ”‚ self.mail_list.append({ โ”‚ โ”‚ 'id': mail_id, โ”‚ โ”‚ 'from': from_addr, โ”‚ โ”‚ 'subject': subject, โ”‚ โ”‚ 'date': date_str, โ”‚ โ”‚ 'msg': msg, # Objeto email completo โ”‚ โ”‚ 'is_seen': is_seen โ”‚ โ”‚ }) โ”‚ โ”‚ โ”‚ โ”‚ 7. MOSTRAR EN LISTBOX โ”‚ โ”‚ if is_seen: โ”‚ โ”‚ display = f' {from_addr} - {subject}' # Sin emoji โ”‚ โ”‚ itemconfig(idx, fg='#888888') # Gris โ”‚ โ”‚ else: โ”‚ โ”‚ display = f'๐Ÿ”ต {from_addr} - {subject}' # Con emoji โ”‚ โ”‚ itemconfig(idx, fg='#000000') # Negro โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` #### **ยฟPor quรฉ BODY.PEEK[]?** ```python # โŒ MAL: RFC822 marca el correo como leรญdo automรกticamente fetch(mail_id, 'RFC822') # โœ… BIEN: BODY.PEEK[] lee sin cambiar el flag \Seen fetch(mail_id, '(BODY.PEEK[] FLAGS)') ``` --- ### ๐Ÿ“ค Envรญo de Correos (SMTP) #### **Funciรณn: `_send_mail_with_attachments()` (lรญneas 2837-2977)** ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ FLUJO DE ENVรO DE CORREO โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1. VALIDAR DESTINATARIOS โ”‚ โ”‚ to_addr_raw = "marcos@psp.es, user2@example.com" โ”‚ โ”‚ โ””โ”€โ–บ Split por comas/punto y coma โ”‚ โ”‚ โ””โ”€โ–บ recipients = ['marcos@psp.es', 'user2@example.com'] โ”‚ โ”‚ โ””โ”€โ–บ Validar regex: [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,} โ”‚ โ”‚ โ”‚ โ”‚ 2. CREAR MENSAJE MIME MULTIPART โ”‚ โ”‚ from email.mime.multipart import MIMEMultipart โ”‚ โ”‚ msg = MIMEMultipart() โ”‚ โ”‚ msg['From'] = 'marcos@psp.es' โ”‚ โ”‚ msg['To'] = 'marcos@psp.es, user2@example.com' โ”‚ โ”‚ msg['Subject'] = 'Asunto del correo' โ”‚ โ”‚ โ”‚ โ”‚ 3. ADJUNTAR CUERPO โ”‚ โ”‚ from email.mime.text import MIMEText โ”‚ โ”‚ msg.attach(MIMEText(body, 'plain', 'utf-8')) โ”‚ โ”‚ โ”‚ โ”‚ 4. ADJUNTAR ARCHIVOS โ”‚ โ”‚ for file_path in attachments: โ”‚ โ”‚ if file_ext == '.png': โ”‚ โ”‚ part = MIMEImage(file_data, name=filename) โ”‚ โ”‚ elif file_ext == '.pdf': โ”‚ โ”‚ part = MIMEApplication(file_data, _subtype='pdf') โ”‚ โ”‚ # ... otros tipos โ”‚ โ”‚ msg.attach(part) โ”‚ โ”‚ โ”‚ โ”‚ 5. CONECTAR SMTP Y ENVIAR โ”‚ โ”‚ with smtplib.SMTP('10.10.0.101', 25) as server: โ”‚ โ”‚ server.send_message(msg, to_addrs=recipients) โ”‚ โ”‚ โ”‚ โ”‚ 6. GUARDAR EN CARPETA SENT (IMAP) โ”‚ โ”‚ โ””โ”€โ–บ _save_to_sent_folder(msg) # Ver siguiente secciรณn โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` #### **Tipos MIME Soportados** | Extensiรณn | Tipo MIME | Clase Python | |-----------|-----------|--------------| | `.png`, `.jpg` | `image/*` | `MIMEImage` | | `.pdf` | `application/pdf` | `MIMEApplication` | | `.doc`, `.docx` | `application/msword` | `MIMEApplication` | | `.xls`, `.xlsx` | `application/vnd.ms-excel` | `MIMEApplication` | | `.zip`, `.rar` | `application/zip` | `MIMEApplication` | | `.txt` | `text/plain` | `MIMEText` | | Otros | `application/octet-stream` | `MIMEBase` | --- ### ๐Ÿ’พ Guardado en Servidor IMAP #### **Funciรณn: `_save_to_sent_folder()` (lรญneas 2979-3033)** Despuรฉs de enviar un correo por SMTP, se guarda una copia en la carpeta "Sent" del servidor IMAP para que aparezca en Webmin y otros clientes. ```python # Intentar mรบltiples nombres de carpeta sent_folders = ['Sent', 'INBOX.Sent', 'Enviados', 'INBOX.Enviados', 'Sent Items'] for folder in sent_folders: try: # Intentar seleccionar la carpeta status, _ = self.imap_connection.select(folder) if status == 'OK': # Guardar correo con APPEND self.imap_connection.append( folder, '\\Seen', # Marcar como leรญdo imaplib.Time2Internaldate(time.time()), msg.as_bytes() ) break except: continue # Si no existe, crearla if not folder_found: self.imap_connection.create('Sent') self.imap_connection.append('Sent', '\\Seen', date, msg_bytes) ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ GUARDADO EN CARPETA SENT DEL SERVIDOR โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1. Usuario envรญa correo con SMTP โ”‚ โ”‚ โ””โ”€โ–บ Correo enviado a marcos@psp.es โ”‚ โ”‚ โ”‚ โ”‚ 2. _save_to_sent_folder() se ejecuta automรกticamente โ”‚ โ”‚ โ”‚ โ”‚ 3. Intenta conectar a carpeta "Sent": โ”‚ โ”‚ โ”œโ”€โ–บ Intenta: SELECT 'Sent' โŒ Falla โ”‚ โ”‚ โ”œโ”€โ–บ Intenta: SELECT 'INBOX.Sent' โœ… OK โ”‚ โ”‚ โ””โ”€โ–บ Carpeta encontrada โ”‚ โ”‚ โ”‚ โ”‚ 4. Guarda el correo con APPEND: โ”‚ โ”‚ APPEND "INBOX.Sent" (\Seen) "19-Feb-2026 19:30:00" {bytes} โ”‚ โ”‚ โ”‚ โ”‚ 5. Resultado: โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ SERVIDOR WEBMIN โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€ INBOX (3 correos) โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€ Sent (1 correo NUEVO) โ† AQUร โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ 6. Al abrir Webmin: โ”‚ โ”‚ โ””โ”€โ–บ El correo aparece en "Sent" โ”‚ โ”‚ โ””โ”€โ–บ Otros clientes (Thunderbird, etc.) tambiรฉn lo ven โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ‘ฅ Envรญo a Mรบltiples Destinatarios #### **Validaciรณn y Parsing (lรญneas 2703-2740)** ```python # Entrada del usuario to_addr_raw = "marcos@psp.es, user2@example.com; user3@test.org" # 1. Split por comas O punto y coma import re recipients = re.split(r'[;,]\s*', to_addr_raw) # โ†’ ['marcos@psp.es', 'user2@example.com', 'user3@test.org'] # 2. Limpiar espacios recipients = [r.strip() for r in recipients if r.strip()] # 3. Validar formato con regex email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' invalid_emails = [email for email in recipients if not re.match(email_pattern, email)] if invalid_emails: messagebox.showwarning('โš ๏ธ Advertencia', f'Los siguientes emails no son vรกlidos:\n{", ".join(invalid_emails)}') return # 4. Confirmar si son mรบltiples if len(recipients) > 1: confirm = messagebox.askyesno('๐Ÿ“ง Mรบltiples destinatarios', f'ยฟEnviar correo a {len(recipients)} destinatarios?\n\n' + '\n'.join(f' โ€ข {email}' for email in recipients)) if not confirm: return # 5. Enviar a todos self._send_mail_with_attachments(recipients, subject, body, attachments) ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ENVรO A MรšLTIPLES DESTINATARIOS โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ Usuario escribe en campo "Para:": โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ marcos@psp.es, user2@test.com, user3@example.org โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ Al hacer clic en "๐Ÿ“ค ENVIAR": โ”‚ โ”‚ โ”‚ โ”‚ 1. Parse: Split por ',' o ';' โ”‚ โ”‚ ['marcos@psp.es', 'user2@test.com', 'user3@example.org'] โ”‚ โ”‚ โ”‚ โ”‚ 2. Validaciรณn regex de cada email โ”‚ โ”‚ โœ… marcos@psp.es โ†’ Vรกlido โ”‚ โ”‚ โœ… user2@test.com โ†’ Vรกlido โ”‚ โ”‚ โœ… user3@example.org โ†’ Vรกlido โ”‚ โ”‚ โ”‚ โ”‚ 3. Diรกlogo de confirmaciรณn: โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ ยฟEnviar a 3 destinatarios? โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ€ข marcos@psp.es โ”‚ โ”‚ โ”‚ โ”‚ โ€ข user2@test.com โ”‚ โ”‚ โ”‚ โ”‚ โ€ข user3@example.org โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ [Sรญ] [No] โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ 4. SMTP envรญa a todos: โ”‚ โ”‚ msg['To'] = 'marcos@psp.es, user2@test.com, user3@example.org' โ”‚ โ”‚ server.send_message(msg, to_addrs=[...]) โ”‚ โ”‚ โ”‚ โ”‚ 5. Mensaje de รฉxito: โ”‚ โ”‚ โœ… "Correo enviado correctamente a 3 destinatarios" โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ“Ž Manejo de Adjuntos #### **Adjuntar Archivos** ```python # Usuario hace clic en "๐Ÿ“Ž Adjuntar archivo" file_paths = filedialog.askopenfilenames( title='Seleccionar archivos', filetypes=[ ('Imรกgenes', '*.png *.jpg *.jpeg *.gif'), ('PDFs', '*.pdf'), ('Documentos', '*.doc *.docx *.xls *.xlsx'), ('Todos', '*.*') ] ) # Se guardan en lista attachments.append(file_path) # Al enviar, se procesan: for file_path in attachments: file_name = os.path.basename(file_path) # "documento.pdf" file_ext = os.path.splitext(file_path)[1] # ".pdf" with open(file_path, 'rb') as f: file_data = f.read() if file_ext == '.pdf': part = MIMEApplication(file_data, _subtype='pdf') part.add_header('Content-Disposition', 'attachment', filename=file_name) msg.attach(part) ``` #### **Imรกgenes Inline con Ctrl+V** ```python # Usuario copia una imagen y presiona Ctrl+V def on_paste(event): try: # Obtener imagen del portapapeles img = ImageGrab.grabclipboard() if img: # Guardar temporalmente temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png') img.save(temp_file.name) # Mostrar miniatura en interfaz thumbnail = img.resize((150, 150)) photo = ImageTk.PhotoImage(thumbnail) label = tk.Label(frame, image=photo) label.image = photo # Mantener referencia label.pack() # Agregar a lista de adjuntos inline_images_data.append({'data': img_bytes}) except: pass ``` --- ### ๐Ÿ–ผ๏ธ Visualizaciรณn de Correos #### **Funciรณn: `_display_mail()` (lรญneas 2055-2337)** ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ VISUALIZACIร“N DE CORREO โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1. Usuario hace clic en un correo de la lista โ”‚ โ”‚ โ””โ”€โ–บ _on_mail_select() โ†’ _display_mail(mail_info) โ”‚ โ”‚ โ”‚ โ”‚ 2. Marcar como leรญdo en servidor (si es INBOX y no leรญdo) โ”‚ โ”‚ if not mail_info['is_seen']: โ”‚ โ”‚ imap_connection.store(mail_id, '+FLAGS', '\\Seen') โ”‚ โ”‚ # Actualizar visualmente: quitar ๐Ÿ”ต, poner gris โ”‚ โ”‚ โ”‚ โ”‚ 3. Actualizar encabezados โ”‚ โ”‚ mail_from_label.config(text='De: marcos@psp.es') โ”‚ โ”‚ mail_subject_label.config(text='Asunto: Test') โ”‚ โ”‚ mail_date_label.config(text='Fecha: 19/02/2026') โ”‚ โ”‚ โ”‚ โ”‚ 4. Procesar contenido multipart โ”‚ โ”‚ if msg.is_multipart(): โ”‚ โ”‚ for part in msg.walk(): โ”‚ โ”‚ if content_type == 'text/plain': โ”‚ โ”‚ body = part.get_payload(decode=True).decode() โ”‚ โ”‚ elif part.get_filename(): # Adjunto โ”‚ โ”‚ attachments.append({...}) โ”‚ โ”‚ โ”‚ โ”‚ 5. Mostrar cuerpo en Text widget โ”‚ โ”‚ mail_body_text.delete('1.0', 'end') โ”‚ โ”‚ mail_body_text.insert('1.0', body) โ”‚ โ”‚ โ”‚ โ”‚ 6. Mostrar imรกgenes inline (si PIL estรก disponible) โ”‚ โ”‚ for att in images: โ”‚ โ”‚ img = Image.open(BytesIO(att['data'])) โ”‚ โ”‚ img.thumbnail((500, 500)) # Redimensionar โ”‚ โ”‚ photo = ImageTk.PhotoImage(img) โ”‚ โ”‚ mail_body_text.image_create('end', image=photo) โ”‚ โ”‚ โ”‚ โ”‚ 7. Mostrar otros adjuntos (PDFs, docs, etc.) โ”‚ โ”‚ for att in other_attachments: โ”‚ โ”‚ # Frame con icono, nombre, tamaรฑo y botรณn "๐Ÿ’พ Guardar" โ”‚ โ”‚ icon = '๐Ÿ“„' if PDF else '๐Ÿ“' if Word else '๐Ÿ“Ž' โ”‚ โ”‚ Button(text='๐Ÿ’พ Guardar', command=save_file) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ”ต Sistema de Indicadores Visuales ```python # Al cargar correos (lรญneas 1775-1790) for mail in mail_list: if is_seen: # Correo leรญdo display_text = f' {from_addr[:27]} - {subject[:37]}' self.mail_listbox.insert('end', display_text) idx = self.mail_listbox.size() - 1 self.mail_listbox.itemconfig(idx, fg='#888888', # Gris selectforeground='#666666' ) else: # Correo NO leรญdo display_text = f'๐Ÿ”ต {from_addr[:27]} - {subject[:37]}' self.mail_listbox.insert('end', display_text) idx = self.mail_listbox.size() - 1 self.mail_listbox.itemconfig(idx, fg='#000000', # Negro selectforeground='#1a73e8' ) # Contador de no leรญdos self.unread_count = sum(1 for m in self.mail_list if not m.get('is_seen', False)) self.unread_label.config(text=f'Correos sin leer: {self.unread_count}') ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ APARIENCIA VISUAL EN LISTBOX โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ ๐Ÿ“ฌ Bandeja de entrada Correos sin leer: 2 โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ ๐Ÿ”ต marcos@psp.es - Test para grabaciรณn โ† NO LEรDO (negro) โ”‚ โ”‚ โ”‚ โ”‚ ๐Ÿ”ต user@example.com - Propuesta proyecto โ† NO LEรDO (negro) โ”‚ โ”‚ โ”‚ โ”‚ admin@server.com - Notificaciรณn โ† LEรDO (gris) โ”‚ โ”‚ โ”‚ โ”‚ webmaster@test.org - Informe โ† LEรDO (gris) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ Al hacer clic en el primero (๐Ÿ”ต NO LEรDO): โ”‚ โ”‚ 1. Se marca como leรญdo en el servidor (STORE +FLAGS \Seen) โ”‚ โ”‚ 2. Se actualiza la visualizaciรณn: โ”‚ โ”‚ โ”œโ”€ Quita el emoji ๐Ÿ”ต โ”‚ โ”‚ โ”œโ”€ Cambia color a gris โ”‚ โ”‚ โ””โ”€ Decrementa contador: "Correos sin leer: 1" โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ›ก๏ธ Manejo de Errores y Log ```python # Funciรณn de log (lรญneas 4304-4315) def _log(self, text: str) -> None: # Verificar si estamos en hilo principal if threading.current_thread() is not threading.main_thread(): self.after(0, lambda t=text: self._log(t)) return # Verificar si el widget notes existe if not hasattr(self, 'notes') or self.notes is None: print(f'[LOG] {text}') # Consola durante inicializaciรณn return # Log normal en interfaz timestamp = datetime.datetime.now().strftime('%H:%M:%S') self.notes.insert('end', f'[{timestamp}] {text}\n') self.notes.see('end') ``` ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ EJEMPLO DE LOG EN INTERFAZ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ ๐Ÿ“ Panel de Notas โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ [19:28:43] Conectando a 10.10.0.101:143... โ”‚ โ”‚ โ”‚ โ”‚ [19:28:44] Conexiรณn IMAP establecida โ”‚ โ”‚ โ”‚ โ”‚ [19:28:44] Carpetas IMAP disponibles: ['INBOX', 'Sent'] โ”‚ โ”‚ โ”‚ โ”‚ [19:28:44] Credenciales guardadas correctamente โ”‚ โ”‚ โ”‚ โ”‚ [19:28:45] Cargando 8 correos... โ”‚ โ”‚ โ”‚ โ”‚ [19:28:46] 8 correos cargados (2 sin leer) โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] === CORREO SELECCIONADO #0: Test grabaciรณn === โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] Correo marcado como leรญdo en el servidor โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] >>> Actualizando encabezados โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] Texto plano encontrado: 245 caracteres โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] Adjunto detectado: imagen.png (image/png) โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] Imagen inline mostrada: imagen.png โ”‚ โ”‚ โ”‚ โ”‚ [19:29:10] >>> _display_mail COMPLETADO OK โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ### ๐Ÿ“Š Resumen de Funciones Clave | Funciรณn | Lรญneas | Responsabilidad | |---------|--------|-----------------| | `_build_tab_correos()` | 632-850 | Construir toda la interfaz del tab Correos | | `_load_mail_credentials()` | 1483-1525 | Cargar credenciales de `.mail_config.json` | | `_save_mail_credentials()` | 1527-1559 | Guardar credenciales con Base64 | | `_connect_mail_server()` | 1561-1613 | Conectar a IMAP, listar carpetas | | `_refresh_mail_list()` | 1635-1801 | Cargar correos de INBOX con FETCH | | `_show_inbox()` | 1803-1811 | Cambiar a bandeja de entrada | | `_show_sent()` | 1828-1880 | Cambiar a carpeta de enviados | | `_on_mail_select()` | 1982-2053 | Manejar clic en correo, marcar como leรญdo | | `_display_mail()` | 2055-2337 | Mostrar contenido, imรกgenes y adjuntos | | `_open_compose_window()` | 2353-2788 | Abrir ventana de redacciรณn | | `_send_mail_with_attachments()` | 2837-2977 | Enviar correo por SMTP con adjuntos | | `_save_to_sent_folder()` | 2979-3033 | Guardar copia en servidor IMAP | --- ### ๐Ÿ”„ Flujo Completo de Usuario ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ FLUJO COMPLETO: LEER Y ENVIAR CORREO โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”‚ 1. INICIAR APLICACIร“N โ”‚ โ”‚ โ””โ”€โ–บ python3 app.py โ”‚ โ”‚ โ””โ”€โ–บ _load_mail_credentials() precarga usuario y contraseรฑa โ”‚ โ”‚ โ”‚ โ”‚ 2. IR A TAB "CORREOS" โ”‚ โ”‚ โ””โ”€โ–บ _build_tab_correos() ya construyรณ la interfaz โ”‚ โ”‚ โ”‚ โ”‚ 3. CONECTAR โ”‚ โ”‚ โ””โ”€โ–บ Clic en "๐Ÿ”— Conectar" โ”‚ โ”‚ โ””โ”€โ–บ _connect_mail_server() โ”‚ โ”‚ โ”œโ”€ IMAP4('10.10.0.101', 143) โ”‚ โ”‚ โ”œโ”€ login('marcos@psp.es', '1234') โ”‚ โ”‚ โ””โ”€ list() โ†’ Muestra carpetas disponibles โ”‚ โ”‚ โ””โ”€โ–บ _save_mail_credentials() si "Recordar" estรก marcado โ”‚ โ”‚ โ””โ”€โ–บ _refresh_mail_list() carga correos automรกticamente โ”‚ โ”‚ โ”‚ โ”‚ 4. LEER CORREO โ”‚ โ”‚ โ””โ”€โ–บ Clic en correo de la lista โ”‚ โ”‚ โ””โ”€โ–บ _on_mail_select() โ”‚ โ”‚ โ”œโ”€ store(id, '+FLAGS', '\\Seen') si no leรญdo โ”‚ โ”‚ โ””โ”€ _display_mail(mail_info) โ”‚ โ”‚ โ”œโ”€ Actualiza encabezados โ”‚ โ”‚ โ”œโ”€ Muestra cuerpo โ”‚ โ”‚ โ”œโ”€ Muestra imรกgenes inline โ”‚ โ”‚ โ””โ”€ Muestra botones para guardar adjuntos โ”‚ โ”‚ โ”‚ โ”‚ 5. ENVIAR NUEVO CORREO โ”‚ โ”‚ โ””โ”€โ–บ Clic en "โœ‰๏ธ Nuevo correo" โ”‚ โ”‚ โ””โ”€โ–บ _open_compose_window() โ”‚ โ”‚ โ”œโ”€ Ventana emergente con campos โ”‚ โ”‚ โ”œโ”€ Botรณn "๐Ÿ“Ž Adjuntar archivo" โ”‚ โ”‚ โ”œโ”€ Soporte Ctrl+V para imรกgenes โ”‚ โ”‚ โ””โ”€ Botรณn "๐Ÿ“ค ENVIAR CORREO" โ”‚ โ”‚ โ”œโ”€ Validar destinatarios (regex) โ”‚ โ”‚ โ”œโ”€ Confirmar si mรบltiples โ”‚ โ”‚ โ”œโ”€ _send_mail_with_attachments() โ”‚ โ”‚ โ”‚ โ”œโ”€ Crear MIMEMultipart โ”‚ โ”‚ โ”‚ โ”œโ”€ Adjuntar archivos โ”‚ โ”‚ โ”‚ โ””โ”€ SMTP send_message() โ”‚ โ”‚ โ””โ”€ _save_to_sent_folder() โ”‚ โ”‚ โ””โ”€ IMAP APPEND a 'Sent' โ”‚ โ”‚ โ”‚ โ”‚ 6. VERIFICAR EN WEBMIN โ”‚ โ”‚ โ””โ”€โ–บ Abrir http://10.10.0.101:20000 โ”‚ โ”‚ โ””โ”€โ–บ Carpeta "Sent" โ†’ Correo aparece ahรญ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ## ๐ŸŽจ Caracterรญsticas del Dashboard ### ๐Ÿ“Š Monitor del Sistema - Grรกfico de CPU en lรญnea temporal - Grรกfico de memoria como รกrea - Contador de hilos del proceso ### ๐ŸŒค๏ธ API del Tiempo (Jรกvea) - Temperatura actual y sensaciรณn tรฉrmica - Humedad y velocidad del viento - Descripciรณn del clima ### ๐Ÿ›’ Anรกlisis Wallapop - Extracciรณn de informaciรณn de anuncios - Headers personalizados para API - Resultados formateados ### โฐ Sistema de Alarmas - Programaciรณn en minutos - Notificaciones visuales - Gestiรณn de alarmas activas --- ## ๐Ÿค Contribuir 1. **Fork** del proyecto 2. Crea tu **Feature Branch** (`git checkout -b feature/NuevaFuncion`) 3. **Commit** tus cambios (`git commit -m 'Add: Nueva funciรณn'`) 4. **Push** a la rama (`git push origin feature/NuevaFuncion`) 5. Abre un **Pull Request** --- ## ๐Ÿ“ Licencia Este proyecto es de carรกcter **educativo** y fue desarrollado como parte del mรณdulo de **Programaciรณn de Servicios y Procesos**. ---

Desarrollado con โค๏ธ por Marcos Ferrandiz

Proyecto 1ยบ Evaluaciรณn - PSP (Programaciรณn de Servicios y Procesos)

---

Estado Versiรณn Python