From 376fa3c5a67e2088339f0973416e867c0704c4c5 Mon Sep 17 00:00:00 2001 From: Tatiana Villa Date: Sun, 10 May 2026 14:40:50 +0200 Subject: [PATCH] audios y podcast --- mvnw | 0 .../tatvil/taiageweb/TaiageApplication.java | 13 +++ .../taiageweb/config/DataInitializer.java | 20 ++++- .../taiageweb/config/SecurityConfig.java | 35 +++++++- .../tatvil/taiageweb/config/StripeConfig.java | 11 +++ .../controlador/AdminController.java | 50 ++++++++++- .../taiageweb/controlador/PagoController.java | 55 +++++++++++- .../controlador/RegistroController.java | 29 ++++++ .../taiageweb/controlador/TemaController.java | 22 +++++ .../taiageweb/controlador/WebController.java | 36 +++++++- .../controlador/WebhookController.java | 31 +++++++ .../es/tatvil/taiageweb/modelo/Usuario.java | 90 +++++++++++++++++-- .../repositorio/UsuarioRepository.java | 18 ++++ .../taiageweb/servicio/UsuarioService.java | 76 ++++++++++++++-- 14 files changed, 462 insertions(+), 24 deletions(-) mode change 100644 => 100755 mvnw diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 diff --git a/src/main/java/es/tatvil/taiageweb/TaiageApplication.java b/src/main/java/es/tatvil/taiageweb/TaiageApplication.java index bd77542..6d2a61e 100644 --- a/src/main/java/es/tatvil/taiageweb/TaiageApplication.java +++ b/src/main/java/es/tatvil/taiageweb/TaiageApplication.java @@ -3,9 +3,22 @@ package es.tatvil.taiageweb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +/** + * Punto de entrada de la aplicación TAIage. + * + *

Plataforma web para la preparación de las oposiciones TAI (Técnico Auxiliar + * de Informática) de la Administración General del Estado (AGE). Ofrece acceso + * al temario completo, legislación y noticias relevantes, con control de acceso + * por roles y pasarela de pago integrada con Stripe.

+ */ @SpringBootApplication public class TaiageApplication { + /** + * Arranca el contexto Spring Boot. + * + * @param args argumentos de línea de comandos (no se utilizan) + */ public static void main(String[] args) { SpringApplication.run(TaiageApplication.class, args); } diff --git a/src/main/java/es/tatvil/taiageweb/config/DataInitializer.java b/src/main/java/es/tatvil/taiageweb/config/DataInitializer.java index 14cc6b2..c2e7958 100644 --- a/src/main/java/es/tatvil/taiageweb/config/DataInitializer.java +++ b/src/main/java/es/tatvil/taiageweb/config/DataInitializer.java @@ -10,8 +10,13 @@ import java.util.HashSet; import java.util.Set; /** - * Crea el usuario admin la primera vez que arranca la aplicación. - * IMPORTANTE: cambia la contraseña en cuanto puedas desde el panel /admin/usuarios. + * Inicializa datos mínimos en base de datos al arrancar la aplicación. + * + *

Si no existe ningún usuario con el email {@code admin@taiage.es}, crea + * automáticamente la cuenta de administrador con credenciales predeterminadas.

+ * + *

IMPORTANTE: cambia la contraseña por defecto en cuanto + * sea posible desde el panel {@code /admin/usuarios}.

*/ @Component public class DataInitializer implements CommandLineRunner { @@ -19,11 +24,22 @@ public class DataInitializer implements CommandLineRunner { private final UsuarioRepository repo; private final PasswordEncoder encoder; + /** + * Constructor con inyección de dependencias. + * + * @param repo repositorio de usuarios + * @param encoder encoder de contraseñas BCrypt + */ public DataInitializer(UsuarioRepository repo, PasswordEncoder encoder) { this.repo = repo; this.encoder = encoder; } + /** + * Crea el usuario administrador si todavía no existe en la base de datos. + * + * @param args argumentos de línea de comandos (no se utilizan) + */ @Override public void run(String... args) { if (repo.findByEmail("admin@taiage.es").isEmpty()) { diff --git a/src/main/java/es/tatvil/taiageweb/config/SecurityConfig.java b/src/main/java/es/tatvil/taiageweb/config/SecurityConfig.java index fe3a70a..29eb94b 100644 --- a/src/main/java/es/tatvil/taiageweb/config/SecurityConfig.java +++ b/src/main/java/es/tatvil/taiageweb/config/SecurityConfig.java @@ -10,15 +10,41 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +/** + * Configuración de seguridad de la aplicación. + * + *

Define las reglas de acceso HTTP, el proveedor de autenticación basado en + * base de datos y la configuración del formulario de login/logout.

+ * + * + */ @Configuration @EnableWebSecurity public class SecurityConfig { + /** + * Crea el {@link PasswordEncoder} BCrypt usado en toda la aplicación. + * + * @return encoder BCrypt con factor de coste por defecto (10) + */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + /** + * Crea el proveedor de autenticación DAO que consulta {@link UsuarioService}. + * + * @param service servicio que implementa {@link org.springframework.security.core.userdetails.UserDetailsService} + * @param encoder encoder de contraseñas + * @return proveedor configurado + */ @Bean public DaoAuthenticationProvider authProvider(UsuarioService service, PasswordEncoder encoder) { var provider = new DaoAuthenticationProvider(service); @@ -26,6 +52,13 @@ public class SecurityConfig { return provider; } + /** + * Define la cadena de filtros de seguridad HTTP. + * + * @param http objeto de configuración de Spring Security + * @return cadena de filtros construida + * @throws Exception si la configuración es inválida + */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http @@ -39,7 +72,7 @@ public class SecurityConfig { "/", "/inicio", "/login", "/registro", "/leyes", "/noticias", "/acceso-denegado", "/error", "/webhook/stripe", - "/css/**", "/js/**", "/images/**", "/leyes/**", "/favicon.ico" + "/css/**", "/js/**", "/images/**", "/leyes/**", "/audios/**", "/favicon.ico" ).permitAll() // Panel de administración .requestMatchers("/admin/**").hasRole("ADMIN") diff --git a/src/main/java/es/tatvil/taiageweb/config/StripeConfig.java b/src/main/java/es/tatvil/taiageweb/config/StripeConfig.java index 76bfcdf..d71c7e8 100644 --- a/src/main/java/es/tatvil/taiageweb/config/StripeConfig.java +++ b/src/main/java/es/tatvil/taiageweb/config/StripeConfig.java @@ -5,12 +5,23 @@ import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +/** + * Inicializa el SDK de Stripe con la clave secreta de la aplicación. + * + *

La clave se obtiene de la propiedad {@code stripe.secret-key}, que debe + * definirse como variable de entorno {@code STRIPE_SECRET_KEY} en producción.

+ */ @Configuration public class StripeConfig { + /** Clave secreta de la cuenta Stripe (inyectada desde propiedades). */ @Value("${stripe.secret-key}") private String secretKey; + /** + * Asigna la clave secreta al SDK de Stripe tras la construcción del bean. + * Se ejecuta una sola vez al arrancar el contexto de Spring. + */ @PostConstruct public void init() { Stripe.apiKey = secretKey; diff --git a/src/main/java/es/tatvil/taiageweb/controlador/AdminController.java b/src/main/java/es/tatvil/taiageweb/controlador/AdminController.java index 990088e..2587eb2 100644 --- a/src/main/java/es/tatvil/taiageweb/controlador/AdminController.java +++ b/src/main/java/es/tatvil/taiageweb/controlador/AdminController.java @@ -6,37 +6,74 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +/** + * Controlador MVC para el panel de administración de usuarios. + * + *

Todas las rutas bajo {@code /admin} requieren {@code ROLE_ADMIN} + * (configurado en {@link es.tatvil.taiageweb.config.SecurityConfig}). + * Permite listar, habilitar/deshabilitar, asignar acceso de pago, + * crear y eliminar usuarios.

+ */ @Controller @RequestMapping("/admin") public class AdminController { private final UsuarioService service; + /** + * Constructor con inyección de dependencias. + * + * @param service servicio de gestión de usuarios + */ public AdminController(UsuarioService service) { this.service = service; } + /** + * Lista todos los usuarios registrados. + * + * @param model modelo Thymeleaf al que se añade el atributo {@code usuarios} + * @return nombre de la plantilla {@code admin/usuarios} + */ @GetMapping({"", "/", "/usuarios"}) public String listar(Model model) { model.addAttribute("usuarios", service.listarTodos()); return "admin/usuarios"; } - /** Activa o desactiva la cuenta */ + /** + * Activa o desactiva la cuenta del usuario indicado. + * + * @param id identificador del usuario + * @return redirección a {@code /admin/usuarios} + */ @PostMapping("/usuarios/{id}/toggle-habilitado") public String toggleHabilitado(@PathVariable Long id) { service.toggleHabilitado(id); return "redirect:/admin/usuarios"; } - /** Concede o revoca el acceso al curso */ + /** + * Concede o revoca el acceso al curso ({@code ROLE_PAGADO}) del usuario indicado. + * + * @param id identificador del usuario + * @return redirección a {@code /admin/usuarios} + */ @PostMapping("/usuarios/{id}/toggle-pagado") public String togglePagado(@PathVariable Long id) { service.toggleRolPagado(id); return "redirect:/admin/usuarios"; } - /** El admin crea un usuario directamente activo */ + /** + * Crea un usuario directamente activo desde el panel de administración. + * + * @param email email del nuevo usuario + * @param password contraseña en texto plano + * @param pagado si {@code true}, se asigna {@code ROLE_PAGADO} además de {@code ROLE_USER} + * @param ra atributos flash para mensajes de error entre redirecciones + * @return redirección a {@code /admin/usuarios} + */ @PostMapping("/usuarios/nuevo") public String crear( @RequestParam String email, @@ -51,7 +88,12 @@ public class AdminController { return "redirect:/admin/usuarios"; } - /** Elimina un usuario */ + /** + * Elimina un usuario de la base de datos. + * + * @param id identificador del usuario a eliminar + * @return redirección a {@code /admin/usuarios} + */ @PostMapping("/usuarios/{id}/eliminar") public String eliminar(@PathVariable Long id) { service.eliminar(id); diff --git a/src/main/java/es/tatvil/taiageweb/controlador/PagoController.java b/src/main/java/es/tatvil/taiageweb/controlador/PagoController.java index 42fa961..c058a4b 100644 --- a/src/main/java/es/tatvil/taiageweb/controlador/PagoController.java +++ b/src/main/java/es/tatvil/taiageweb/controlador/PagoController.java @@ -13,6 +13,20 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +/** + * Controlador MVC para el flujo de pago con Stripe. + * + *

Gestiona tres pasos del proceso de compra:

+ *
    + *
  1. Mostrar la página informativa con precio ({@code GET /pagar}).
  2. + *
  3. Crear la sesión de checkout en Stripe y redirigir al usuario + * ({@code POST /pagar/iniciar}).
  4. + *
  5. Mostrar páginas de éxito o cancelación tras volver de Stripe.
  6. + *
+ * + *

La concesión real del acceso ocurre vía webhook en + * {@link WebhookController}, no en las páginas de retorno de Stripe.

+ */ @Controller public class PagoController { @@ -24,11 +38,25 @@ public class PagoController { private final UsuarioRepository usuarioRepo; + /** + * Constructor con inyección de dependencias. + * + * @param usuarioRepo repositorio de usuarios + */ public PagoController(UsuarioRepository usuarioRepo) { this.usuarioRepo = usuarioRepo; } - /** Página informativa con precio y botón "Pagar ahora" */ + /** + * Muestra la página informativa de pago con el precio actual. + * + *

Si el usuario ya tiene {@code ROLE_PAGADO}, se le redirige directamente + * al curso sin pasar por la pasarela.

+ * + * @param auth autenticación del usuario en sesión (puede ser {@code null} si no está autenticado) + * @param model modelo Thymeleaf para pasar el precio formateado + * @return vista {@code pagar} o redirección a {@code /curso} + */ @GetMapping("/pagar") public String mostrarPagina(Authentication auth, Model model) { // Si ya tiene acceso, redirigir directo al curso @@ -40,7 +68,17 @@ public class PagoController { return "pagar"; } - /** Crea la sesión de pago en Stripe y redirige a su pasarela */ + /** + * Crea una sesión de pago en Stripe y redirige al usuario a la pasarela. + * + *

Utiliza el id del usuario como {@code clientReferenceId} para poder + * identificarlo cuando llegue el webhook de confirmación.

+ * + * @param auth autenticación del usuario en sesión + * @param request petición HTTP para construir las URLs de retorno + * @return redirección a la URL de checkout de Stripe + * @throws StripeException si falla la comunicación con la API de Stripe + */ @PostMapping("/pagar/iniciar") public String iniciarPago(Authentication auth, HttpServletRequest request) throws StripeException { String baseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); @@ -71,11 +109,24 @@ public class PagoController { return "redirect:" + session.getUrl(); } + /** + * Página de confirmación mostrada tras un pago exitoso. + * + *

El acceso real al curso se concede mediante el webhook de Stripe, + * no en esta página.

+ * + * @return nombre de la plantilla {@code pago-exito} + */ @GetMapping("/pagar/exito") public String exitoPago() { return "pago-exito"; } + /** + * Página mostrada cuando el usuario cancela el pago en Stripe. + * + * @return nombre de la plantilla {@code pago-cancelado} + */ @GetMapping("/pagar/cancelado") public String canceladoPago() { return "pago-cancelado"; diff --git a/src/main/java/es/tatvil/taiageweb/controlador/RegistroController.java b/src/main/java/es/tatvil/taiageweb/controlador/RegistroController.java index 3be5f69..48c9818 100644 --- a/src/main/java/es/tatvil/taiageweb/controlador/RegistroController.java +++ b/src/main/java/es/tatvil/taiageweb/controlador/RegistroController.java @@ -7,20 +7,49 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +/** + * Controlador MVC para el auto-registro de nuevos usuarios. + * + *

Procesa el formulario de registro en {@code /registro}. La cuenta creada + * queda deshabilitada hasta que el administrador la active desde + * {@code /admin/usuarios}.

+ */ @Controller public class RegistroController { private final UsuarioService service; + /** + * Constructor con inyección de dependencias. + * + * @param service servicio de gestión de usuarios + */ public RegistroController(UsuarioService service) { this.service = service; } + /** + * Muestra el formulario de registro. + * + * @return nombre de la plantilla {@code registro} + */ @GetMapping("/registro") public String mostrarFormulario() { return "registro"; } + /** + * Procesa el formulario de registro. + * + *

Valida que las contraseñas coincidan y delega en {@link UsuarioService#registrar}. + * Si el registro tiene éxito, redirige al login con el parámetro {@code ?registrado}.

+ * + * @param email email del nuevo usuario + * @param password contraseña elegida + * @param passwordConfirm confirmación de la contraseña + * @param model modelo Thymeleaf para enviar mensajes de error + * @return redirección a {@code /login?registrado} si éxito, o la vista {@code registro} con error + */ @PostMapping("/registro") public String registrar( @RequestParam String email, diff --git a/src/main/java/es/tatvil/taiageweb/controlador/TemaController.java b/src/main/java/es/tatvil/taiageweb/controlador/TemaController.java index 3bee047..a0b7b78 100644 --- a/src/main/java/es/tatvil/taiageweb/controlador/TemaController.java +++ b/src/main/java/es/tatvil/taiageweb/controlador/TemaController.java @@ -11,10 +11,32 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.nio.charset.StandardCharsets; +/** + * API REST que sirve los ficheros Markdown del temario. + * + *

Todos los recursos están bajo {@code /api/temas/**} y requieren + * autenticación con {@code ROLE_PAGADO} o {@code ROLE_ADMIN} (configurado en + * {@link es.tatvil.taiageweb.config.SecurityConfig}).

+ * + *

Los ficheros se leen desde el classpath en {@code temas/} y se devuelven + * como texto plano UTF-8. Se protege contra ataques de path traversal + * rechazando rutas que contengan {@code ..} o {@code //}.

+ */ @RestController @RequestMapping("/api/temas") public class TemaController { + /** + * Devuelve el contenido Markdown de un tema dado su ruta relativa. + * + *

Ejemplo de petición: {@code GET /api/temas/bloque1/tema1.md}

+ * + * @param request petición HTTP de la que se extrae la ruta del recurso + * @return {@code 200 OK} con el contenido Markdown como texto plano, + * {@code 400 Bad Request} si la ruta es inválida, + * {@code 404 Not Found} si el fichero no existe + * @throws IOException si ocurre un error leyendo el recurso del classpath + */ @GetMapping("/**") public ResponseEntity getTema(HttpServletRequest request) throws IOException { String uri = request.getRequestURI(); diff --git a/src/main/java/es/tatvil/taiageweb/controlador/WebController.java b/src/main/java/es/tatvil/taiageweb/controlador/WebController.java index d111226..1fdde90 100644 --- a/src/main/java/es/tatvil/taiageweb/controlador/WebController.java +++ b/src/main/java/es/tatvil/taiageweb/controlador/WebController.java @@ -4,26 +4,60 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; /** - * Sirve las páginas principales del sitio a través de Thymeleaf. + * Controlador MVC que sirve las páginas principales del sitio a través de Thymeleaf. + * + *

Todas las rutas devuelven el nombre de la plantilla correspondiente en + * {@code src/main/resources/templates/}. Spring Security controla qué rutas + * son accesibles sin autenticación.

*/ @Controller public class WebController { + /** + * Página de inicio. + * + * @return nombre de la plantilla {@code index} + */ @GetMapping({"/", "/inicio"}) public String inicio() { return "index"; } + /** + * Página de inicio de sesión. + * + * @return nombre de la plantilla {@code login} + */ @GetMapping("/login") public String login() { return "login"; } + /** + * Visor del temario del curso (requiere {@code ROLE_PAGADO} o {@code ROLE_ADMIN}). + * + * @return nombre de la plantilla {@code curso} + */ @GetMapping("/curso") public String curso() { return "curso"; } + /** + * Navegador de legislación; acceso público. + * + * @return nombre de la plantilla {@code leyes} + */ @GetMapping("/leyes") public String leyes() { return "leyes"; } + /** + * Agregador de noticias INAP/BOE; acceso público. + * + * @return nombre de la plantilla {@code noticias} + */ @GetMapping("/noticias") public String noticias() { return "noticias"; } + /** + * Página de acceso denegado (HTTP 403). + * + * @return nombre de la plantilla {@code acceso-denegado} + */ @GetMapping("/acceso-denegado") public String accesoDenegado() { return "acceso-denegado"; } } diff --git a/src/main/java/es/tatvil/taiageweb/controlador/WebhookController.java b/src/main/java/es/tatvil/taiageweb/controlador/WebhookController.java index 67f1d81..efcd047 100644 --- a/src/main/java/es/tatvil/taiageweb/controlador/WebhookController.java +++ b/src/main/java/es/tatvil/taiageweb/controlador/WebhookController.java @@ -9,18 +9,49 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +/** + * Controlador REST que recibe y procesa webhooks de Stripe. + * + *

Escucha en {@code POST /webhook/stripe} (ruta excluida de CSRF en + * {@link es.tatvil.taiageweb.config.SecurityConfig}). Verifica la firma + * del payload con el secreto de webhook antes de procesar ningún evento.

+ * + *

Evento gestionado:

+ * + */ @RestController public class WebhookController { + /** Secreto de webhook de Stripe (inyectado desde {@code STRIPE_WEBHOOK_SECRET}). */ @Value("${stripe.webhook-secret}") private String webhookSecret; private final UsuarioService usuarioService; + /** + * Constructor con inyección de dependencias. + * + * @param usuarioService servicio de gestión de usuarios + */ public WebhookController(UsuarioService usuarioService) { this.usuarioService = usuarioService; } + /** + * Endpoint receptor de eventos de Stripe. + * + *

Verifica la firma {@code Stripe-Signature} del payload. Si es válida + * y el evento es {@code checkout.session.completed}, concede + * {@code ROLE_PAGADO} al usuario referenciado.

+ * + * @param payload cuerpo crudo de la petición HTTP (necesario para verificar la firma) + * @param sigHeader valor del header {@code Stripe-Signature} + * @return {@code 200 OK} si el evento se procesa correctamente, + * {@code 400 Bad Request} si la firma es inválida + */ @PostMapping("/webhook/stripe") public ResponseEntity stripeWebhook( @RequestBody String payload, diff --git a/src/main/java/es/tatvil/taiageweb/modelo/Usuario.java b/src/main/java/es/tatvil/taiageweb/modelo/Usuario.java index cece9c4..090f086 100644 --- a/src/main/java/es/tatvil/taiageweb/modelo/Usuario.java +++ b/src/main/java/es/tatvil/taiageweb/modelo/Usuario.java @@ -4,55 +4,135 @@ import jakarta.persistence.*; import java.util.HashSet; import java.util.Set; +/** + * Entidad JPA que representa a un usuario registrado en la plataforma. + * + *

Los usuarios se almacenan en la tabla {@code usuarios}. Sus roles se guardan + * en la tabla auxiliar {@code usuario_roles} mediante una relación + * {@code @ElementCollection}.

+ * + *

Roles disponibles

+ *
    + *
  • {@code ROLE_USER} – usuario registrado, sin acceso al curso.
  • + *
  • {@code ROLE_PAGADO} – ha completado el pago; accede al temario completo.
  • + *
  • {@code ROLE_ADMIN} – administrador; acceso total al panel de gestión.
  • + *
+ */ @Entity @Table(name = "usuarios") public class Usuario { + /** Identificador autogenerado (clave primaria). */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + /** Dirección de correo electrónico; única e indexada, se usa como nombre de usuario. */ @Column(unique = true, nullable = false, length = 100) private String email; + /** Contraseña cifrada con BCrypt. Nunca se almacena en texto plano. */ @Column(nullable = false, length = 255) private String password; - /** Cuenta activa (admin la activa; pago automático también puede activarla) */ + /** + * Indica si la cuenta está activa. + *

Las cuentas nuevas arrancan con {@code false}; el administrador o un pago + * completado la activan automáticamente.

+ */ @Column(nullable = false) private boolean habilitado = false; /** - * Roles posibles: - * ROLE_USER – registrado (sin acceso al curso) - * ROLE_PAGADO – ha pagado (acceso al curso) - * ROLE_ADMIN – administrador (acceso total) + * Conjunto de roles asignados al usuario. + * Los valores posibles son {@code ROLE_USER}, {@code ROLE_PAGADO} y {@code ROLE_ADMIN}. */ @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "usuario_roles", joinColumns = @JoinColumn(name = "usuario_id")) @Column(name = "rol") private Set roles = new HashSet<>(); + /** Constructor sin argumentos requerido por JPA. */ public Usuario() {} // ── Getters / Setters ──────────────────────────────────── + /** + * Devuelve el identificador único del usuario. + * + * @return id generado por la base de datos + */ public Long getId() { return id; } + /** + * Devuelve el email del usuario. + * + * @return dirección de correo tal como fue registrada + */ public String getEmail() { return email; } + + /** + * Establece el email del usuario. + * + * @param email dirección de correo electrónico + */ public void setEmail(String email) { this.email = email; } + /** + * Devuelve la contraseña cifrada. + * + * @return hash BCrypt de la contraseña + */ public String getPassword() { return password; } + + /** + * Establece la contraseña (debe ser el hash BCrypt, nunca texto plano). + * + * @param password hash BCrypt + */ public void setPassword(String password) { this.password = password; } + /** + * Indica si la cuenta está habilitada para iniciar sesión. + * + * @return {@code true} si la cuenta está activa + */ public boolean isHabilitado() { return habilitado; } + + /** + * Activa o desactiva la cuenta. + * + * @param habilitado {@code true} para habilitar la cuenta + */ public void setHabilitado(boolean habilitado) { this.habilitado = habilitado; } + /** + * Devuelve el conjunto de roles asignados al usuario. + * + * @return conjunto mutable de roles + */ public Set getRoles() { return roles; } + + /** + * Reemplaza el conjunto de roles del usuario. + * + * @param roles nuevo conjunto de roles + */ public void setRoles(Set roles) { this.roles = roles; } // ── Helpers para la vista ──────────────────────────────── + /** + * Indica si el usuario tiene acceso al curso (rol {@code ROLE_PAGADO}). + * + * @return {@code true} si el rol está presente + */ public boolean isPagado() { return roles.contains("ROLE_PAGADO"); } + + /** + * Indica si el usuario es administrador (rol {@code ROLE_ADMIN}). + * + * @return {@code true} si el rol está presente + */ public boolean isAdmin() { return roles.contains("ROLE_ADMIN"); } } diff --git a/src/main/java/es/tatvil/taiageweb/repositorio/UsuarioRepository.java b/src/main/java/es/tatvil/taiageweb/repositorio/UsuarioRepository.java index c750a83..b0169d3 100644 --- a/src/main/java/es/tatvil/taiageweb/repositorio/UsuarioRepository.java +++ b/src/main/java/es/tatvil/taiageweb/repositorio/UsuarioRepository.java @@ -5,9 +5,27 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; +/** + * Repositorio JPA para la entidad {@link Usuario}. + * + *

Extiende {@link JpaRepository} con métodos de búsqueda adicionales + * necesarios para la autenticación y el registro de usuarios.

+ */ public interface UsuarioRepository extends JpaRepository { + /** + * Busca un usuario por su dirección de correo electrónico. + * + * @param email email del usuario a buscar + * @return {@link Optional} con el usuario si existe, vacío en caso contrario + */ Optional findByEmail(String email); + /** + * Comprueba si ya existe un usuario registrado con el email dado. + * + * @param email email a verificar + * @return {@code true} si el email ya está en uso + */ boolean existsByEmail(String email); } diff --git a/src/main/java/es/tatvil/taiageweb/servicio/UsuarioService.java b/src/main/java/es/tatvil/taiageweb/servicio/UsuarioService.java index 354df69..ebd60b0 100644 --- a/src/main/java/es/tatvil/taiageweb/servicio/UsuarioService.java +++ b/src/main/java/es/tatvil/taiageweb/servicio/UsuarioService.java @@ -15,19 +15,39 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +/** + * Servicio de negocio para la gestión de usuarios. + * + *

Implementa {@link UserDetailsService} para la integración con Spring Security + * y centraliza todas las operaciones sobre la entidad {@link Usuario}: registro, + * activación, asignación de roles y eliminación.

+ */ @Service public class UsuarioService implements UserDetailsService { private final UsuarioRepository repo; private final PasswordEncoder encoder; + /** + * Constructor con inyección de dependencias. + * + * @param repo repositorio de usuarios + * @param encoder encoder de contraseñas BCrypt + */ public UsuarioService(UsuarioRepository repo, PasswordEncoder encoder) { this.repo = repo; this.encoder = encoder; } - // ── Spring Security ────────────────────────────────────── + // ── Spring Security ────────────────────────────────── + /** + * Carga los detalles de un usuario por su email para Spring Security. + * + * @param email email del usuario (campo {@code username} del formulario de login) + * @return detalles del usuario listos para la autenticación + * @throws UsernameNotFoundException si no existe ningún usuario con ese email + */ @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Usuario u = repo.findByEmail(email) @@ -48,8 +68,13 @@ public class UsuarioService implements UserDetailsService { } // ── Auto-registro (pendiente de activación) ────────────── - - @Transactional + /** + * Registra un nuevo usuario con la cuenta deshabilitada hasta que el admin la active. + * + * @param email email del nuevo usuario + * @param passwordPlana contraseña en texto plano (mínimo 8 caracteres) + * @throws IllegalArgumentException si el email ya está registrado o la contraseña es demasiado corta + */ @Transactional public void registrar(String email, String passwordPlana) { if (repo.existsByEmail(email)) { throw new IllegalArgumentException("El email ya está registrado"); @@ -66,19 +91,32 @@ public class UsuarioService implements UserDetailsService { } // ── Consultas ──────────────────────────────────────────── - - public List listarTodos() { + /** + * Devuelve todos los usuarios registrados. + * + * @return lista de usuarios ordenada por ID + */ public List listarTodos() { return repo.findAll(); } // ── Acciones de admin ──────────────────────────────────── - - @Transactional + /** + * Invierte el estado de habilitación de la cuenta de un usuario. + * + * @param id identificador del usuario + * @throws java.util.NoSuchElementException si no existe un usuario con ese id + */ @Transactional public void toggleHabilitado(Long id) { Usuario u = repo.findById(id).orElseThrow(); u.setHabilitado(!u.isHabilitado()); } + /** + * Concede o revoca el rol {@code ROLE_PAGADO} de un usuario. + * + * @param id identificador del usuario + * @throws java.util.NoSuchElementException si no existe un usuario con ese id + */ @Transactional public void toggleRolPagado(Long id) { Usuario u = repo.findById(id).orElseThrow(); @@ -89,7 +127,15 @@ public class UsuarioService implements UserDetailsService { } } - /** Concede acceso al curso tras un pago confirmado por Stripe (nunca quita el rol) */ + /** + * Concede acceso al curso tras un pago confirmado por Stripe. + * + *

Añade {@code ROLE_PAGADO} y activa la cuenta si estuviera deshabilitada. + * Este método nunca quita el rol una vez concedido.

+ * + * @param id identificador del usuario + * @throws java.util.NoSuchElementException si no existe un usuario con ese id + */ @Transactional public void darAccesoPagado(Long id) { Usuario u = repo.findById(id).orElseThrow(); @@ -97,7 +143,14 @@ public class UsuarioService implements UserDetailsService { u.setHabilitado(true); } - /** El admin crea directamente un usuario ya activo */ + /** + * Crea un usuario directamente activo desde el panel de administración. + * + * @param email email del nuevo usuario + * @param passwordPlana contraseña en texto plano + * @param pagado si {@code true}, se añade {@code ROLE_PAGADO} además de {@code ROLE_USER} + * @throws IllegalArgumentException si el email ya está registrado + */ @Transactional public void crearPorAdmin(String email, String passwordPlana, boolean pagado) { if (repo.existsByEmail(email)) { @@ -114,6 +167,11 @@ public class UsuarioService implements UserDetailsService { repo.save(u); } + /** + * Elimina un usuario de la base de datos. + * + * @param id identificador del usuario a eliminar + */ @Transactional public void eliminar(Long id) { repo.deleteById(id);