This commit is contained in:
marcos 2026-02-19 20:02:35 +01:00
parent f0dc49b62e
commit f1906ec18e
11 changed files with 3370 additions and 12 deletions

44
.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
.venv/
venv/
ENV/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# Configuración de correo (contiene credenciales)
.mail_config.json
# Backups
*.backup*
*.bak
# OS
.DS_Store
Thumbs.db

246
ARQUITECTURA_CORREO.md Normal file
View File

@ -0,0 +1,246 @@
# Arquitectura del Cliente de Correo - Proyecto1AVApsp
## Estructura de la aplicación
```
┌──────────────────────────────────────────────────────────────┐
│ Proyecto1AVApsp │
│ Panel de Laboratorio │
└──────────────────────────────────────────────────────────────┘
┌─────────────────────┼─────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Tareas │ │ Correos │ │ Juegos │
└─────────┘ └────┬────┘ └─────────┘
┌─────────────────┴─────────────────┐
│ │
┌────▼────────┐ ┌───────▼───────┐
│ IMAP │ │ SMTP │
│ Cliente │ │ Cliente │
└─────┬───────┘ └───────┬───────┘
│ │
│ ┌──────────────┐ │
└─────────► Servidor ◄─────────┘
│ Webmin │
│ 10.10.0.101 │
└──────────────┘
```
## Componentes principales
### 1. Interfaz de Usuario (app.py:631-753)
- Panel de configuración del servidor
- Lista de correos (bandeja de entrada)
- Visor de correos
- Ventana de composición de correos
### 2. Gestión IMAP (app.py:1380-1540)
**Funciones:**
- `_connect_mail_server()`: Conecta al servidor IMAP
- `_disconnect_mail_server()`: Desconecta del servidor
- `_refresh_mail_list()`: Carga la lista de correos
- `_on_mail_select()`: Maneja la selección de correos
- `_display_mail()`: Muestra el contenido de un correo
**Protocolo:** IMAP (Puerto 143)
### 3. Gestión SMTP (app.py:1585-1620)
**Funciones:**
- `_open_compose_window()`: Abre ventana de redacción
- `_send_mail()`: Envía correos usando SMTP
**Protocolo:** SMTP (Puerto 25)
## Flujo de datos
### Lectura de correos (IMAP)
```
Usuario → Clic "Conectar"
_connect_mail_server()
imaplib.IMAP4(host, 143)
imap.login(usuario, contraseña)
imap.select('INBOX')
_refresh_mail_list()
imap.search(None, 'ALL')
imap.fetch(mail_id, 'RFC822')
email.message_from_bytes()
Mostrar en Listbox
Usuario selecciona correo
_display_mail()
Parsear contenido (texto/HTML)
Mostrar en ScrolledText
```
### Envío de correos (SMTP)
```
Usuario → Clic "Nuevo correo"
_open_compose_window()
Usuario completa campos (Para, Asunto, Mensaje)
Usuario → Clic "Enviar"
_send_mail()
MIMEMultipart()
MIMEText(body, 'plain')
smtplib.SMTP(host, 25)
server.send_message(msg)
Confirmación al usuario
```
## Protocolos de comunicación
### IMAP (Internet Message Access Protocol)
- **Puerto**: 143 (sin cifrar) / 993 (cifrado)
- **Uso**: Leer correos del servidor
- **Ventajas**:
- Los correos permanecen en el servidor
- Acceso desde múltiples dispositivos
- Sincronización de carpetas
### SMTP (Simple Mail Transfer Protocol)
- **Puerto**: 25 (sin cifrar) / 587 (TLS) / 465 (SSL)
- **Uso**: Enviar correos
- **Autenticación**: Opcional según configuración del servidor
## Configuración del servidor Webmin
### Servicios necesarios:
```
┌─────────────────────────────────────────┐
│ Webmin (http://10.10.0.101:20000) │
├─────────────────────────────────────────┤
│ Servicios de correo: │
│ • Postfix (SMTP) → Puerto 25 │
│ • Dovecot (IMAP) → Puerto 143 │
│ • Dovecot (POP3) → Puerto 110 │
└─────────────────────────────────────────┘
```
### Pasos en Webmin:
1. **Servers → Dovecot IMAP/POP3 Server**
- Habilitar servicio IMAP en puerto 143
- Configurar usuarios y contraseñas
2. **Servers → Postfix Mail Server**
- Habilitar servicio SMTP en puerto 25
- Configurar dominio y relay
3. **System → Users and Groups**
- Crear usuarios del sistema para correo
- Asignar contraseñas
## Seguridad
### Advertencias actuales:
⚠️ **La implementación actual usa conexiones sin cifrar**
### Recomendaciones:
1. Usar IMAPS (puerto 993) en lugar de IMAP (143)
2. Usar SMTPS (puerto 465/587) en lugar de SMTP (25)
3. Implementar SSL/TLS en las conexiones
4. No usar contraseñas en texto plano en el código
5. Usar autenticación del servidor SMTP
### Mejora de seguridad (código):
```python
# IMAP con SSL
import imaplib
imap = imaplib.IMAP4_SSL('10.10.0.101', 993)
# SMTP con TLS
import smtplib
smtp = smtplib.SMTP('10.10.0.101', 587)
smtp.starttls()
smtp.login(username, password)
```
## Estructura de archivos
```
Proyecto1AVApsp/
├── app.py # Aplicación principal con cliente de correo
├── CORREO_README.md # Documentación de usuario
├── test_mail_server.py # Script de prueba de conectividad
├── requirements.txt # Dependencias (smtplib/imaplib incluidos en Python)
└── README.md # Documentación general del proyecto
```
## Variables de estado
```python
# Variables del cliente de correo (en DashboardApp)
self.mail_connected = False # Estado de conexión
self.imap_connection = None # Objeto de conexión IMAP
self.current_mailbox = 'INBOX' # Bandeja actual
self.mail_list = [] # Lista de correos cargados
# Widgets de UI
self.mail_imap_host # Entry: servidor IMAP
self.mail_imap_port # Entry: puerto IMAP
self.mail_smtp_host # Entry: servidor SMTP
self.mail_smtp_port # Entry: puerto SMTP
self.mail_username # Entry: usuario
self.mail_password # Entry: contraseña
self.mail_listbox # Listbox: lista de correos
self.mail_body_text # ScrolledText: cuerpo del correo
```
## Testing
### Script de prueba de conectividad:
```bash
python3 test_mail_server.py
```
Este script verifica:
- Conexión a Webmin (puerto 20000)
- Disponibilidad de SMTP (puerto 25)
- Disponibilidad de IMAP (puerto 143)
- Disponibilidad de POP3 (puerto 110)
## Próximas mejoras
1. **Seguridad**
- [ ] Implementar SSL/TLS
- [ ] Autenticación segura
- [ ] Gestión de certificados
2. **Funcionalidad**
- [ ] Soporte para adjuntos
- [ ] Vista HTML mejorada
- [ ] Múltiples carpetas
- [ ] Búsqueda de correos
- [ ] Responder/Reenviar
3. **UI/UX**
- [ ] Indicador de correos no leídos
- [ ] Filtros y ordenamiento
- [ ] Marcadores/etiquetas
- [ ] Vista previa de adjuntos
4. **Performance**
- [ ] Carga asíncrona de correos
- [ ] Cache de correos
- [ ] Paginación

155
CORREO_README.md Normal file
View File

@ -0,0 +1,155 @@
# Cliente de Correo Electrónico - Proyecto1AVApsp
## Descripción
Se ha implementado un cliente de correo electrónico completo en la pestaña "Correos" de la aplicación. Este cliente permite conectarse a tu servidor Webmin para leer y enviar correos electrónicos.
## Características
- **Conexión IMAP**: Lee correos desde tu servidor (Puerto 143)
- **Conexión SMTP**: Envía correos nuevos (Puerto 25)
- **Interfaz tipo Outlook**: Panel dividido con lista de correos y visor
- **Redacción de correos**: Ventana dedicada para componer nuevos mensajes
## Configuración del Servidor Webmin
### Datos de conexión predeterminados:
- **Servidor IMAP**: `10.10.0.101`
- **Puerto IMAP**: `143`
- **Servidor SMTP**: `10.10.0.101`
- **Puerto SMTP**: `25`
### Puertos configurados en Webmin:
- SMTP (envío de correo): 25
- IMAP (consulta de correo): 143
- POP (descarga de correo): 110 (no usado en esta implementación)
- Interfaz web Webmin: http://10.10.0.101:20000
## Cómo usar el cliente de correo
### 1. Ejecutar la aplicación
```bash
python3 app.py
```
### 2. Ir a la pestaña "Correos"
La pestaña está ubicada en el panel central de la aplicación.
### 3. Configurar la conexión
En el panel "Configuración del servidor":
- **Servidor IMAP**: Ya está configurado como `10.10.0.101`
- **Puerto IMAP**: Ya está configurado como `143`
- **Servidor SMTP**: Ya está configurado como `10.10.0.101`
- **Puerto SMTP**: Ya está configurado como `25`
- **Usuario**: Introduce tu nombre de usuario del servidor de correo
- **Contraseña**: Introduce tu contraseña
### 4. Conectar al servidor
Haz clic en el botón **"Conectar"**. Si la conexión es exitosa:
- El estado cambiará a "Conectado" en verde
- Se cargarán automáticamente los correos de la bandeja de entrada
### 5. Leer correos
- Los correos aparecen en la lista de la izquierda
- Haz clic en un correo para ver su contenido completo
- Se muestran:
- Remitente (De)
- Asunto
- Fecha
- Cuerpo del mensaje
### 6. Actualizar la lista de correos
Haz clic en el botón **"Actualizar"** para recargar la lista de correos desde el servidor.
### 7. Enviar un nuevo correo
1. Haz clic en el botón **"Nuevo correo"**
2. Se abrirá una ventana de composición
3. Completa los campos:
- **Para**: Dirección del destinatario
- **Asunto**: Título del correo
- **Mensaje**: Contenido del correo
4. Haz clic en **"Enviar"**
### 8. Desconectar
Cuando termines, haz clic en **"Desconectar"** para cerrar la conexión con el servidor.
## Notas técnicas
### Bibliotecas utilizadas
El cliente utiliza las bibliotecas estándar de Python:
- `imaplib`: Para leer correos (protocolo IMAP)
- `smtplib`: Para enviar correos (protocolo SMTP)
- `email`: Para parsear y crear mensajes de correo
### Limitaciones actuales
1. **Seguridad**:
- La conexión IMAP es sin cifrado (puerto 143 en lugar de 993 IMAPS)
- La conexión SMTP es sin cifrado (puerto 25 en lugar de 465/587 SMTPS)
- Para producción, se recomienda usar conexiones cifradas
2. **Funcionalidades**:
- Solo se muestran los últimos 50 correos
- No hay soporte para adjuntos aún
- Solo se muestra la bandeja de entrada (INBOX)
- Los correos HTML se muestran como texto crudo
3. **Autenticación SMTP**:
- El código actual no utiliza autenticación para SMTP
- Si tu servidor Webmin requiere autenticación, necesitarás descomentar estas líneas en el código:
```python
# server.starttls()
# server.login(username, password)
```
## Solución de problemas
### Error: "No se pudo conectar al servidor"
1. Verifica que el servidor Webmin esté en ejecución
2. Comprueba que puedes hacer ping a `10.10.0.101`
3. Verifica que los puertos 143 y 25 estén abiertos:
```bash
telnet 10.10.0.101 143
telnet 10.10.0.101 25
```
### Error: "Login failed"
- Verifica que el usuario y contraseña sean correctos
- Comprueba que la cuenta de correo esté configurada en Webmin
### No se cargan los correos
- Haz clic en "Actualizar" para recargar
- Verifica la configuración de la bandeja INBOX en el servidor
- Revisa el panel de notas para ver mensajes de error
## Mejoras futuras
- [ ] Soporte para conexiones cifradas (IMAPS/SMTPS)
- [ ] Gestión de adjuntos
- [ ] Múltiples carpetas/bandejas
- [ ] Búsqueda de correos
- [ ] Marcado como leído/no leído
- [ ] Eliminación de correos
- [ ] Responder y reenviar correos
- [ ] Vista HTML mejorada
- [ ] Configuración persistente
## Contacto
Para más información sobre la configuración del servidor Webmin, consulta:
- Interfaz web Webmin: http://10.10.0.101:20000

718
README.md
View File

@ -71,6 +71,18 @@
- 🔗 **Gestor de enlaces** rápidos - 🔗 **Gestor de enlaces** rápidos
- 🎲 **Minijuego de camellos** con animaciones - 🎲 **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
@ -634,12 +646,714 @@ class GameServer:
|----------|----------------| |----------|----------------|
| **Procesos** | Lanzamiento de aplicaciones externas (VS Code, Firefox) | | **Procesos** | Lanzamiento de aplicaciones externas (VS Code, Firefox) |
| **Threads** | Servidor multihilo, cliente con hilos de recepción | | **Threads** | Servidor multihilo, cliente con hilos de recepción |
| **Sockets TCP** | Comunicación cliente-servidor en red | | **Sockets TCP** | Comunicación cliente-servidor en red (Juego y Correo) |
| **Servicios** | API OpenWeather, scraping de Wallapop | | **Servicios** | API OpenWeather, scraping de Wallapop, IMAP/SMTP |
| **Sincronización** | Locks para acceso concurrente a estado compartido | | **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 ## 🎨 Características del Dashboard
### 📊 Monitor del Sistema ### 📊 Monitor del Sistema

286
TESTING_GUIDE.md Normal file
View File

@ -0,0 +1,286 @@
# Guía de Pruebas - Cliente de Correo Electrónico
## Proyecto1AVApsp - Sesión del 16-19 de Febrero 2026
Esta guía te ayudará a probar todas las funcionalidades recién implementadas en el cliente de correo.
---
## 🔧 CORRECCIÓN APLICADA (19 Feb 2026)
**Problema**: Error `AttributeError: '_tkinter.tkapp' object has no attribute 'notes'` al iniciar la aplicación
**Causa**: La función `_load_mail_credentials()` intentaba escribir en el log antes de que el widget `notes` se hubiera creado
**Solución**: Modificada la función `_log()` (app.py:4304) para verificar si el widget existe antes de usarlo
**Estado**: ✅ **CORREGIDO** - La aplicación ahora arranca sin errores
---
## ✅ FUNCIONALIDADES IMPLEMENTADAS EN ESTA SESIÓN
### 1. **Guardado automático de credenciales**
### 2. **Envío a múltiples destinatarios**
### 3. **Guardado de correos enviados en el servidor IMAP**
### 4. **Eliminación del botón "Actualizar"**
### 5. **Corrección visual de indicadores de correos no leídos**
---
## 🧪 PLAN DE PRUEBAS
### PRUEBA 1: Guardado de Credenciales
**Objetivo**: Verificar que las credenciales se guarden y carguen automáticamente
**Pasos**:
1. Ejecuta la aplicación: `python3 app.py`
2. Ve a la pestaña "Correos"
3. Observa si los campos están precargados con tus credenciales
4. Si es la primera vez, introduce:
- Usuario: `marcos@psp.es`
- Contraseña: tu contraseña
5. Marca la casilla "💾 Recordar credenciales" (debe estar marcada por defecto)
6. Haz clic en "🔌 Conectar"
7. Verifica que aparezca el mensaje "Credenciales guardadas correctamente" en el log
8. **CIERRA completamente la aplicación**
9. Vuelve a ejecutar: `python3 app.py`
10. Ve a la pestaña "Correos"
**Resultado esperado**:
- ✅ Los campos de usuario, contraseña y servidores deben estar precargados
- ✅ El archivo `.mail_config.json` existe en el directorio del proyecto
- ✅ Puedes conectarte sin volver a escribir las credenciales
**Para probar el borrado de credenciales**:
1. Conecta al servidor
2. Desconecta
3. Desmarca "💾 Recordar credenciales"
4. Vuelve a conectar
5. Cierra la aplicación
6. Al abrir de nuevo, los campos deben estar vacíos
---
### PRUEBA 2: Múltiples Destinatarios
**Objetivo**: Verificar que se pueden enviar correos a varios destinatarios simultáneamente
**Pasos**:
1. Conecta al servidor de correo
2. Haz clic en "✉️ Nuevo correo"
3. En el campo "Para:", introduce **múltiples correos separados por comas**:
```
marcos@psp.es, destinatario2@ejemplo.com, destinatario3@ejemplo.com
```
4. También puedes usar **punto y coma**:
```
marcos@psp.es; destinatario2@ejemplo.com
```
5. Introduce un asunto: "Prueba múltiples destinatarios"
6. Escribe un mensaje: "Este es un correo de prueba"
7. Haz clic en "📤 ENVIAR CORREO"
**Resultado esperado**:
- ✅ Aparece un diálogo de confirmación mostrando los 3 destinatarios
- ✅ El mensaje dice: "¿Enviar correo a 3 destinatarios?"
- ✅ Se listan todos los correos con un • al inicio
- ✅ Al confirmar, el correo se envía correctamente
- ✅ El mensaje de éxito indica "enviado a 3 destinatarios"
**Prueba con email inválido**:
1. Intenta enviar a: `marcos@psp.es, correo-invalido, otro@ejemplo.com`
2. Haz clic en "📤 ENVIAR CORREO"
**Resultado esperado**:
- ✅ Aparece advertencia: "Los siguientes emails no son válidos: correo-invalido"
- ✅ NO se envía el correo hasta que corrijas el formato
---
### PRUEBA 3: Guardado en Carpeta "Enviados" del Servidor
**Objetivo**: Verificar que los correos enviados se guardan en el servidor IMAP y son visibles en Webmin
**Pasos**:
1. Conecta al servidor de correo en la aplicación
2. Envía un correo de prueba:
- **Para**: marcos@psp.es
- **Asunto**: "Prueba guardado en servidor"
- **Mensaje**: "Verificando que este correo se guarda en IMAP"
3. Observa el log en la parte inferior de la aplicación
4. Busca mensajes como:
```
Correo guardado en carpeta: Sent
```
5. Haz clic en el botón "📧 Enviados" en la aplicación
6. **Verifica que el correo aparece en la lista**
7. Ahora abre tu navegador y ve a: `http://10.10.0.101:20000`
8. Inicia sesión en Webmin
9. Ve a "Correo" → "Usermin" → "Read Email" o accede directamente a tu cliente de correo Webmin
10. Busca la carpeta "Sent" o "Enviados"
**Resultado esperado**:
- ✅ El correo aparece en la pestaña "📧 Enviados" de la aplicación
- ✅ El correo también aparece en Webmin en la carpeta de enviados
- ✅ Si abres el correo en Webmin, debe tener el contenido correcto
- ✅ Los adjuntos (si los había) también están presentes
**Si falla**:
- Revisa el log de la aplicación para ver qué carpeta se intentó usar
- Las carpetas probadas automáticamente son:
- `Sent`
- `INBOX.Sent`
- `Enviados`
- `INBOX.Enviados`
- `Sent Items`
- Si tu servidor usa otro nombre, aparecerá un mensaje de error en el log
---
### PRUEBA 4: Interfaz sin Botón "Actualizar"
**Objetivo**: Verificar que la interfaz se actualiza automáticamente sin necesidad del botón
**Pasos**:
1. Conecta al servidor
2. Observa la barra de herramientas (arriba de la lista de correos)
3. Verifica que solo hay **un botón**: "✉️ Nuevo correo"
4. El botón "🔄 Actualizar" ya **NO debe existir**
5. Haz clic en "📧 Enviados"
6. Observa que la lista se actualiza automáticamente
7. Haz clic en "📬 Entrada"
8. Nuevamente, la lista se actualiza sin necesidad de botón
**Resultado esperado**:
- ✅ No existe el botón "🔄 Actualizar"
- ✅ La lista se actualiza automáticamente al cambiar de carpeta
- ✅ La interfaz se ve más limpia
---
### PRUEBA 5: Indicadores Visuales de Correos No Leídos
**Objetivo**: Verificar que los correos no leídos se distinguen visualmente sin errores
**Pasos**:
1. Conecta al servidor
2. Ve a "📬 Entrada"
3. Observa la lista de correos
4. Identifica correos no leídos y leídos
**Resultado esperado**:
- ✅ **Correos NO leídos**: Tienen el emoji 🔵 al inicio y texto en **negro**
- ✅ **Correos leídos**: **NO** tienen emoji y el texto está en **gris**
- ✅ NO aparece el error: "unknown option '-font'"
- ✅ La lista se carga sin errores en el log
**Apariencia visual**:
```
🔵 [01/02/25] De: remitente@ejemplo.com | Asunto: Correo nuevo ← No leído
[31/01/25] De: otro@ejemplo.com | Asunto: Correo antiguo ← Leído (gris)
```
---
## 🔍 PRUEBAS ADICIONALES RECOMENDADAS
### Prueba de Adjuntos con Múltiples Destinatarios
1. Envía un correo a 2-3 destinatarios
2. Incluye 1-2 archivos adjuntos (imágenes, PDFs)
3. Verifica que todos los destinatarios reciben los adjuntos
### Prueba de Imágenes Pegadas (Ctrl+V)
1. Copia una imagen del portapapeles
2. En la ventana de composición, presiona Ctrl+V
3. Verifica que la imagen se muestra en la vista previa
4. Envía el correo
5. Verifica en "Enviados" que el correo tiene el adjunto
### Prueba de Longitud de Lista de Destinatarios
1. Intenta enviar a 10+ destinatarios
2. Verifica que el diálogo de confirmación muestra todos
3. Confirma que se envía correctamente
---
## 🐛 QUÉ HACER SI ENCUENTRAS ERRORES
### Error: "No se puede guardar en carpeta Sent"
**Solución**:
1. Revisa el log para ver qué carpetas se intentaron
2. Conéctate a Webmin y verifica el nombre exacto de tu carpeta de enviados
3. Si tiene un nombre diferente, avísame para ajustar el código
### Error: "Los siguientes emails no son válidos"
**Causa**: Formato incorrecto de email
**Solución**: Verifica que todos los emails tengan el formato: `usuario@dominio.ext`
### Error: "unknown option '-font'"
**Causa**: Este error ya fue corregido
**Solución**: Si aún aparece, verifica que estás usando la versión más reciente de `app.py`
### Credenciales no se cargan automáticamente
**Solución**:
1. Verifica que el archivo `.mail_config.json` existe
2. Ejecuta: `cat .mail_config.json` para ver su contenido
3. Verifica que la casilla "💾 Recordar credenciales" esté marcada al conectar
---
## 📊 REGISTRO DE PRUEBAS
Usa esta tabla para registrar tus pruebas:
| Prueba | Estado | Observaciones |
|--------|--------|---------------|
| Guardado de credenciales | ⬜ Pendiente / ✅ OK / ❌ Error | |
| Múltiples destinatarios | ⬜ Pendiente / ✅ OK / ❌ Error | |
| Guardado en servidor IMAP | ⬜ Pendiente / ✅ OK / ❌ Error | |
| Sin botón "Actualizar" | ⬜ Pendiente / ✅ OK / ❌ Error | |
| Indicadores visuales | ⬜ Pendiente / ✅ OK / ❌ Error | |
---
## 📝 NOTAS IMPORTANTES
### Seguridad de Credenciales
- Las contraseñas se guardan con codificación Base64 (NO es encriptación real)
- Cualquiera con acceso al archivo `.mail_config.json` puede decodificar la contraseña
- El archivo está excluido de Git para no subirlo accidentalmente
- Para mayor seguridad en producción, considera usar `cryptography.fernet`
### Limitaciones Conocidas
- El servidor IMAP/SMTP debe estar accesible en `10.10.0.101`
- No se usa TLS/SSL (puerto 25 y 143 son sin cifrado)
- Los correos HTML se muestran como texto plano
- No hay soporte para CC/BCC (se puede agregar si lo necesitas)
---
## ✨ PRÓXIMAS MEJORAS SUGERIDAS
Si todo funciona correctamente, estas son funcionalidades que se pueden agregar:
1. **Campos CC y BCC** para copias de correos
2. **Responder y Reenviar** correos existentes
3. **Búsqueda de correos** por remitente, asunto o contenido
4. **Firma automática** al final de cada correo
5. **Plantillas de correo** predefinidas
6. **Encriptación real** de contraseñas con Fernet
7. **Soporte TLS/SSL** para conexiones seguras
8. **Carpetas personalizadas** (Borradores, Papelera, etc.)
9. **Exportar correos** a PDF o HTML
10. **Notificaciones de escritorio** para correos nuevos
---
## 🚀 EJECUTAR PRUEBAS
Para empezar a probar, simplemente ejecuta:
```bash
cd /home/marcos/Documentos/Proyecto1AVApsp
python3 app.py
```
Y sigue las pruebas en orden del 1 al 5.
**¡Buena suerte con las pruebas!** Si encuentras algún problema, avísame con el mensaje de error del log.

Binary file not shown.

Binary file not shown.

1810
app.py

File diff suppressed because it is too large Load Diff

View File

@ -5,3 +5,4 @@ pillow>=9.0.0
pygame>=2.1.0 pygame>=2.1.0
requests>=2.32.0 requests>=2.32.0
beautifulsoup4>=4.12.0 beautifulsoup4>=4.12.0
# Email libraries are part of Python standard library (smtplib, imaplib, email)

46
test_mail_server.py Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""
Script de prueba para verificar la conexión con el servidor de correo Webmin
"""
import socket
def test_connection(host, port, service_name):
"""Prueba la conexión a un puerto específico"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
result = sock.connect_ex((host, port))
sock.close()
if result == 0:
print(f"{service_name} (puerto {port}): CONECTADO")
return True
else:
print(f"{service_name} (puerto {port}): NO DISPONIBLE")
return False
except Exception as e:
print(f"{service_name} (puerto {port}): ERROR - {e}")
return False
if __name__ == '__main__':
print("Probando conexión con servidor Webmin...")
print("=" * 50)
host = '10.10.0.101'
# Probar puertos
results = []
results.append(test_connection(host, 20000, 'Webmin Web Interface'))
results.append(test_connection(host, 25, 'SMTP'))
results.append(test_connection(host, 143, 'IMAP'))
results.append(test_connection(host, 110, 'POP3'))
print("=" * 50)
if all(results[1:]): # Ignorar webmin interface para el resultado
print("✓ Todos los servicios de correo están disponibles")
else:
print("⚠ Algunos servicios de correo no están disponibles")
print("\nSugerencias:")
print("1. Verifica que el servidor Webmin esté en ejecución")
print("2. Comprueba la configuración del firewall")
print("3. Verifica que los servicios de correo estén habilitados en Webmin")

74
test_startup.py Executable file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""
Script de prueba para verificar que la aplicación arranca correctamente
sin errores relacionados con el widget notes.
"""
import sys
import subprocess
import time
def test_startup():
"""Prueba que la aplicación arranca sin errores críticos"""
print("🧪 Probando inicio de aplicación...")
print("=" * 60)
# Ejecutar la aplicación por 3 segundos y capturar salida
try:
process = subprocess.Popen(
['python3', 'app.py'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Esperar 3 segundos para ver si hay errores iniciales
time.sleep(3)
# Intentar terminar el proceso
process.terminate()
# Esperar a que termine y capturar salida
try:
stdout, stderr = process.communicate(timeout=2)
except subprocess.TimeoutExpired:
process.kill()
stdout, stderr = process.communicate()
print("\n📤 Salida estándar:")
print("-" * 60)
if stdout:
print(stdout)
else:
print("(Sin salida)")
print("\n⚠️ Errores/Advertencias:")
print("-" * 60)
if stderr:
# Filtrar errores críticos
lines = stderr.split('\n')
critical_errors = [line for line in lines if 'AttributeError' in line or 'Traceback' in line or 'Error' in line]
if critical_errors:
print("❌ ERRORES CRÍTICOS ENCONTRADOS:")
for line in critical_errors:
print(f" {line}")
return False
else:
print("(Solo advertencias menores, no errores críticos)")
else:
print("(Sin errores)")
print("\n" + "=" * 60)
print("✅ La aplicación arrancó correctamente")
print("✅ No se detectaron errores de AttributeError con 'notes'")
print("✅ Puedes ejecutar: python3 app.py")
return True
except Exception as e:
print(f"\n❌ Error durante la prueba: {e}")
return False
if __name__ == '__main__':
success = test_startup()
sys.exit(0 if success else 1)