Proyecto Final — Programación Orientada a Objetos
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.