Memoria técnica · POO

# Proyecto Final — Programación Orientada a Objetos

Documentación Técnica Completa
1º Ingeniería Informática · Curso 2024-2025
Integrantes: JP Aceves · Adrián Duque · Juan Carlos Alcazarde · Ignacio del Peso

# PARTE 1 — DOCUMENTACIÓN ORGANIZATIVA DEL PROYECTO

## 1.1Descripción general del proyecto

MiniJuegos es una aplicación de escritorio desarrollada en Java con interfaz gráfica Swing. Permite a varios usuarios registrados jugar partidas de minijuegos de forma individual o multijugador, pausar y reanudar partidas, y consultar un historial de estadísticas. Incluye además un panel de administración para usuarios con rol de administrador.

### Stack tecnológico

- Lenguaje: Java SE (JDK 17+)
- Interfaz gráfica: Swing (javax.swing, java.awt)
- Persistencia: Ficheros de texto plano (.txt, .dat) en la carpeta data/
- Librerías externas: Ninguna — requisito explícito del enunciado
- Seguridad: SHA-256 (java.security.MessageDigest)

### Juegos implementados

- Pasapalabra: rosco de 27 letras con 4 niveles de dificultad (Infantil, Fácil, Medio, Avanzado). El jugador dispone de 2 minutos 30 segundos para responder todas las definiciones.
- Tres en Raya: tablero 3×3 para dos jugadores con detección automática de victoria y empate.

## 1.2Requisitos del enunciado

- Aplicación de minijuegos con mínimo dos juegos distintos.
- Gestión de usuarios con registro, login y roles (usuario estándar / administrador).
- Persistencia en ficheros de texto sin librerías externas de base de datos.
- Arquitectura orientada a objetos: uso de herencia, polimorfismo, clases abstractas e interfaces.
- Interfaz gráfica con Swing.
- Historial de estadísticas por usuario y ranking global por juego.
- Posibilidad de pausar y reanudar partidas.
- Panel de administración accesible solo para usuarios admin.

## 1.3Arquitectura en capas

El sistema está organizado en cuatro capas bien diferenciadas ubicadas en paquetes Java independientes:

### Vista (paquete Vista)

Contiene todas las ventanas y paneles Swing. Cada ventana es responsable de recoger la interacción del usuario y delegar la lógica en los gestores. No contiene reglas de negocio. Las clases son: Aplicacion, Tema, VentanaLogin, VentanaMenuPrincipal, VentanaSeleccionJuego, VentanaJuego (abstracta), VentanaJuegoPasapalabra, VentanaJuegoTresEnRaya, VentanaEstadisticas y VentanaAdmin.

### Controladores (paquete Controlador)

Coordinan Vista con Modelo. Cada gestor cubre un dominio específico: GestorUsuarios (autenticación, registro, sesión), GestorJuegos (registro y creación de juegos), GestorPartidas (ciclo de vida de partidas: iniciar, pausar, reanudar, finalizar), GestorEstadisticas (registro y consulta de resultados).

### Modelo (paquete Modelo)

Clases de dominio puras sin dependencias de UI ni de persistencia: Usuario (abstracta), Jugador, Administrador, Juego (abstracta), PasaPalabra, TresEnRaya, Partida, Estadistica, PuntuacionJugador y el enum EstadoPartida.

### Persistencia (paquete Persistencia)

La interfaz GestorPersistencia define el contrato de acceso a datos (usuarios, estadísticas, partidas pausadas). La clase PersistenciaArchivos implementa ese contrato usando ficheros de texto en la carpeta data/. Esta separación permite cambiar el mecanismo de almacenamiento (p. ej. base de datos) sin modificar el resto del sistema.

## 1.4Decisiones de diseño tomadas y por qué

#### Juego es clase abstracta (no interfaz)

Todos los juegos comparten estado común (nombre, descripción, juegoFinalizado, listaPuntuacionPorJugador) y comportamiento concreto (sumarPuntos, getPuntuacion). Una interfaz no puede llevar estado. Los métodos abstractos (inicializar, getEstadoTexto, serializarEstado, deserializarEstado, terminar) obligan a cada subclase a implementar su mecánica propia.

#### Usuario abstracta con subclases Jugador y Administrador (en lugar de boolean esAdmin)

La decisión inicial era un campo esAdmin:boolean dentro de Usuario. En la implementación final se optó por polimorfismo real (instanceof Administrador) porque simplifica los condicionales en GestorUsuarios y VentanaAdmin, y facilita extender el sistema con más roles en el futuro sin modificar código existente.

#### GestorPersistencia es una interfaz

Permite cambiar de ficheros de texto a cualquier otra fuente de datos (base de datos relacional, XML, red) sin tocar ni los controladores ni la vista. Solo hay que proporcionar una nueva implementación de la interfaz.

#### Contraseñas almacenadas como hash SHA-256

Las contraseñas nunca se guardan en texto plano. La clase Usuario aplica SHA-256 internamente. El constructor alternativo (yaEsHash=true) permite reconstruir objetos desde disco sin volver a hashear.

#### PasaPalabra carga una pregunta aleatoria por letra de cada bloque de 10

Cada nivel tiene ~10 preguntas por letra. cargarDatos() elige una al azar en cada ejecución, lo que aumenta la rejugabilidad sin aumentar la complejidad del modelo.

#### Estado de partida pausada serializado en formato texto inline

El estado se serializa como una cadena única (ej. "TresEnRaya|user1,user2|casilla00,...") guardada en un fichero .dat por partida. Esto evita dependencias de serialización Java y hace el formato legible y fácil de depurar.

#### Gestores expuestos como campos estáticos desde Aplicacion

En lugar de inyectar los gestores en cada constructor de ventana (lo que sería más puro), se accede a ellos vía Aplicacion.getGestorX(). Simplifica el código de las ventanas y evita cadenas largas de parámetros en constructores, a costa de un acoplamiento mayor con la clase Aplicacion.

#### VentanaJuegoPasapalabra NO extiende VentanaJuego

Pasapalabra necesita lógica propia de temporizador y resumen final que difiere del ciclo estándar de accionFinalizar(). Se decidió que extender VentanaJuego añadiría más complejidad que valor. VentanaJuegoPasapalabra extiende directamente JFrame.

#### Atributos siempre private; protected solo donde la subclase lo necesita

Juego declara algunos atributos como protected (nombreJuego, descripcion, juegoFinalizado, listaPuntuacionPorJugador) porque TresEnRaya y PasaPalabra acceden directamente a ellos. El resto de clases usa private con getters/setters.

#### Clase Tema con constantes de diseño visual

Centraliza toda la paleta de colores y tipografía. Así, cambiar el aspecto visual de la aplicación requiere modificar un único fichero en lugar de buscar valores hardcodeados por todas las ventanas.

## 1.5Reparto de trabajo por integrante

### JP Aceves

Coordinación general, arquitectura, modelo de partidas y persistencia.

Clase / Componente | Responsabilidad
--- | ---
enum EstadoPartida | Define los tres estados posibles de una partida
PuntuacionJugador | Par username/puntos; usado internamente por Juego para las puntuaciones de la partida
interface GestorPersistencia | Contrato para toda operación de lectura/escritura en disco
PersistenciaArchivos | Implementación de GestorPersistencia sobre ficheros de texto
Juego (abstracta) | Clase base de todos los juegos; define estado común y métodos abstractos
Partida | Representa una sesión concreta de juego; coordina turno, estado y resultados
GestorPartidas | Ciclo de vida completo de las partidas (iniciar/pausar/reanudar/finalizar)
VentanaJuego (abstracta) | Base de las ventanas de juego con accionPausar y accionFinalizar
Aplicacion | Punto de entrada main; instancia y expone todos los gestores
Tema | Paleta de colores y fuentes compartida por toda la UI
VentanaSeleccionJuego | Diálogo modal para que el usuario elija el juego a jugar
GestorPartidas (colaboración) | Codesarrollo con Adrián Duque para el soporte de partidas pausadas

### Adrián Duque

Modelo de usuarios, juego Pasapalabra y vistas de login y menú.

Clase / Componente | Responsabilidad
--- | ---
Usuario (abstracta) | Clase base de usuarios; hash SHA-256, verificación de contraseña
Jugador | Subclase de Usuario sin privilegios especiales
Administrador | Subclase de Usuario con rol de administrador
PasaPalabra | Implementación completa del juego: carga de roscos, lógica de turno, serialización
GestorUsuarios | Autenticación, registro, validaciones de formato y gestión de sesión
VentanaLogin | Pantalla de inicio de sesión / registro
VentanaMenuPrincipal | Menú principal tras login con acceso a todas las funcionalidades
VentanaJuegoPasapalabra | Vista completa del juego Pasapalabra con rosco visual y temporizador
PersistenciaArchivos (colaboración) | Codesarrollo con JP
GestorPartidas (colaboración) | Codesarrollo con JP

### Juan Carlos Alcazarde

Estadísticas y panel de administración.

Clase / Componente | Responsabilidad
--- | ---
Estadistica | Registro de resultado de una partida: usuario, juego, puntuación, victoria, fecha
GestorEstadisticas | Registro, consulta, ranking y últimas partidas de un usuario
VentanaEstadisticas | Historial de resultados del usuario actual en tabla interactiva
VentanaAdmin | Panel de administración con ranking por juego y lista de usuarios

### Ignacio del Peso

Juego Tres en Raya y su vista.

Clase / Componente | Responsabilidad
--- | ---
TresEnRaya | Tablero 3×3, lógica de turnos, detección de victoria/empate, serialización de estado
GestorJuegos | Registro de juegos disponibles y fábrica para crear instancias
VentanaJuegoTresEnRaya | Vista del tablero 3×3 con botones clicables, turno actual y puntuaciones

## 1.6Guía de métodos por clase

A continuación se listan todos los métodos de cada clase con una descripción de su función.

### MODELO

Usuario (abstracta)

Método | Descripción
--- | ---
Usuario(username, password) | Constructor para usuarios nuevos. Hashea la contraseña con SHA-256.
Usuario(username, passwordHash, yaEsHash) | Constructor para reconstruir desde fichero. No vuelve a hashear si yaEsHash=true.
hashear(password) [private static] | Aplica SHA-256 y convierte el resultado a cadena hexadecimal de 64 caracteres.
getUsername() | Devuelve el identificador del usuario.
verificarPassword(introPassword) | Hashea la contraseña introducida y la compara con el hash almacenado.
toArchivo() [abstracto] | Serializa el usuario al formato CSV usado en usuarios.txt.
toString() [abstracto] | Representación legible del usuario.

Jugador extends Usuario

Método | Descripción
--- | ---
Jugador(username, password) | Constructor para nuevo jugador.
Jugador(username, passwordHash, yaEsHash) | Constructor para reconstruir desde fichero.
toString() | Devuelve "Jugador{username='X'}"
toArchivo() | Devuelve "username;passwordHash;false"

Administrador extends Usuario

Método | Descripción
--- | ---
Administrador(username, password) | Constructor para nuevo administrador.
Administrador(username, passwordHash, yaEsHash) | Constructor para reconstruir desde fichero.
toString() | Devuelve "Administrador{username='X'}"
toArchivo() | Devuelve "username;passwordHash;true"

Juego (abstracta)

Método | Descripción
--- | ---
Juego(nombreJuego, descripcion, juegoFinalizado) | Constructor base. juegoFinalizado siempre se inicializa a false.
inicializar() [abstracto] | Prepara el estado inicial del juego.
getEstadoTexto() [abstracto] | Devuelve el estado actual como texto legible.
serializarEstado() [abstracto] | Convierte el estado a String para guardar en disco al pausar.
deserializarEstado(estado) [abstracto] | Reconstruye el estado desde el String deserializado.
terminar() [abstracto] | Marca el juego como finalizado.
sumarPuntos(username, n) | Suma n puntos al jugador dado; crea entrada si no existía.
getPuntuacion(username) | Devuelve la puntuación actual del jugador; 0 si no existe.
getPuntuaciones() | Devuelve la lista completa de PuntuacionJugador.
isTerminado() | Devuelve si el juego ha finalizado.
getNombre() | Devuelve el nombre identificador del juego.
getDescripcion() | Devuelve la descripción breve del juego.

TresEnRaya extends Juego

Método | Descripción
--- | ---
TresEnRaya() | Constructor: nombre "TresEnRaya", tablero vacío, ganador=null.
inicializar() | Limpia el tablero 3×3, resetea ganador y juegoFinalizado.
jugarTurno(username, ficha, fila, columna) | Coloca la ficha, comprueba victoria/empate. Devuelve false si celda ocupada o juego terminado.
casillaOcupada(fila, columna) [private] | Verifica si la celda ya tiene ficha.
hayVictoria(ficha) [private] | Comprueba filas, columnas y diagonales para detectar victoria.
tableroLleno() [private] | Devuelve true si no quedan celdas vacías (empate).
getGanador() | Devuelve el username del ganador, o null si no hay.
getTablero() | Devuelve una copia defensiva del tablero 3×3.
getEstadoTexto() | Tablero ASCII con separadores + indicación de ganador/empate si terminó.
serializarEstado() | "casilla00,casilla01,...,casilla22;ganador"
deserializarEstado(s) | Reconstruye tablero y ganador desde el formato serializado.
terminar() | Establece juegoFinalizado=true.

PasaPalabra extends Juego

Método | Descripción
--- | ---
PasaPalabra(nivel) | Constructor con nivel específico (0=Infantil, 1=Fácil, 2=Medio, 3=Avanzado).
PasaPalabra() | Constructor por defecto con nivel 1 (Fácil).
inicializar() | Carga el rosco desde fichero, resetea contadores, avanza a primera letra pendiente.
procesarRespuesta(respuesta) | Compara normalizada. Si correcta: +10 pts, avanza. Si no: marca incorrecta, avanza.
pasarPalabra() | Marca la letra como PASAPALABRA y avanza a la siguiente pendiente.
avanzarAProximaPendiente() [private] | Búsqueda circular de la siguiente letra PENDIENTE o PASAPALABRA.
comprobarFinDeJuego() [private] | Llama a terminar() si no quedan letras pendientes.
obtenerUsernameJugador() [private] | Devuelve el username del primer jugador de la lista de puntuaciones.
limpiarTexto(texto) [public static] | Normaliza NFD, elimina diacríticos, convierte a minúsculas y elimina espacios.
cargarDatos(nivel) [private static] | Lee el fichero de rosco del nivel indicado; selecciona 1 pregunta aleatoria por cada bloque de 10.
getAciertos() | Getter del contador de respuestas correctas.
getFallos() | Getter del contador de respuestas incorrectas.
getPasaPalabras() | Getter del contador de pasapalabras.
getLetraActual() | Getter del índice de la letra en turno.
getNivel() | Getter del nivel actual.
getDatosLetra(indice) | Devuelve copia de [letra, definición, respuesta, estado] para el índice dado.
contarPendientes() | Cuenta letras en estado PENDIENTE o PASAPALABRA.
getTotalLetras() | Devuelve 27 (total de letras del abecedario español).
getEstadoTexto() | "Letra X - definición" de la letra actual.
serializarEstado() | "letraActual,aciertos,fallos,pasapalabras,estado0,...,estado26"
deserializarEstado(estado) | Restaura letraActual, contadores y el estado de cada una de las 27 letras.
terminar() | Establece juegoFinalizado=true.

Partida

Método | Descripción
--- | ---
Partida(id, juego, listaJugadores) | Constructor: estado EN_CURSO, turnoActual=0, fecha=hoy, fechaFin=null.
pausar() | Cambia el estado a PAUSADA.
reanudar(estadoSerializado) | Restaura el estado del juego y pone la partida EN_CURSO.
finalizar() | Cambia el estado a FINALIZADA y guarda fechaFin=hoy.
getJugadorActual() | Devuelve el Usuario al que le toca jugar.
siguienteTurno() | Avanza al siguiente jugador de forma circular.
contieneJugador(u) | Comprueba si el usuario es participante de esta partida.
getPuntuacion(u) | Delega en juego.getPuntuacion() para el usuario dado.
getPuntuaciones() | Delega en juego.getPuntuaciones(). Devuelve lista de PuntuacionJugador.
getGanador() | Devuelve el jugador con mayor puntuación; null si la lista está vacía.
getId() | Getter del identificador.
getFecha() | Getter de la fecha de inicio.
getFechaFin() | Getter de la fecha de fin; null si no ha terminado.
getEstadoActual() | Getter del EstadoPartida (EN_CURSO / PAUSADA / FINALIZADA).
getJuego() | Getter del objeto Juego asociado.
getListaJugadores() | Getter de los jugadores participantes.
toString() | "Partida{id=X, juego=Y, fecha=Z, estado=W}"

PuntuacionJugador

Método | Descripción
--- | ---
PuntuacionJugador(username) | Constructor: puntos=0.
getUsername() | Devuelve el nombre de usuario.
getPuntos() | Devuelve los puntos acumulados.
sumarPuntos(n) | Suma n puntos al contador.

Estadistica

Método | Descripción
--- | ---
Estadistica(username, nombreJuego, puntuacion, victoria, fecha) | Constructor completo.
getUsername() | Devuelve el nombre de usuario.
getNombreJuego() | Devuelve el nombre del juego.
getPuntuacion() | Devuelve la puntuación.
isVictoria() | Devuelve true si el jugador ganó la partida.
getFecha() | Devuelve la fecha de la partida.
toArchivo() | "username;nombreJuego;puntuacion;victoria;fecha_ISO"
toString() | Representación legible con etiquetas VICTORIA/DERROTA.

### CONTROLADORES

GestorUsuarios

Método | Descripción
--- | ---
GestorUsuarios(persistencia) | Constructor: inyecta persistencia y carga usuarios de disco.
iniciarSesion(username, contrasena) | Busca usuario, verifica password, guarda en usuarioActual. Devuelve null si falla.
registrarUsuario(username, contrasena) | Valida formato, unicidad, crea Jugador y persiste. Devuelve null si OK o mensaje de error.
validarUsername(username) [private] | No vacío, ≥3 chars, solo [a-zA-Z0-9-]+. Devuelve null si válido.
validarPassword(password) [private] | No vacía, ≥8 chars, sin punto y coma. Devuelve null si válida.
cerrarSesion() | Pone usuarioActual a null.
getUsuarioActual() | Devuelve el usuario con sesión activa.
buscarUsuario(username) | Búsqueda lineal en la lista; devuelve null si no encontrado.
esAdministrador() | Devuelve true si el usuarioActual es instancia de Administrador.
getListaUsuarios() | Devuelve la lista completa de usuarios.

GestorJuegos

Método | Descripción
--- | ---
GestorJuegos() | Constructor: lista vacía de juegos disponibles.
registrarJuego(nombre) | Añade el nombre a la lista si no estaba ya registrado.
crearJuego(nombre) | Fábrica: devuelve new PasaPalabra() o new TresEnRaya() según el nombre; null si no reconocido.
esMultijugador(nombre) | Devuelve true solo para "TresEnRaya".
getMaxJugadores(nombre) | Devuelve 2 para "TresEnRaya", 1 para el resto.
getJuegosDisponibles() | Devuelve una copia defensiva de la lista de juegos disponibles.

GestorPartidas

Método | Descripción
--- | ---
GestorPartidas(persistencia) | Constructor: inyecta persistencia, llama inicializarContador().
inicializarContador() [private] | Lee ids de partidas pausadas en disco y fija contadorId al máximo.
iniciarPartida(juego, jugadores) | Llama juego.inicializar(), crea Partida con id autoincremental, la guarda en partidaActual.
pausarPartida() | Llama partidaActual.pausar(), serializa con formato "nombreJuego|users|estado", llama persistencia.guardarPartidaPausada().
reanudarPartida(id, datos, juego, jugadores) | Parsea los datos, llama juego.inicializar() + partida.reanudar(estado). Devuelve la Partida activa.
finalizarPartida() | Llama partida.finalizar(), añade al historial, elimina el .dat, deja partidaActual=null.
listarPartidasPausadas() | Delega en persistencia.listarPartidasPausadas(). Devuelve lista de ids.
listarPartidasUsuario(username) | Filtra las partidas pausadas donde el username aparece en el campo de jugadores.
cargarDatosPartida(id) | Delega en persistencia.cargarPartidaPausada(id).
getPartidaActual() | Devuelve la partida en curso actual.
getPartidasUsuario(usuario) | Filtra el historial en memoria por contieneJugador().

GestorEstadisticas

Método | Descripción
--- | ---
GestorEstadisticas(persistencia) | Constructor: inyecta persistencia, carga estadísticas de disco.
registrarResultado(partida) | Crea una Estadistica por cada PuntuacionJugador; determina victoria comparando con partida.getGanador().
getEstadisticasUsuario(u) | Filtra la lista en memoria por username.
getUltimasPartidas(u, n) | Ordena por fecha descendente (bubble sort) y devuelve las n primeras.
calcularRanking(nombreJuego) | Filtra por juego, ordena por puntuación descendente (bubble sort).
contarPartidas(username) | Total de estadísticas del usuario.
contarVictorias(username) | Estadísticas del usuario con isVictoria()==true.

### PERSISTENCIA

GestorPersistencia (interfaz)

Método | Descripción
--- | ---
guardarUsuarios(listaUsuarios) | Persiste la lista completa de usuarios.
cargarUsuarios() | Carga y devuelve todos los usuarios almacenados.
agregarEstadistica(e) | Añade (append) una nueva estadística.
cargarEstadisticas() | Carga y devuelve todas las estadísticas.
guardarPartidaPausada(id, estado) | Persiste el estado serializado de una partida.
cargarPartidaPausada(id) | Carga el estado de la partida con ese id.
eliminarPartidaPausada(id) | Elimina el fichero/entrada de la partida pausada.
listarPartidasPausadas() | Devuelve la lista de ids de partidas pausadas disponibles.

PersistenciaArchivos implements GestorPersistencia

Método | Descripción
--- | ---
PersistenciaArchivos() | Constructor: crea directorios data/ y data/partidas/ con mkdirs() si no existen.
guardarUsuarios(listaUsuarios) | Sobrescribe data/usuarios.txt línea a línea usando toArchivo().
cargarUsuarios() | Lee data/usuarios.txt; parsea "username;hash;esAdmin"; instancia Jugador o Administrador.
agregarEstadistica(e) | Abre data/estadisticas.txt en modo append y escribe e.toArchivo().
cargarEstadisticas() | Lee data/estadisticas.txt; parsea los 5 campos; reconstruye LocalDate con parse().
guardarPartidaPausada(id, estado) | Escribe estado en data/partidas/id.dat.
cargarPartidaPausada(id) | Lee la primera línea de data/partidas/id.dat; devuelve null si no existe.
eliminarPartidaPausada(id) | Elimina data/partidas/id.dat si existe.
listarPartidasPausadas() | Recorre data/partidas/, filtra .dat, extrae el id del nombre de fichero.

### VISTA

Aplicacion

Método | Descripción
--- | ---
main(args) | Punto de entrada: crea PersistenciaArchivos, instancia los 4 gestores, registra juegos, lanza VentanaLogin.
getGestorUsuarios() | Devuelve la instancia estática de GestorUsuarios.
getGestorJuegos() | Devuelve la instancia estática de GestorJuegos.
getGestorPartidas() | Devuelve la instancia estática de GestorPartidas.
getGestorEstadisticas() | Devuelve la instancia estática de GestorEstadisticas.

Tema (final)

No tiene métodos. Declara únicamente constantes public static final. Colores: FONDO, FONDO_PANEL, FONDO_OSCURO, FONDO_CAMPO, FONDO_INPUT, ACENTO, TEXTO, SUBTEXTO, ERROR, BORDE, BORDE_INPUT, CORRECTO, INCORRECTO, PASAPALABRA, ACTUAL, PENDIENTE, FICHA_X, FICHA_O, GRIS_BOTON. Fuentes: FUENTE_TITULO, FUENTE_LABEL, FUENTE_CAMPO, FUENTE_BOTON, FUENTE_ENLACE, FUENTE_GRANDE, FUENTE_CELDA, FUENTE_TURNO, FUENTE_PUNTOS, FUENTE_TIEMPO, FUENTE_LETRA, FUENTE_DEFINICION, FUENTE_RESPUESTA, FUENTE_BOTON_JUEGO, FUENTE_ROSCO.

VentanaLogin extends JFrame

Método | Descripción
--- | ---
VentanaLogin(gestorUsuarios) | Constructor: inicializa campos, construye UI, modoLogin=true.
inicializarVentana() | Construye el layout GridBag con campos username/password, botones y label de error.
accionPrincipal() | Si modoLogin: llama iniciarSesion() y abre menú. Si registro: llama registrarUsuario(), auto-login y abre menú.
cambiarModo() | Alterna modoLogin; actualiza textos de título, botón principal y botón de cambio de modo.
abrirMenuPrincipal() | Instancia VentanaMenuPrincipal con todos los gestores de Aplicacion; oculta esta ventana.

VentanaMenuPrincipal extends JFrame

Método | Descripción
--- | ---
VentanaMenuPrincipal(...) | Constructor: recibe los 4 gestores y el usuario actual; construye UI.
accionJugar() | Abre VentanaSeleccionJuego; si multijugador, pide credenciales del 2º jugador; inicia partida y abre ventana de juego.
accionCargarPartida() | Lista partidas pausadas del usuario, muestra diálogo de selección, reanuda y abre ventana de juego.
accionEstadisticas() | Abre VentanaEstadisticas con el usuario actual.
accionAdmin() | Verifica que es admin, abre VentanaAdmin.
accionCerrarSesion() | Llama cerrarSesion(), abre VentanaLogin, cierra esta ventana.

VentanaSeleccionJuego extends JDialog

Método | Descripción
--- | ---
VentanaSeleccionJuego(padre, gestorJuegos) | Constructor: rellena JList con juegos disponibles; modal; bloquea hasta selección.
accionSeleccionar() | Guarda el juego seleccionado y cierra el diálogo.
accionCancelar() | Deja juegoSeleccionado=null y cierra el diálogo.
getJuegoSeleccionado() | Devuelve el nombre del juego elegido o null si se canceló.

VentanaJuego (abstracta) extends JFrame

Método | Descripción
--- | ---
VentanaJuego(ventanaPadre, gestorPartidas, gestorEstadisticas) | Constructor base.
actualizarVista() [abstracto] | Repinta la UI con el estado actual del juego.
accionPausar() [protected] | Llama gestorPartidas.pausarPartida(), muestra aviso, vuelve al menú.
accionFinalizar() [protected] | Obtiene la partida, llama finalizarPartida() luego registrarResultado(). Vuelve al menú.

VentanaJuegoTresEnRaya extends VentanaJuego

Método | Descripción
--- | ---
VentanaJuegoTresEnRaya(...) | Constructor: recibe ventanaPadre, gestores, partida; castea juego a TresEnRaya; construye UI.
inicializarComponentes() | Panel norte (turno + puntos), centro (tablero 3×3), sur (Pausar + Finalizar).
manejarJugada(fila, columna) | Determina ficha, llama juego.jugarTurno(), avanza turno si no terminó, actualiza vista, muestra resultado si terminó.
mostrarResultado() | Muestra JOptionPane con ganador o "empate", luego llama accionFinalizar().
actualizarVista() | Actualiza texto/color de cada botón del tablero, label de turno y label de puntuaciones.

VentanaJuegoPasapalabra extends JFrame (independiente)

Método | Descripción
--- | ---
VentanaJuegoPasapalabra(...) | Constructor: recibe ventanaPadre, gestores, partida; castea juego a PasaPalabra; construye UI, inicia timer.
construirUI() | BorderLayout: rosco al CENTER, panel derecho al EAST, control al SOUTH.
construirPanelDerecho() | Temporizador, letra actual, definición, campo respuesta, botones Responder/PasaPalabra, contadores aciertos/fallos/pasapalabras.
actualizarVista() | Si terminó llama accionFinalizar(). Si no, actualiza letra, definición, contadores y repinta rosco.
onResponder() | Llama juego.procesarRespuesta(), muestra flash de color (verde/rojo), llama actualizarVista().
onPasapalabra() | Llama juego.pasarPalabra(), flash azul, llama actualizarVista().
accionPausar() | Detiene el timer, llama gestorPartidas.pausarPartida(), vuelve al menú.
accionFinalizar() | Detiene el timer, llama finalizarPartida() y registrarResultado(), muestra resumen HTML, vuelve al menú.
iniciarTimer() | Timer de 1 segundo; decrementa segundosRestantes; si llega a 0 llama accionFinalizar(). Color rojo si ≤30 s.
PanelRosco.paintComponent(g) [inner class] | Dibuja 27 círculos en disposición circular, coloreados según el estado de cada letra.

VentanaEstadisticas extends JFrame

Método | Descripción
--- | ---
VentanaEstadisticas(username, gestores) | Constructor: crea tabla, llama mostrarTabla().
mostrarTabla() | Busca el usuario por username, obtiene estadísticas, rellena la JTable con juego/puntuación/resultado/fecha.

VentanaAdmin extends JFrame

Método | Descripción
--- | ---
VentanaAdmin(gestores) | Constructor: crea JTabbedPane con dos pestañas (Ranking y Usuarios).
crearPanelRanking() | ComboBox con juegos disponibles; JTable con columnas Posición/Usuario/Puntuación/Fecha.
crearPanelUsuarios() | JTable con columnas Username/Tipo (ADMIN o Usuario).
mostrarRanking(juego) | Llama gestorEstadisticas.calcularRanking(), rellena la tabla con posición incremental.
mostrarListaUsuarios() | Llama gestorUsuarios.getListaUsuarios(), muestra tipo según instanceof Administrador.

# PARTE 2 — ANÁLISIS TÉCNICO DEL CÓDIGO

Esta parte está basada en la lectura directa del código fuente ubicado en /JuegoFinal/Programa/src/.

## 2.1Estructura de paquetes

    JuegoFinal/Programa/

    ├── src/

    │   ├── Controlador/     GestorEstadisticas, GestorJuegos, GestorPartidas, GestorUsuarios

    │   ├── Modelo/          Administrador, Estadistica, EstadoPartida (enum), Juego,

    │   │                    Jugador, Partida, PasaPalabra, PuntuacionJugador,

    │   │                    TresEnRaya, Usuario

    │   ├── Persistencia/    GestorPersistencia (interfaz), PersistenciaArchivos

    │   └── Vista/           Aplicacion, Tema, VentanaAdmin, VentanaEstadisticas,

    │                        VentanaJuego, VentanaJuegoPasapalabra,

    │                        VentanaJuegoTresEnRaya, VentanaLogin,

    │                        VentanaMenuPrincipal, VentanaSeleccionJuego

    └── data/

        ├── usuarios.txt          username;sha256hash;esAdmin

        ├── estadisticas.txt      username;juego;pts;victoria;fecha (generado en runtime)

        ├── partidas/id.dat       nombreJuego|users|estadoSerializado (generado en runtime)

        └── roscos/

            ├── rosco_infantil.txt    Letra;Definición;Respuesta  (269 líneas)

            ├── rosco_facil.txt

            ├── rosco_medio.txt

            └── rosco_avanzado.txt

## 2.2Paquete Modelo

### Usuario (abstracta)

Propósito: clase base para todos los usuarios del sistema. Gestiona identidad y autenticación.

Atributo | Tipo | Acceso | Descripción
--- | --- | --- | ---
username | String | protected | Identificador único del usuario
passwordHash | String | protected | Hash SHA-256 de 64 caracteres de la contraseña

Método | Parámetros | Retorno | Descripción
--- | --- | --- | ---
hashear | String password | String | SHA-256 → hex. RuntimeException si SHA-256 no disponible
getUsername | — | String | Devuelve username
verificarPassword | String introPassword | boolean | Compara hash del input con el almacenado
toArchivo | — | String | [abstracto] Serialización CSV para fichero
toString | — | String | [abstracto] Representación legible

Relaciones: extendida por Jugador y Administrador. Usada por GestorUsuarios, GestorPartidas, Partida.

### Jugador extends Usuario

Sin campos propios. toArchivo() serializa esAdmin siempre como false.

Relaciones: instanceof comprobado en GestorUsuarios.esAdministrador() y VentanaAdmin.

### Administrador extends Usuario

Sin campos propios. toArchivo() serializa esAdmin como true.

Relaciones: instanceof comprobado en GestorUsuarios.esAdministrador() y VentanaAdmin. VentanaMenuPrincipal muestra el botón de admin solo si el usuario es instancia de esta clase.

### Juego (abstracta)

Propósito: clase base de todos los juegos. Define estado compartido y el contrato abstracto de mecánica.

Atributo | Tipo | Acceso | Descripción
--- | --- | --- | ---
nombreJuego | String | protected | Nombre identificador del juego
descripcion | String | protected | Descripción breve para el menú
juegoFinalizado | boolean | protected | true cuando el juego ha concluido
listaPuntuacionPorJugador | ArrayList<PuntuacionJugador> | package | Puntuaciones de la partida en curso

Relaciones: extendida por PasaPalabra y TresEnRaya. Agregada en Partida (campo juego). GestorPartidas trabaja siempre con referencia tipo Juego (polimorfismo).

### TresEnRaya extends Juego

Propósito: implementación del juego Tres en Raya para dos jugadores.

Atributo | Tipo | Acceso | Descripción
--- | --- | --- | ---
tablero | char[][] | private | Tablero 3×3. Espacio=vacío, 'X'/'O'=fichas
ganador | String | private | Username del ganador; null si no ha terminado o empate

Detalle de serialización: "casilla00,casilla01,...,casilla22;ganador" donde casillas vacías se representan como espacio y sin ganador como "null". VentanaJuegoTresEnRaya castea la referencia Juego de Partida a TresEnRaya para acceder a jugarTurno() y getTablero().

### PasaPalabra extends Juego

Propósito: juego PasaPalabra con rosco de 27 letras y 4 niveles de dificultad.

Constante | Valor | Significado
--- | --- | ---
ESTADO_PENDIENTE | "0" | Letra aún sin responder
ESTADO_CORRECTA | "1" | Respondida correctamente (+10 pts)
ESTADO_INCORRECTA | "2" | Respondida incorrectamente
ESTADO_PASAPALABRA | "3" | Pasada para más tarde
DIR_ROSCOS | "data/roscos/" | Ruta base de los ficheros de preguntas

Atributo | Tipo | Descripción
--- | --- | ---
rosco | String[][] | Matriz 27×4: [letra, definición, respuesta_correcta, estado]
letraActual | int | Índice (0-26) de la letra en turno
nivel | int | 0=Infantil, 1=Fácil, 2=Medio, 3=Avanzado
aciertos | int | Respuestas correctas acumuladas
fallos | int | Respuestas incorrectas acumuladas
pasapalabras | int | Letras pasadas pendientes de resolver

Carga de roscos: cargarDatos() lee el fichero del nivel, divide en bloques de 10 preguntas por letra y elige una al azar en cada bloque. Esto garantiza variedad entre partidas. limpiarTexto() normaliza NFD y elimina diacríticos para comparar respuestas sin sensibilidad a tildes.

### Partida

Propósito: sesión concreta de juego. Coordina turno circular, estado y resultados.

Atributo | Tipo | Descripción
--- | --- | ---
id | int | Identificador único de la partida
fecha | LocalDate | Fecha de inicio (LocalDate.now() en constructor)
fechaFin | LocalDate | Fecha de finalización; null hasta que se finaliza
turnoActual | int | Índice del jugador activo en listaJugadores
estadoActual | EstadoPartida | EN_CURSO / PAUSADA / FINALIZADA
juego | Juego | Referencia polimórfica al juego de la partida
listaJugadores | ArrayList<Usuario> | Jugadores participantes

### Estadistica

Propósito: registro inmutable del resultado de una partida para un jugador concreto.

Atributo | Tipo | Descripción
--- | --- | ---
username | String | Jugador al que corresponde
nombreJuego | String | Juego jugado
puntuacion | int | Puntuación obtenida
victoria | boolean | true si fue el ganador
fecha | LocalDate | Fecha de la partida

### PuntuacionJugador y EstadoPartida

PuntuacionJugador es un par (username, puntos) utilizado internamente por Juego. EstadoPartida es un enum con tres valores: EN_CURSO, PAUSADA y FINALIZADA.

## 2.3Paquete Controlador

### GestorUsuarios

Gestiona autenticación, registro y sesión activa.

Atributo | Tipo | Descripción
--- | --- | ---
persistencia | GestorPersistencia | Capa de acceso a datos inyectada por constructor
listaUsuarios | ArrayList<Usuario> | Cache en memoria; cargada una sola vez al construir
usuarioActual | Usuario | Usuario con sesión activa; null si no hay sesión

Relaciones: usa GestorPersistencia para cargar/guardar. VentanaLogin llama iniciarSesion() y registrarUsuario(). VentanaAdmin usa esAdministrador() y getListaUsuarios().

### GestorJuegos

Registro de juegos disponibles y fábrica (Factory Method) para crear instancias.

Atributo | Tipo | Descripción
--- | --- | ---
juegosDisponibles | ArrayList<String> | Nombres de juegos registrados

Relaciones: Aplicacion.main() registra "TresEnRaya" y "Pasapalabra". VentanaMenuPrincipal y VentanaSeleccionJuego consultan getJuegosDisponibles(). crearJuego() instancia TresEnRaya o PasaPalabra según el nombre.

### GestorPartidas

Gestiona el ciclo de vida completo de las partidas.

Atributo | Tipo | Descripción
--- | --- | ---
contadorId | int (static) | Autoincremental global; sincronizado con ficheros .dat al arrancar
partidaActual | Partida | Partida en curso; null si ninguna
listaPartidas | ArrayList<Partida> | Historial de partidas finalizadas en esta ejecución
persistencia | GestorPersistencia | Capa de acceso a datos

Formato del fichero .dat: "nombreJuego|user1,user2|estadoSerializado" (formato con jugadores). También soporta el legado "nombreJuego|estadoSerializado" (sin jugadores). Relaciones: delega persistencia en GestorPersistencia. VentanaMenuPrincipal y ventanas de juego llaman iniciarPartida(), pausarPartida(), finalizarPartida().

### GestorEstadisticas

Registro y consulta de estadísticas históricas.

Atributo | Tipo | Descripción
--- | --- | ---
persistencia | GestorPersistencia | Capa de acceso a datos
listaEstadisticas | ArrayList<Estadistica> | Cache en memoria; cargada al construir

Los algoritmos de ordenación (calcularRanking, getUltimasPartidas) usan bubble sort implementado a mano con LocalDate.isBefore(). Relaciones: VentanaEstadisticas y VentanaAdmin consumen sus métodos. VentanaJuego y VentanaJuegoPasapalabra llaman registrarResultado() al finalizar.

## 2.4Paquete Persistencia

### GestorPersistencia (interfaz)

Define 8 operaciones de acceso a datos agrupadas en tres áreas: usuarios (guardar/cargar), estadísticas (agregar/cargar) y partidas pausadas (guardar/cargar/eliminar/listar). La separación mediante interfaz permite cambiar PersistenciaArchivos por cualquier otra implementación (BD relacional, XML, red) sin afectar a los controladores.

### PersistenciaArchivos implements GestorPersistencia

Implementación sobre ficheros de texto plano en la carpeta data/.

Constante | Valor
--- | ---
DATA_USUARIOS | "data/usuarios.txt"
DATA_ESTADISTICAS | "data/estadisticas.txt"
CARPETA_PARTIDAS | "data/partidas/"

El constructor crea los directorios necesarios con mkdirs() si no existen. El formato de usuarios.txt es "username;sha256hash;esAdmin" (un usuario por línea). estadisticas.txt usa append (no se sobreescribe; cada partida añade una línea). Los ficheros .dat en data/partidas/ almacenan el estado serializado completo de una partida pausada.

## 2.5Paquete Vista

### Aplicacion — Punto de entrada

main() crea PersistenciaArchivos, instancia los 4 gestores inyectando la persistencia, registra los dos juegos disponibles ("TresEnRaya" y "Pasapalabra") en GestorJuegos, y lanza VentanaLogin dentro de SwingUtilities.invokeLater() para garantizar que todo el código Swing corra en el Event Dispatch Thread. Expone los gestores como campos estáticos accesibles desde todas las ventanas.

### VentanaLogin

Alterna entre modo login y modo registro con un único botón de cambio de modo. En modo registro: valida campos, crea el usuario, hace auto-login y abre el menú. Muestra mensajes de error en rojo (Tema.ERROR) en un JLabel dedicado.

### VentanaMenuPrincipal

Hub central de la aplicación. Muestra botón de administración solo si el usuario tiene rol de Administrador. La acción "Jugar" inicia el flujo completo: selección de juego, opcionalmente solicitar credenciales del 2º jugador (si multijugador), crear partida, y abrir la ventana específica del juego.

La acción "Cargar Partida" obtiene la lista de partidas pausadas del usuario y muestra sus etiquetas legibles en un JOptionPane. Al seleccionar, reconstruye el objeto Juego correcto, llama reanudarPartida() y abre la ventana correspondiente.

### VentanaJuegoTresEnRaya

Tablero de 9 JButton dispuestos en GridLayout 3×3. Cada botón llama manejarJugada(). La ficha asignada a cada jugador (X para el jugador 0, O para el jugador 1) se determina comparando el índice en listaJugadores con turnoActual. Cuando el juego termina, muestra JOptionPane con el resultado y llama a accionFinalizar() heredado de VentanaJuego.

### VentanaJuegoPasapalabra

No extiende VentanaJuego. Incluye un Timer Swing que cuenta 150 segundos (2:30 min). El panel central es PanelRosco, una inner class que sobreescribe paintComponent() para dibujar 27 círculos en disposición circular, cada uno coloreado según el estado de su letra (verde=correcta, rojo=incorrecta, azul=pasapalabra, gris=pendiente, amarillo=actual). Al finalizar muestra un resumen HTML en un JOptionPane con aciertos, fallos, pasapalabras y puntuación.

### VentanaEstadisticas

JTable con modelo DefaultTableModel y 4 columnas (Juego, Puntuación, Resultado, Fecha). Carga las estadísticas del usuario actual al construir.

### VentanaAdmin

JTabbedPane con dos pestañas. "Ranking": JComboBox con los juegos disponibles; al cambiar el juego se recalcula y muestra el ranking. "Usuarios": tabla con todos los usuarios registrados y su tipo (ADMIN / Usuario).

### Tema

Clase utilitaria final no instanciable. Centraliza la paleta de colores y el conjunto de fuentes de la aplicación. Todas las ventanas referencian Tema.FONDO, Tema.ACENTO, Tema.FUENTE_TITULO, etc. en lugar de valores hardcodeados.

## 2.6Flujo de ejecución — del arranque a una partida

#### 1. Arranque

main() en Aplicacion crea PersistenciaArchivos y los 4 gestores (GestorUsuarios, GestorJuegos, GestorPartidas, GestorEstadisticas). GestorUsuarios y GestorEstadisticas cargan datos de disco. GestorPartidas sincroniza contadorId con los .dat existentes. GestorJuegos registra "TresEnRaya" y "Pasapalabra". SwingUtilities.invokeLater lanza VentanaLogin.

#### 2. Login / Registro

El usuario introduce credenciales. Si es login, GestorUsuarios.iniciarSesion() verifica el hash. Si es registro, GestorUsuarios.registrarUsuario() valida formato, unicidad, crea Jugador, guarda en disco. En ambos casos, si OK, VentanaLogin abre VentanaMenuPrincipal y se oculta.

#### 3. Selección de juego

El usuario pulsa "Jugar". VentanaMenuPrincipal abre VentanaSeleccionJuego (modal). El usuario elige el juego y pulsa "Jugar". Si es multijugador (TresEnRaya), se pide username y contraseña del 2º jugador con verificación de credenciales.

#### 4. Inicio de partida

VentanaMenuPrincipal llama GestorJuegos.crearJuego() para obtener la instancia. Luego llama GestorPartidas.iniciarPartida(juego, jugadores), que invoca juego.inicializar() y crea la Partida con id autoincremental y estado EN_CURSO. A continuación abre VentanaJuegoTresEnRaya o VentanaJuegoPasapalabra según el juego.

#### 5. Juego en curso — Tres en Raya

Cada clic en un botón del tablero llama manejarJugada(). manejarJugada() llama juego.jugarTurno(username, ficha, fila, columna). TresEnRaya coloca la ficha, comprueba victoria/empate y suma 10 pts si hay victoria. La ventana llama partida.siguienteTurno() y actualizarVista(). Si el juego terminó, mostrarResultado() llama accionFinalizar().

#### 5. Juego en curso — Pasapalabra

El timer corre 150 s. El jugador teclea en txtRespuesta y pulsa "Responder" u "onPasapalabra". PasaPalabra.procesarRespuesta() normaliza y compara; si correcta suma 10 pts y avanza. pasarPalabra() marca PASAPALABRA y avanza circularmente. Cada acción llama actualizarVista() que repinta PanelRosco. Al llegar a 0 letras pendientes o al terminar el timer, se llama accionFinalizar().

#### 6. Pausa (opcional)

El usuario pulsa "Pausar". La ventana llama GestorPartidas.pausarPartida(), que serializa el estado del juego como "nombreJuego|user1,user2|estadoSerializado" y lo persiste en data/partidas/id.dat. La ventana se cierra y vuelve a VentanaMenuPrincipal.

#### 7. Reanudación

El usuario pulsa "Cargar Partida". GestorPartidas.listarPartidasUsuario() filtra los .dat que contienen el username. El usuario selecciona una partida. VentanaMenuPrincipal llama GestorJuegos.crearJuego(), GestorPartidas.reanudarPartida() (que parsea el .dat y llama juego.deserializarEstado()), y abre la ventana de juego.

#### 8. Finalización

accionFinalizar() (o accionFinalizar() interno en Pasapalabra) llama primero GestorPartidas.finalizarPartida() (que marca la partida como FINALIZADA, la añade al historial y elimina el .dat), y luego GestorEstadisticas.registrarResultado(partida), que crea una Estadistica por jugador y la persiste en estadisticas.txt. La ventana se cierra y vuelve a VentanaMenuPrincipal.

## 2.7Mapa de conexiones entre clases

Origen | Relación | Destino
--- | --- | ---
Aplicacion | Crea y expone | GestorUsuarios, GestorJuegos, GestorPartidas, GestorEstadisticas
Aplicacion | Crea | PersistenciaArchivos (inyectada en gestores)
Aplicacion | Abre | VentanaLogin
VentanaLogin | Llama | GestorUsuarios.iniciarSesion() / registrarUsuario()
VentanaLogin | Abre | VentanaMenuPrincipal
VentanaMenuPrincipal | Llama | GestorJuegos.crearJuego()
VentanaMenuPrincipal | Llama | GestorPartidas.iniciarPartida() / listarPartidasUsuario() / reanudarPartida()
VentanaMenuPrincipal | Abre | VentanaSeleccionJuego, VentanaJuegoPasapalabra, VentanaJuegoTresEnRaya, VentanaEstadisticas, VentanaAdmin
VentanaJuegoTresEnRaya | Castea y llama | TresEnRaya.jugarTurno() vía referencia Juego de Partida
VentanaJuegoTresEnRaya | Llama (heredado) | VentanaJuego.accionPausar() / accionFinalizar()
VentanaJuego.accionFinalizar() | Llama | GestorPartidas.finalizarPartida() + GestorEstadisticas.registrarResultado()
VentanaJuegoPasapalabra | Castea y llama | PasaPalabra.procesarRespuesta() / pasarPalabra() vía referencia Juego
VentanaJuegoPasapalabra | Llama (propia) | GestorPartidas.pausarPartida() / finalizarPartida() + GestorEstadisticas.registrarResultado()
GestorPartidas | Delega E/S en | GestorPersistencia (cargar / guardar / eliminar partidas)
GestorUsuarios | Delega E/S en | GestorPersistencia (cargar / guardar usuarios)
GestorEstadisticas | Delega E/S en | GestorPersistencia (cargar / agregar estadísticas)
PersistenciaArchivos | Implementa | GestorPersistencia sobre ficheros data/
Partida | Agrega | Juego (referencia polimórfica), ArrayList<Usuario>
GestorPartidas | Serializa vía | Juego.serializarEstado() / deserializarEstado()
GestorEstadisticas.registrarResultado() | Lee | Partida.getPuntuaciones() + Partida.getGanador()
VentanaAdmin | Usa | GestorEstadisticas.calcularRanking() + GestorUsuarios.getListaUsuarios()
VentanaEstadisticas | Usa | GestorEstadisticas.getEstadisticasUsuario()

## 2.8Discrepancias entre diseño y código real

#### ⚠ Las siguientes diferencias se encontraron al comparar la documentación de diseño inicial con el código fuente.

#### 1. Jerarquía de usuarios

Diseño original: Usuario con boolean esAdmin. Código real: Usuario abstracta + subclases Jugador y Administrador. La diferencia es una mejora: permite polimorfismo con instanceof y facilita futuros roles sin modificar código existente.

#### 2. VentanaJuegoPasapalabra no extiende VentanaJuego

Diseño: VentanaJuegoPasapalabra debería extender VentanaJuego (abstracta). Código real: extiende directamente JFrame. Motivo: la lógica de temporizador y resumen final de Pasapalabra difiere suficientemente de accionFinalizar() de VentanaJuego. Consecuencia: algo de código duplicado (pause/finalizar implementado de forma independiente).

#### 3. GestorUsuarios no tiene clase GestorPersistencia propia

El diseño mencionaba "GestorPersistencia (colaboración con JP)" para Adrián Duque. En el código, GestorPersistencia es una interfaz única implementada por PersistenciaArchivos; no hay implementación separada por gestor.

#### 4. Clase Tema no estaba en el diseño inicial

Añadida durante la implementación como buena práctica de centralización de estilos. No modifica ningún comportamiento funcional.

#### 5. VentanaSeleccionJuego no estaba en el diseño inicial

Añadida como JDialog modal para el flujo de selección de juego. No estaba en la arquitectura original pero encaja limpiamente en la capa Vista.

#### 6. Usuario usa nombre Jugador (no Usuario concreta)

El diseño mencionaba "Usuario" como posible clase concreta para jugadores normales. En el código se usa "Jugador" como subclase de Usuario (abstracta). Más correcto desde el punto de vista OO.

#### 7. Bubble sort en GestorEstadisticas en lugar de Collections.sort()

Las ordenaciones en calcularRanking() y getUltimasPartidas() están implementadas con bubble sort manual. No es un error de diseño sino una decisión de implementación, posiblemente por familiaridad del autor con el algoritmo.

## Conclusión

El proyecto implementa correctamente todos los requisitos del enunciado: dos juegos funcionales, gestión de usuarios con roles, persistencia en ficheros sin dependencias externas, arquitectura en cuatro capas con herencia polimorfismo e interfaces, historial de estadísticas, pausa/reanudación de partidas y panel de administración. Las discrepancias encontradas son mejoras respecto al diseño inicial, no regresiones.