correo
This commit is contained in:
parent
f0dc49b62e
commit
f1906ec18e
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
718
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
@ -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)
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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)
|
||||||
Loading…
Reference in New Issue