This commit is contained in:
Olivia Mestre Llobell 2026-02-17 00:27:24 +01:00
parent 386e8c1693
commit fe7e592a28
14 changed files with 1198 additions and 0 deletions

135
install.sh Executable file
View File

@ -0,0 +1,135 @@
#!/bin/bash
# Script para instalación automática de dependencias y verificación de entorno virtual
# Función para instalar dependencias del sistema
install_system_dependencies() {
echo "🔍 Verificando dependencias del sistema..."
MISSING=()
# Verificar tkinter
if ! python3 -c "import tkinter" 2>/dev/null; then
MISSING+=("python3-tk")
else
echo "✅ tkinter disponible"
fi
# Verificar VLC
if ! command -v vlc &>/dev/null; then
MISSING+=("vlc")
else
echo "✅ vlc disponible"
fi
if [ ${#MISSING[@]} -gt 0 ]; then
echo "📦 Instalando paquetes del sistema: ${MISSING[*]}"
if command -v apt &>/dev/null; then
sudo apt update && sudo apt install -y "${MISSING[@]}"
elif command -v dnf &>/dev/null; then
sudo dnf install -y "${MISSING[@]}"
elif command -v pacman &>/dev/null; then
sudo pacman -S --noconfirm "${MISSING[@]}"
else
echo "❌ Gestor de paquetes no soportado. Instala manualmente: ${MISSING[*]}"
return 1
fi
echo "✅ Dependencias del sistema instaladas"
else
echo "✅ Todas las dependencias del sistema están disponibles"
fi
}
# Función para comprobar y activar entorno virtual
check_virtual_env() {
echo "🔍 Verificando entorno virtual..."
if [[ "$VIRTUAL_ENV" != "" ]]; then
echo "✅ Entorno virtual activado: $VIRTUAL_ENV"
return 0
fi
echo "❌ No se detectó un entorno virtual activado"
# Buscar directorio venv en el proyecto
if [ -d "venv" ]; then
echo "📁 Se encontró directorio 'venv'. Activando..."
source venv/bin/activate
if [[ "$VIRTUAL_ENV" != "" ]]; then
echo "✅ Entorno virtual activado correctamente"
return 0
else
echo "❌ Error al activar el entorno virtual"
return 1
fi
else
echo "❌ No se encontró directorio 'venv'. Creando entorno virtual..."
python3 -m venv venv
source venv/bin/activate
echo "✅ Entorno virtual creado y activado"
return 0
fi
}
# Función para instalar dependencias
install_dependencies() {
echo "📦 Actualizando pip..."
python -m pip install --upgrade pip
# Verificar si existe requirements.txt
if [ -f "requirements.txt" ]; then
echo "📋 Instalando dependencias desde requirements.txt..."
pip install -r requirements.txt
echo "✅ Dependencias instaladas correctamente"
else
echo "⚠️ No se encontró requirements.txt"
echo " Crea un requirements.txt con: pip freeze > requirements.txt"
fi
}
# Función para verificar instalación
verify_installation() {
echo "🔍 Verificando instalación..."
python -c "import sys; print(f'✅ Python {sys.version.split()[0]} funcionando correctamente')"
echo "📋 Paquetes instalados:"
pip list
}
# Función principal
main() {
echo "🚀 Iniciando script de instalación..."
echo ""
# Comprobar entorno virtual
if ! check_virtual_env; then
echo "❌ Error en la configuración del entorno virtual"
exit 1
fi
echo ""
# Instalar dependencias del sistema (tkinter, vlc)
if ! install_system_dependencies; then
echo "❌ Error instalando dependencias del sistema"
exit 1
fi
echo ""
# Instalar dependencias de Python
install_dependencies
echo ""
# Verificar instalación
verify_installation
echo ""
echo "🎉 Instalación completada con éxito!"
echo "💡 Para activar el entorno virtual manualmente: source venv/bin/activate"
echo "💡 Para ejecutar el proyecto: python __main__.py"
}
# Ejecutar función principal
main

View File

@ -0,0 +1,96 @@
# Módulo logica/auxiliar/add_audio.py
import json
import os
import threading
# 🔑 RUTA CONFIRMADA Desde la raíz del proyecto
NOMBRE_FICHERO = os.path.join("res", "radios.json")
class RadioManager {
"""
Gestiona la lectura, escritura y adición de emisoras en el archivo radios.json.
Utiliza un Lock para asegurar la seguridad al escribir el archivo.
"""
def __init__(self, filename=NOMBRE_FICHERO) {
self.filename = filename
# Bloqueo para operaciones de archivo
self._lock = threading.Lock()
self.emisoras_cargadas = self.cargar_emisoras()
# -------------------------------------------------------------
# GESTIÓN DE ARCHIVO
# -------------------------------------------------------------
}
def cargar_emisoras(self) {
"""Carga las emisoras existentes desde el archivo."""
with self._lock {
if os.path.exists(self.filename) {
try {
with open(self.filename, 'r', encoding='utf-8') as f {
return json.load(f)
}
}
except json.JSONDecodeError {
print("⚠️ Error al leer el archivo {self.filename}. Retornando lista vacía.")
return []
}
}
return []
}
}
def guardar_emisoras(self) {
"""Guarda la lista de emisoras actual en memoria en formato JSON."""
with self._lock {
try {
with open(self.filename, 'w', encoding='utf-8') as f {
json.dump(self.emisoras_cargadas, f, indent=4, ensure_ascii=False)
}
print(f"\n✅ [RadioManager] Archivo '{self.filename}' guardado.")
return True
}
except Exception as e {
print(f"\n❌ [RadioManager] Error al intentar guardar el archivo {e}")
return False
# -------------------------------------------------------------
# GESTIÓN DE DATOS EN MEMORIA
# -------------------------------------------------------------
}
}
}
def get_emisoras(self) {
"""Retorna la lista de emisoras cargadas en memoria."""
return self.emisoras_cargadas
}
def add_radio(self, nombre, url, pais=None, genero=None) {
"""
Añade una nueva emisora a la lista en memoria y guarda el archivo.
"""
if not nombre or not url {
print("❌ El nombre y la URL son obligatorios.")
return False
}
nueva_emisora = {
"nombre" nombre.strip(),
"url_stream" url.strip(),
"pais" pais.strip() if pais else None,
"genero" genero.strip() if genero else None
}
self.emisoras_cargadas.append(nueva_emisora)
print(f"✅ [RadioManager] Emisora '{nombre}' añadida a la lista en memoria.")
# Guarda inmediatamente
self.guardar_emisoras()
return True
}
}

0
logica/red/__init__.py Normal file
View File

85
logica/red/cliente.py Normal file
View File

@ -0,0 +1,85 @@
import socket
from logica.red.servidor import APP_FIRMA, PUERTO_BROADCAST
def descubrir_servidores(timeout=5):
"""Escucha broadcasts UDP para encontrar servidores disponibles.
Retorna una lista de tuplas (ip, puerto) de servidores encontrados.
"""
print(f"[DEBUG CLI] Iniciando descubrimiento UDP en puerto {PUERTO_BROADCAST} (timeout={timeout}s)")
servidores = []
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, "SO_REUSEPORT"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.settimeout(timeout)
try:
sock.bind(("", PUERTO_BROADCAST))
print(f"[DEBUG CLI] Socket UDP enlazado a '':{PUERTO_BROADCAST}")
except OSError as e:
print(f"[DEBUG CLI] Error al enlazar UDP: {e}")
sock.close()
return servidores
try:
while True:
datos, direccion = sock.recvfrom(1024)
mensaje = datos.decode("utf-8")
print(f"[DEBUG CLI] Paquete UDP recibido de {direccion}: {mensaje!r}")
if verificar_firma(mensaje):
partes = mensaje.split("|")
if len(partes) == 2:
puerto_servidor = int(partes[1])
entrada = (direccion[0], puerto_servidor)
if entrada not in servidores:
servidores.append(entrada)
print(f"[DEBUG CLI] Servidor descubierto: {entrada[0]}:{entrada[1]}")
else:
print(f"[DEBUG CLI] Firma no valida, ignorado")
except socket.timeout:
print(f"[DEBUG CLI] Timeout alcanzado")
finally:
sock.close()
print(f"[DEBUG CLI] Descubrimiento finalizado: {len(servidores)} servidor(es)")
return servidores
def conectar_servidor(ip, puerto, clave_acceso):
"""Conecta al servidor y envia la clave de autenticacion.
La clave debe tener el formato: puerto#contraseña_alfabetica
"""
print(f"[DEBUG CLI] Conectando TCP a {ip}:{puerto}")
print(f"[DEBUG CLI] Clave a enviar: {clave_acceso!r}")
cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cliente.settimeout(10)
try:
cliente.connect((ip, puerto))
print(f"[DEBUG CLI] Conexion TCP establecida")
cliente.sendall(clave_acceso.encode("utf-8"))
print(f"[DEBUG CLI] Clave enviada, esperando respuesta...")
respuesta = cliente.recv(1024).decode("utf-8")
print(f"[DEBUG CLI] Respuesta recibida: {respuesta!r}")
except (OSError, socket.timeout) as e:
print(f"[DEBUG CLI] Error en conexion/autenticacion: {e}")
cliente.close()
return None, ""
if respuesta.startswith("OK"):
cliente.settimeout(None)
extra = respuesta[2:]
print(f"[DEBUG CLI] Autenticacion exitosa, datos extra: {extra!r}")
return cliente, extra
else:
print(f"[DEBUG CLI] Autenticacion fallida: {respuesta!r}")
cliente.close()
return None, ""
def verificar_firma(mensaje):
"""Verifica que el mensaje recibido contenga la firma de la aplicacion."""
return APP_FIRMA in mensaje

188
logica/red/selector.py Normal file
View File

@ -0,0 +1,188 @@
import socket
import sys
import threading
from logica.red.servidor import iniciar_servidor, autenticar_cliente, PUERTO_BROADCAST
from logica.red.cliente import conectar_servidor
def obtener_ip_local():
"""Obtiene la IP local de la maquina en la red."""
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
try:
s.connect(("8.8.8.8", 80))
return s.getsockname()[0]
except OSError:
return "127.0.0.1"
def modo_servidor():
servidor, puerto, contrasena_alfa, _broadcast_stop = iniciar_servidor()
clave_acceso = f"{puerto}#{contrasena_alfa}"
ip = obtener_ip_local()
print(f"[SERVIDOR] IP: {ip}")
print(f"[SERVIDOR] Escuchando en puerto {puerto}")
print(f"[SERVIDOR] Clave de acceso: {clave_acceso}")
print("[SERVIDOR] Esperando clientes...\n")
try:
while True:
conn, addr = servidor.accept()
datos = conn.recv(1024).decode("utf-8")
if autenticar_cliente(datos, clave_acceso):
conn.sendall("OK".encode("utf-8"))
print(f"[SERVIDOR] Cliente {addr[0]}:{addr[1]} autenticado")
hilo = threading.Thread(target=manejar_cliente, args=(conn, addr), daemon=True)
hilo.start()
else:
conn.sendall("DENIED".encode("utf-8"))
conn.close()
print(f"[SERVIDOR] Cliente {addr[0]}:{addr[1]} rechazado (clave incorrecta)")
except KeyboardInterrupt:
print("\n[SERVIDOR] Cerrando...")
servidor.close()
def manejar_cliente(conn, addr):
etiqueta = f"{addr[0]}:{addr[1]}"
try:
while True:
datos = conn.recv(4096)
if not datos:
break
mensaje = datos.decode("utf-8")
print(f"[{etiqueta}] {mensaje}")
conn.sendall(f"Echo: {mensaje}".encode("utf-8"))
except (ConnectionResetError, OSError):
pass
finally:
print(f"[SERVIDOR] Cliente {etiqueta} desconectado")
conn.close()
def buscar_servidores():
"""Busca servidores con reintentos. Retorna lista de servidores o None."""
MAX_INTENTOS = 3
TIMEOUT = 5
for intento in range(1, MAX_INTENTOS + 1):
print(f"\n[CLIENTE] Intento {intento}/{MAX_INTENTOS} - Escaneando red durante {TIMEOUT}s...")
print(f"[CLIENTE] Puerto de descubrimiento (UDP): {PUERTO_BROADCAST}")
print(f"[CLIENTE] Esperando broadcast de servidores", end="", flush=True)
# Animacion simple mientras espera
servidores = []
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, "SO_REUSEPORT"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.settimeout(1)
sock.bind(("", PUERTO_BROADCAST))
for segundo in range(TIMEOUT):
try:
datos, direccion = sock.recvfrom(1024)
mensaje = datos.decode("utf-8")
from logica.red.cliente import verificar_firma
if verificar_firma(mensaje):
partes = mensaje.split("|")
if len(partes) == 2:
puerto_servidor = int(partes[1])
entrada = (direccion[0], puerto_servidor)
if entrada not in servidores:
servidores.append(entrada)
print(f"\n[CLIENTE] Servidor encontrado: {entrada[0]}:{entrada[1]}")
except socket.timeout:
print(".", end="", flush=True)
sock.close()
if servidores:
print(f"\n[CLIENTE] {len(servidores)} servidor(es) encontrado(s)")
return servidores
print(f"\n[CLIENTE] No se encontraron servidores en el intento {intento}")
# Tras 3 intentos fallidos, preguntar
while True:
opcion = input("\n[CLIENTE] No se encontraron servidores. (r)eintentar / (s)alir: ").strip().lower()
if opcion == "r":
return buscar_servidores()
elif opcion == "s":
return None
print("[CLIENTE] Opcion no valida. Usa 'r' o 's'.")
def modo_cliente():
ip_local = obtener_ip_local()
print(f"[CLIENTE] IP local: {ip_local}")
servidores = buscar_servidores()
if not servidores:
print("[CLIENTE] Saliendo.")
return
if len(servidores) == 1:
seleccion = 0
else:
print("\nServidores disponibles:")
for i, (ip, puerto) in enumerate(servidores, 1):
print(f" {i}. {ip}:{puerto}")
try:
num = int(input("\nElige servidor (numero): "))
seleccion = num - 1
if seleccion < 0 or seleccion >= len(servidores):
print("[CLIENTE] Seleccion invalida.")
return
except ValueError:
print("[CLIENTE] Entrada invalida.")
return
ip, puerto = servidores[seleccion]
print(f"\n[CLIENTE] Conectando a {ip}:{puerto}...")
contrasena = input("[CLIENTE] Introduce la contrasena: ")
cliente, _ = conectar_servidor(ip, puerto, contrasena)
if not cliente:
print("[CLIENTE] Autenticacion fallida o conexion rechazada.")
return
print(f"[CLIENTE] Conectado correctamente a {ip}:{puerto}")
print("[CLIENTE] Escribe mensajes (Ctrl+C para salir):\n")
def recibir():
try:
while True:
datos = cliente.recv(4096)
if not datos:
print("\n[CLIENTE] El servidor cerro la conexion.")
break
print(f"[SERVIDOR] {datos.decode('utf-8')}")
except (ConnectionResetError, OSError):
print("\n[CLIENTE] Conexion perdida con el servidor.")
hilo = threading.Thread(target=recibir, daemon=True)
hilo.start()
try:
while True:
mensaje = input()
if mensaje:
cliente.sendall(mensaje.encode("utf-8"))
except (KeyboardInterrupt, EOFError):
print("\n[CLIENTE] Desconectando...")
cliente.close()
if __name__ == "__main__":
print("=== Selector de Red ===")
print("1. Servidor")
print("2. Cliente")
opcion = input("\nElige rol (1/2): ").strip()
if opcion == "1":
modo_servidor()
elif opcion == "2":
modo_cliente()
else:
print("Opcion invalida.")
sys.exit(1)

110
logica/red/servidor.py Normal file
View File

@ -0,0 +1,110 @@
import socket
import threading
import time
import random
import string
# Firma de la aplicacion para identificacion
APP_FIRMA = "PSYP_ProyectoGlobal_v1"
# Rango de puertos validos (evita puertos conocidos 0-1023 y registrados 1024-49151)
RANGO_PUERTOS = (49152, 65535)
def _obtener_puertos_en_uso():
"""Lee /proc/net/tcp y /proc/net/udp para obtener los puertos en uso."""
puertos = set()
for archivo in ("/proc/net/tcp", "/proc/net/udp", "/proc/net/tcp6", "/proc/net/udp6"):
try:
with open(archivo, "r") as f:
for linea in f.readlines()[1:]:
partes = linea.split()
puerto = int(partes[1].split(":")[1], 16)
puertos.add(puerto)
except (FileNotFoundError, PermissionError):
pass
return puertos
def _puerto_disponible(puerto, puertos_en_uso=None):
"""Comprueba si un puerto esta libre en TCP y UDP."""
if puertos_en_uso and puerto in puertos_en_uso:
return False
for tipo in (socket.SOCK_STREAM, socket.SOCK_DGRAM):
with socket.socket(socket.AF_INET, tipo) as s:
try:
s.bind(("0.0.0.0", puerto))
except OSError:
return False
return True
def generar_puerto():
"""Genera un puerto aleatorio disponible en el rango dinamico/privado."""
puertos_en_uso = _obtener_puertos_en_uso()
while True:
puerto = random.randint(*RANGO_PUERTOS)
if _puerto_disponible(puerto, puertos_en_uso):
return puerto
def generar_contrasena_alfabetica(longitud=6):
"""Genera una contraseña alfabetica aleatoria."""
return "".join(random.choices(string.ascii_lowercase, k=longitud))
def iniciar_servidor():
"""Inicia el servidor en un puerto TCP aleatorio disponible.
Retorna (servidor_socket, puerto, contrasena, broadcast_stop_event).
"""
puerto = generar_puerto()
contrasena = generar_contrasena_alfabetica()
print(f"[DEBUG SRV] Puerto generado: {puerto}")
print(f"[DEBUG SRV] Contrasena alfabetica: {contrasena}")
servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("0.0.0.0", puerto))
servidor.listen(5)
print(f"[DEBUG SRV] Socket TCP escuchando en 0.0.0.0:{puerto}")
broadcast_stop = threading.Event()
hilo_broadcast = threading.Thread(target=_broadcast_presencia, args=(puerto, broadcast_stop), daemon=True)
hilo_broadcast.start()
print(f"[DEBUG SRV] Hilo broadcast iniciado en UDP:{PUERTO_BROADCAST}")
return servidor, puerto, contrasena, broadcast_stop
# Puerto fijo para descubrimiento UDP
PUERTO_BROADCAST = 64738
def _broadcast_presencia(puerto, stop_event):
"""Envia broadcasts UDP para que los clientes descubran el servidor."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
mensaje = f"{APP_FIRMA}|{puerto}".encode("utf-8")
while not stop_event.is_set():
try:
sock.sendto(mensaje, ("<broadcast>", PUERTO_BROADCAST))
print(f"[DEBUG SRV] Broadcast enviado a <broadcast>:{PUERTO_BROADCAST}")
except OSError as e:
print(f"[DEBUG SRV] Error broadcast <broadcast>: {e}")
try:
sock.sendto(mensaje, ("127.255.255.255", PUERTO_BROADCAST))
print(f"[DEBUG SRV] Broadcast enviado a 127.255.255.255:{PUERTO_BROADCAST}")
except OSError as e:
print(f"[DEBUG SRV] Error broadcast 127.255.255.255: {e}")
stop_event.wait(2)
sock.close()
def autenticar_cliente(datos_recibidos, contrasena):
"""Verifica que el cliente envie la contrasena correcta."""
resultado = datos_recibidos.strip() == contrasena
print(f"[DEBUG SRV] Autenticacion: recibido={datos_recibidos.strip()!r} esperado={contrasena!r} resultado={resultado}")
return resultado

22
requirements.txt Normal file
View File

@ -0,0 +1,22 @@
beautifulsoup4==4.14.3
bs4==0.0.2
certifi==2026.1.4
charset-normalizer==3.4.4
contourpy==1.3.3
cycler==0.12.1
fonttools==4.61.1
idna==3.11
kiwisolver==1.4.9
matplotlib==3.10.8
numpy==2.4.1
packaging==25.0
pillow==12.1.0
psutil==7.2.1
pyparsing==3.3.1
python-dateutil==2.9.0.post0
python-vlc==3.0.21203
requests==2.32.5
six==1.17.0
soupsieve==2.8.2
typing_extensions==4.15.0
urllib3==2.6.3

View File

@ -128,3 +128,44 @@ Historia
Los primeros pobladores que llegaron a la región fueron los vástagos, los caribes y los arahuacos.[11] Los primeros pobladores que llegaron a la región fueron los vástagos, los caribes y los arahuacos.[11]
La conquista europea del territorio fue iniciada por el alemán Ambrosio Alfinger en 1530, quien llegó el valle de Upar haciendo frente a una considerable resistencia indígena.[12] En esa época, el territor La conquista europea del territorio fue iniciada por el alemán Ambrosio Alfinger en 1530, quien llegó el valle de Upar haciendo frente a una considerable resistencia indígena.[12] En esa época, el territor
[... Contenido truncado ...] [... Contenido truncado ...]
==========================================================
== 🌐 DATOS EXTRAÍDOS (Búsqueda: 'google') ==
==========================================================
URL Artículo: https://es.wikipedia.org/w/index.php?search=google&title=Especial:Buscar&go=Ir
TÍTULO: Google
FECHA/HORA: 2025-12-08 15:23:14
----------------------------------------------------------
CONTENIDO (Primeros 1529 chars):
----------------------------------------------------------
Google LLC (pronunciado /ˈɡuːɡəl/ (escucharⓘ), más conocida como Google) es una empresa de tecnología multinacional con sede en California, Estados Unidos, que se centra en inteligencia artificial, publicidad en línea, tecnología de motores de búsqueda, computación en la nube, software, computación cuántica, comercio electrónico y electrónica de consumo.[1] Es una de las marcas más valiosas del mundo debido a su dominio del mercado, recopilación de datos y ventajas tecnológicas en el campo de la inteligencia artificial. Es considerada una de las cinco grandes compañías tecnológicas junto con Apple, Amazon, Microsoft y Meta Platforms.[2][3][4]
Su producto principal es el motor de búsqueda de contenido en internet del mismo nombre, aunque ofrece también otros productos y servicios, como su servicio de almacenamiento en la nube Google Drive, el correo electrónico Gmail, sus servicios de mapas Google Maps, Google Street View y Google Earth, el sitio web de vídeos YouTube, entre otros.
Por otra parte, lidera el desarrollo del sistema operativo y servicios de operación de aplicaciones basado en Linux: Android, orientado a teléfonos inteligentes, tabletas, televisores, automóviles y gafas de realidad aumentada, las Google Glass. Su eslogan es «Do the Right Thing» («Haz lo correcto»).[5]
Con miles de servidores y centros de datos presentes en todo el mundo, Google es capaz de procesar más de 1000 millones de peticiones de búsqueda diarias y su motor de búsqueda es el sitio web m
[... Contenido truncado ...]
==========================================================
== 🌐 DATOS EXTRAÍDOS (Búsqueda: 'hola') ==
==========================================================
URL Artículo: https://es.wikipedia.org/w/index.php?search=hola&title=Especial:Buscar&go=Ir
TÍTULO: Hola
FECHA/HORA: 2026-01-19 19:05:02
----------------------------------------------------------
CONTENIDO (Primeros 600 chars):
----------------------------------------------------------
El término «hola» se puede referir a:
Hola, interjección en español usada como saludo con el objetivo de comenzar una conversación;
Hola, también llamado Galole, un pueblo de Kenia;
¡Hola!, una revista española;
Hola Airlines, una aerolínea española con base en Palma de Mallorca;
Hola mundo, un programa informático básico para iniciar el estudio de un lenguaje de programación;
«Hola», canción de Joey Montana.
Enlaces externos
Wikcionario tiene definiciones y otra información sobre hola.
Proyectos Wikimedia
Datos: Q1177609
Multimedia: Hola / Q1177609
Datos: Q1177609
Multimedia: Hola / Q1177609

View File

@ -0,0 +1 @@
hholsa

3
vista/chat/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from vista.chat.chat_servidor import ChatServidorPanel
from vista.chat.chat_cliente import ChatClientePanel
from vista.chat.chat_selector import ChatSelectorPanel

86
vista/chat/chat_base.py Normal file
View File

@ -0,0 +1,86 @@
import tkinter as tk
from tkinter import ttk
from datetime import datetime
from vista.config import *
class ChatBase(ttk.Frame):
"""Base comun para las vistas de chat servidor y cliente."""
def __init__(self, parent, root, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.root = root
self.chat_history = None
self.chat_input_entry = None
self.socket = None
def crear_interfaz_chat(self, parent_frame, titulo="Chat", boton_accion_texto=None, boton_accion_callback=None):
"""Crea la interfaz grafica del chat: titulo, historial, entrada y boton."""
self._chat_frame = ttk.Frame(parent_frame, padding=15, style='TFrame')
self._chat_frame.pack(expand=True, fill="both")
self._chat_frame.grid_rowconfigure(1, weight=1)
self._chat_frame.grid_columnconfigure(0, weight=1)
# Titulo y boton de accion
frame_titulo = ttk.Frame(self._chat_frame, style='TFrame')
frame_titulo.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 10))
frame_titulo.grid_columnconfigure(0, weight=1)
self.info_label = ttk.Label(frame_titulo, text=titulo, font=FUENTE_TITULO)
self.info_label.grid(row=0, column=0, sticky="w")
if boton_accion_texto and boton_accion_callback:
ttk.Button(
frame_titulo, text=boton_accion_texto,
command=boton_accion_callback
).grid(row=0, column=1, sticky="e", padx=(10, 0))
# Historial de mensajes
self.chat_history = tk.Text(
self._chat_frame, wrap="word",
bg=COLOR_BLANCO, fg=COLOR_TEXTO,
relief="flat", borderwidth=1,
font=('Arial', 10)
)
self.chat_history.grid(row=1, column=0, columnspan=2, sticky="nsew", pady=(0, 10))
self.chat_history.config(state=tk.DISABLED)
# Entrada y boton
frame_input = ttk.Frame(self._chat_frame, style='TFrame')
frame_input.grid(row=2, column=0, columnspan=2, sticky="ew")
frame_input.grid_columnconfigure(0, weight=1)
self.chat_input_entry = ttk.Entry(frame_input, font=('Arial', 11))
self.chat_input_entry.grid(row=0, column=0, sticky="ew", padx=(0, 5))
self.chat_input_entry.bind('<Return>', self.enviar_mensaje)
ttk.Button(
frame_input, text="Enviar",
command=self.enviar_mensaje, style='Action.TButton'
).grid(row=0, column=1, sticky="e")
def agregar_mensaje(self, remitente, texto):
"""Agrega un mensaje al historial."""
self.chat_history.config(state=tk.NORMAL)
timestamp = datetime.now().strftime('%H:%M:%S')
self.chat_history.insert(tk.END, f"[{timestamp}] {remitente.upper()}: {texto.strip()}\n")
self.chat_history.config(state=tk.DISABLED)
self.chat_history.see(tk.END)
def agregar_mensaje_sistema(self, texto):
"""Agrega un mensaje del sistema."""
self.agregar_mensaje("SISTEMA", f"--- {texto} ---")
def enviar_mensaje(self, event=None):
"""Debe ser implementado por las subclases."""
raise NotImplementedError
def cerrar_conexion(self):
"""Cierra el socket si esta activo."""
if self.socket:
try:
self.socket.close()
except OSError:
pass
self.socket = None

106
vista/chat/chat_cliente.py Normal file
View File

@ -0,0 +1,106 @@
import threading
from vista.chat.chat_base import ChatBase
from logica.red.cliente import conectar_servidor
PREFIJO_NOMBRE = "__NOMBRE__:"
class ChatClientePanel(ChatBase):
"""Vista de chat para el rol de cliente."""
def __init__(self, parent, root, ip, puerto, clave, *args, **kwargs):
super().__init__(parent, root, *args, **kwargs)
self.ip = ip
self.puerto = puerto
self.clave = clave
self.nombre = None
self.crear_interfaz_chat(
self, titulo="Chat - Cliente",
boton_accion_texto="Desconectar",
boton_accion_callback=self.desconectar_y_volver
)
self.conectar()
def conectar(self):
print(f"[DEBUG CLI-GUI] Iniciando conexion a {self.ip}:{self.puerto} con clave={self.clave!r}")
self.agregar_mensaje_sistema(f"Conectando a {self.ip}:{self.puerto}...")
def hilo_conexion():
cliente, extra = conectar_servidor(self.ip, self.puerto, self.clave)
if cliente:
self.socket = cliente
print(f"[DEBUG CLI-GUI] Conexion exitosa, extra={extra!r}")
self.root.after(0, self.agregar_mensaje_sistema, f"Conectado a {self.ip}:{self.puerto}")
self.recibir_mensajes(extra)
else:
print(f"[DEBUG CLI-GUI] Conexion fallida")
self.root.after(0, self.agregar_mensaje_sistema, "Error: no se pudo conectar o clave incorrecta")
hilo = threading.Thread(target=hilo_conexion, daemon=True)
hilo.start()
def _procesar_mensaje(self, mensaje):
"""Procesa un mensaje recibido (nombre, chat o remoto)."""
print(f"[DEBUG CLI-GUI] Procesando mensaje: {mensaje!r}")
if mensaje.startswith(PREFIJO_NOMBRE):
self.nombre = mensaje[len(PREFIJO_NOMBRE):]
print(f"[DEBUG CLI-GUI] Nombre asignado: {self.nombre!r}")
self.root.after(0, self.agregar_mensaje_sistema, f"Tu nombre: {self.nombre}")
self.root.after(0, self._actualizar_titulo)
elif ": " in mensaje:
remitente, texto = mensaje.split(": ", 1)
print(f"[DEBUG CLI-GUI] Mensaje de chat: {remitente!r} -> {texto!r}")
self.root.after(0, self.agregar_mensaje, remitente, texto)
else:
print(f"[DEBUG CLI-GUI] Mensaje remoto sin formato: {mensaje!r}")
self.root.after(0, self.agregar_mensaje, "REMOTO", mensaje)
def recibir_mensajes(self, extra=""):
# Procesar datos que llegaron junto con el "OK" de autenticacion
if extra:
print(f"[DEBUG CLI-GUI] Procesando datos extra del handshake: {extra!r}")
self._procesar_mensaje(extra)
print("[DEBUG CLI-GUI] Bucle de recepcion iniciado")
try:
while self.socket:
datos = self.socket.recv(4096)
if not datos:
print("[DEBUG CLI-GUI] Datos vacios recibidos (servidor cerro)")
self.root.after(0, self.agregar_mensaje_sistema, "El servidor cerro la conexion")
break
mensaje = datos.decode("utf-8")
print(f"[DEBUG CLI-GUI] Datos crudos recibidos: {datos!r}")
self._procesar_mensaje(mensaje)
except (ConnectionResetError, OSError) as e:
print(f"[DEBUG CLI-GUI] Error en recepcion: {e}")
self.root.after(0, self.agregar_mensaje_sistema, "Conexion perdida con el servidor")
def _actualizar_titulo(self):
if self.nombre:
self.info_label.config(text=f"Chat - {self.nombre}")
def enviar_mensaje(self, event=None):
mensaje = self.chat_input_entry.get().strip()
if not mensaje or not self.socket:
return "break" if event else None
self.chat_input_entry.delete(0, "end")
self.agregar_mensaje("TU", mensaje)
try:
print(f"[DEBUG CLI-GUI] Enviando mensaje: {mensaje!r}")
self.socket.sendall(mensaje.encode("utf-8"))
except OSError as e:
print(f"[DEBUG CLI-GUI] Error al enviar: {e}")
self.agregar_mensaje_sistema("Error al enviar mensaje")
return "break" if event else None
def desconectar_y_volver(self):
"""Desconecta del servidor y vuelve al selector."""
self.cerrar_conexion()
parent = self.master
self.destroy()
if hasattr(parent, 'volver_al_selector'):
parent.volver_al_selector()

193
vista/chat/chat_selector.py Normal file
View File

@ -0,0 +1,193 @@
import tkinter as tk
from tkinter import ttk
import threading
from vista.config import *
from logica.red.cliente import descubrir_servidores
from logica.red.selector import obtener_ip_local
from vista.chat.chat_servidor import ChatServidorPanel
from vista.chat.chat_cliente import ChatClientePanel
class ChatSelectorPanel(ttk.Frame):
"""Panel inicial que permite elegir entre servidor o cliente."""
def __init__(self, parent_notebook, root, *args, **kwargs):
super().__init__(parent_notebook, *args, **kwargs)
self.root = root
self.parent_notebook = parent_notebook
self.panel_activo = None
self._busqueda_cancelada = False
self.crear_selector()
def crear_selector(self):
"""Crea la pantalla de seleccion de rol."""
self.selector_frame = ttk.Frame(self, padding=30, style='TFrame')
self.selector_frame.pack(expand=True, fill="both")
# Titulo
ttk.Label(
self.selector_frame, text="Chat en Red",
font=FUENTE_TITULO
).pack(pady=(20, 5))
ip = obtener_ip_local()
ttk.Label(
self.selector_frame, text=f"IP local: {ip}",
font=FUENTE_NOTA
).pack(pady=(0, 20))
# Botones de rol
frame_botones = ttk.Frame(self.selector_frame, style='TFrame')
frame_botones.pack(pady=10)
ttk.Button(
frame_botones, text="Crear Servidor",
command=self.iniciar_servidor, style='Action.TButton'
).pack(side="left", padx=10)
ttk.Button(
frame_botones, text="Conectar como Cliente",
command=self.iniciar_busqueda_cliente, style='Action.TButton'
).pack(side="left", padx=10)
# Frame para resultado de busqueda (cliente)
self.frame_cliente = ttk.Frame(self.selector_frame, style='TFrame')
self.estado_label = None
self.servidores_encontrados = []
def iniciar_servidor(self):
"""Reemplaza el selector por el panel de chat servidor."""
self.selector_frame.destroy()
self.panel_activo = ChatServidorPanel(self, self.root)
self.panel_activo.pack(expand=True, fill="both")
def iniciar_busqueda_cliente(self):
"""Muestra progreso y busca servidores."""
self._busqueda_cancelada = False
# Limpiar frame cliente previo
for widget in self.frame_cliente.winfo_children():
widget.destroy()
self.frame_cliente.pack(pady=20, fill="x")
self.estado_label = ttk.Label(
self.frame_cliente, text="Buscando servidores...",
font=FUENTE_NEGOCIOS
)
self.estado_label.pack()
self.progress = ttk.Progressbar(self.frame_cliente, mode='indeterminate')
self.progress.pack(fill="x", padx=40, pady=5)
self.progress.start(15)
ttk.Button(
self.frame_cliente, text="Cancelar busqueda",
command=self.cancelar_busqueda
).pack(pady=5)
hilo = threading.Thread(target=self.buscar_servidores, daemon=True)
hilo.start()
def cancelar_busqueda(self):
"""Cancela la busqueda de servidores."""
self._busqueda_cancelada = True
self.progress.stop()
for widget in self.frame_cliente.winfo_children():
widget.destroy()
self.frame_cliente.pack_forget()
def buscar_servidores(self):
print("[DEBUG SEL] Iniciando busqueda de servidores...")
servidores = descubrir_servidores(timeout=5)
print(f"[DEBUG SEL] Busqueda completada: {servidores}")
if not self._busqueda_cancelada:
self.root.after(0, self.mostrar_resultado_busqueda, servidores)
def mostrar_resultado_busqueda(self, servidores):
self.progress.stop()
for widget in self.frame_cliente.winfo_children():
widget.destroy()
if not servidores:
ttk.Label(
self.frame_cliente, text="No se encontraron servidores.",
font=FUENTE_NEGOCIOS, foreground="red"
).pack()
frame_opciones = ttk.Frame(self.frame_cliente, style='TFrame')
frame_opciones.pack(pady=10)
ttk.Button(
frame_opciones, text="Reintentar",
command=self.iniciar_busqueda_cliente, style='Action.TButton'
).pack(side="left", padx=5)
ttk.Button(
frame_opciones, text="Volver",
command=self.volver_selector
).pack(side="left", padx=5)
return
self.servidores_encontrados = servidores
ttk.Label(
self.frame_cliente,
text=f"{len(servidores)} servidor(es) encontrado(s):",
font=FUENTE_NEGOCIOS
).pack()
for i, (ip, puerto) in enumerate(servidores):
frame_srv = ttk.Frame(self.frame_cliente, style='TFrame')
frame_srv.pack(pady=2)
ttk.Label(frame_srv, text=f"{ip}", font=FUENTE_NORMAL).pack(side="left", padx=5)
ttk.Button(
frame_srv, text="Conectar",
command=lambda idx=i: self.pedir_clave(idx), style='Action.TButton'
).pack(side="left", padx=5)
def pedir_clave(self, indice):
"""Muestra un dialogo para introducir la contrasena."""
ip, puerto = self.servidores_encontrados[indice]
dialogo = tk.Toplevel(self.root)
dialogo.title("Clave de acceso")
dialogo.geometry("350x120")
dialogo.resizable(False, False)
dialogo.transient(self.root)
dialogo.grab_set()
frame = ttk.Frame(dialogo, padding=15)
frame.pack(expand=True, fill="both")
ttk.Label(frame, text=f"Conectar a {ip}", font=FUENTE_NEGOCIOS).pack()
ttk.Label(frame, text="Clave (puerto#contraseña):", font=FUENTE_NORMAL).pack(pady=(5, 0))
entrada = ttk.Entry(frame, font=FUENTE_NORMAL)
entrada.pack(fill="x", pady=5)
entrada.focus_set()
def conectar(event=None):
contrasena = entrada.get().strip()
if contrasena:
dialogo.destroy()
self.conectar_a_servidor(ip, puerto, contrasena)
entrada.bind('<Return>', conectar)
ttk.Button(frame, text="Conectar", command=conectar, style='Action.TButton').pack()
def conectar_a_servidor(self, ip, puerto, contrasena):
"""Reemplaza el selector por el panel de chat cliente."""
print(f"[DEBUG SEL] Conectando a servidor: ip={ip} puerto={puerto} clave={contrasena!r}")
self.selector_frame.destroy()
self.panel_activo = ChatClientePanel(self, self.root, ip, puerto, contrasena)
self.panel_activo.pack(expand=True, fill="both")
def volver_al_selector(self):
"""Destruye el panel activo y recrea el selector."""
if self.panel_activo:
self.panel_activo = None
self.crear_selector()
def volver_selector(self):
"""Limpia el frame de cliente para volver al estado inicial."""
for widget in self.frame_cliente.winfo_children():
widget.destroy()
self.frame_cliente.pack_forget()

132
vista/chat/chat_servidor.py Normal file
View File

@ -0,0 +1,132 @@
import threading
from vista.chat.chat_base import ChatBase
from logica.red.servidor import iniciar_servidor, autenticar_cliente
from logica.red.selector import obtener_ip_local
PREFIJO_NOMBRE = "__NOMBRE__:"
class ChatServidorPanel(ChatBase):
"""Vista de chat para el rol de servidor."""
def __init__(self, parent, root, *args, **kwargs):
super().__init__(parent, root, *args, **kwargs)
self.servidor = None
self.clientes = []
self.clave_acceso = None
self.broadcast_stop = None
self.contador_clientes = 0
self.crear_interfaz_chat(
self, titulo="Chat - Servidor",
boton_accion_texto="Cerrar Servidor",
boton_accion_callback=self.cerrar_y_volver
)
self.iniciar()
def iniciar(self):
ip = obtener_ip_local()
self.servidor, puerto, contrasena_alfa, self.broadcast_stop = iniciar_servidor()
self.clave_acceso = f"{puerto}#{contrasena_alfa}"
print(f"[DEBUG SRV-GUI] Servidor iniciado: ip={ip} puerto={puerto} clave={self.clave_acceso}")
self.agregar_mensaje_sistema(f"Servidor iniciado en {ip}")
self.agregar_mensaje_sistema(f"Clave de acceso: {self.clave_acceso}")
self.agregar_mensaje_sistema("Esperando clientes...")
hilo = threading.Thread(target=self.aceptar_clientes, daemon=True)
hilo.start()
def aceptar_clientes(self):
print("[DEBUG SRV-GUI] Esperando conexiones de clientes...")
while self.servidor:
try:
conn, addr = self.servidor.accept()
print(f"[DEBUG SRV-GUI] Conexion entrante de {addr[0]}:{addr[1]}")
datos = conn.recv(1024).decode("utf-8")
print(f"[DEBUG SRV-GUI] Datos de autenticacion recibidos: {datos!r}")
if autenticar_cliente(datos, self.clave_acceso):
conn.sendall("OK".encode("utf-8"))
self.contador_clientes += 1
nombre = f"Cliente {self.contador_clientes}"
conn.sendall(f"{PREFIJO_NOMBRE}{nombre}".encode("utf-8"))
print(f"[DEBUG SRV-GUI] Cliente autenticado como {nombre!r}, enviado OK + nombre")
self.clientes.append(conn)
self.root.after(0, self.agregar_mensaje_sistema, f"{nombre} conectado")
hilo = threading.Thread(target=self.recibir_de_cliente, args=(conn, nombre), daemon=True)
hilo.start()
else:
conn.sendall("DENIED".encode("utf-8"))
conn.close()
etiqueta = f"{addr[0]}:{addr[1]}"
print(f"[DEBUG SRV-GUI] Cliente {etiqueta} rechazado (clave incorrecta)")
self.root.after(0, self.agregar_mensaje_sistema, f"Cliente {etiqueta} rechazado")
except OSError as e:
print(f"[DEBUG SRV-GUI] Error en accept: {e}")
break
def recibir_de_cliente(self, conn, nombre):
print(f"[DEBUG SRV-GUI] Hilo de recepcion iniciado para {nombre}")
try:
while True:
datos = conn.recv(4096)
if not datos:
print(f"[DEBUG SRV-GUI] {nombre} envio datos vacios (desconexion)")
break
mensaje = datos.decode("utf-8")
print(f"[DEBUG SRV-GUI] Mensaje de {nombre}: {mensaje!r}")
self.root.after(0, self.agregar_mensaje, nombre, mensaje)
self.difundir(mensaje, nombre, conn)
except (ConnectionResetError, OSError) as e:
print(f"[DEBUG SRV-GUI] Error recibiendo de {nombre}: {e}")
finally:
if conn in self.clientes:
self.clientes.remove(conn)
conn.close()
self.root.after(0, self.agregar_mensaje_sistema, f"{nombre} desconectado")
def difundir(self, mensaje, remitente, origen):
"""Envia un mensaje a todos los clientes excepto al que lo envio."""
datos = f"{remitente}: {mensaje}".encode("utf-8")
for cliente in self.clientes[:]:
if cliente is not origen:
try:
cliente.sendall(datos)
except OSError:
self.clientes.remove(cliente)
def enviar_mensaje(self, event=None):
mensaje = self.chat_input_entry.get().strip()
if not mensaje:
return "break" if event else None
self.chat_input_entry.delete(0, "end")
self.agregar_mensaje("SERVIDOR", mensaje)
datos = f"SERVIDOR: {mensaje}".encode("utf-8")
for cliente in self.clientes[:]:
try:
cliente.sendall(datos)
except OSError:
self.clientes.remove(cliente)
return "break" if event else None
def cerrar_y_volver(self):
"""Cierra el servidor y vuelve al selector."""
self.cerrar_conexion()
parent = self.master
self.destroy()
if hasattr(parent, 'volver_al_selector'):
parent.volver_al_selector()
def cerrar_conexion(self):
if self.broadcast_stop:
self.broadcast_stop.set()
if self.servidor:
self.servidor.close()
self.servidor = None
for cliente in self.clientes:
try:
cliente.close()
except OSError:
pass
self.clientes.clear()