PULSOFT

3.Validaciones

1.Jugador

Crea una API en Spring Boot para gestionar jugadores de fútbol, enfocada únicamente en practicar Bean Validation. No es necesario usar Service ni base de datos; solo entidad y controller.

Debes crear una entidad Jugador con los campos id, nombre, dorsal, posicion y fechaNacimiento. El nombre es obligatorio y debe tener entre 3 y 40 caracteres, el dorsal debe ser un número entre 1 y 99, la posición es obligatoria y no puede estar vacía, y la fecha de nacimiento es obligatoria y debe ser una fecha pasada.

Expón un endpoint POST /jugadores que reciba un Jugador en el body, lo valide con @Valid y lo devuelva si los datos son correctos.

Prueba el endpoint con distintos JSON válidos e inválidos para comprobar el comportamiento automático de las validaciones en Spring Boot.

package es.pulsoft.spring_liga.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.time.LocalDate;

/**
 * Entidad que representa un jugador de fútbol dentro del sistema.
 * Se mapea a la tabla "jugador" en la base de datos.
 * Incluye validaciones Bean Validation para asegurar la integridad de los datos.
 */
@Entity
@Table(name = "jugador")
public class Jugador {

    /**
     * Identificador único del jugador.
     * Se genera automáticamente mediante auto-increment en base de datos.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * Nombre del jugador.
     * Campo obligatorio con longitud entre 3 y 40 caracteres.
     */
    @NotBlank(message = "El nombre no puede quedar sin rellenar")
    @Size(min = 3, max = 40, message = "El nombre tiene que tener entre 3 y 40 caracteres")
    private String nombre;

    /**
     * Dorsal del jugador.
     * Campo obligatorio con rango entre 1 y 99.
     */
    @NotNull(message = "El dorsal es obligatorio")
    @Min(value = 1, message = "El dorsal no puede ser inferior a 1")
    @Max(value = 99, message = "El dorsal no puede ser mayor de 99")
    private Integer dorsal;

    /**
     * Posición del jugador en el campo (portero, defensa, centrocampista, delantero).
     * Campo obligatorio.
     */
    @NotBlank(message = "La posicion es obligatoria")
    private String posicion;

    /**
     * Fecha de nacimiento del jugador.
     * Debe ser una fecha pasada o presente, nunca futura.
     */
    @PastOrPresent(message = "La fecha no puede ser mayor al dia presente")
    private LocalDate fechaNacimiento;

    /**
     * Constructor vacío requerido por JPA.
     */
    public Jugador() {
    }

    /**
     * Constructor para crear instancias del jugador sin id.
     */
    public Jugador(String nombre, Integer dorsal, String posicion, LocalDate fechaNacimiento) {
        this.nombre = nombre;
        this.dorsal = dorsal;
        this.posicion = posicion;
        this.fechaNacimiento = fechaNacimiento;
    }

    // =========================
    // GETTERS
    // =========================

    public Long getId() {
        return id;
    }

    public String getNombre() {
        return nombre;
    }

    public Integer getDorsal() {
        return dorsal;
    }

    public String getPosicion() {
        return posicion;
    }

    public LocalDate getFechaNacimiento() {
        return fechaNacimiento;
    }

    // =========================
    // SETTERS
    // =========================

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public void setDorsal(Integer dorsal) {
        this.dorsal = dorsal;
    }

    public void setPosicion(String posicion) {
        this.posicion = posicion;
    }

    public void setFechaNacimiento(LocalDate fechaNacimiento) {
        this.fechaNacimiento = fechaNacimiento;
    }
}
package es.pulsoft.spring_liga.controller;

import es.pulsoft.spring_liga.model.Jugador;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * Controlador REST encargado de gestionar las operaciones relacionadas con Jugador.
 * En esta fase del proyecto se utiliza únicamente para probar validaciones Bean Validation.
 */
@RestController
@RequestMapping("/jugadores")
public class JugadorController {

    /**
     * Endpoint para crear un jugador.
     * Recibe un objeto Jugador en formato JSON, lo valida automáticamente con @Valid
     * y lo devuelve si cumple todas las restricciones definidas en el modelo.
     *
     * Si alguna validación falla, Spring devuelve automáticamente un 400 Bad Request.
     */
    @PostMapping
    public ResponseEntity<Jugador> crearJugador(@Valid @RequestBody Jugador jugador) {
        return ResponseEntity.ok(jugador);
    }
}
2.Equipo

Ejercicio: Validaciones API Equipo

Crea una API en Spring Boot para gestionar equipos de fútbol, centrada únicamente en validaciones con Bean Validation. No es necesario usar Service ni persistencia en base de datos.

Debes crear una entidad Equipo con los campos id, nombre, ciudad, estadio y fundacion. El nombre es obligatorio y debe tener entre 3 y 50 caracteres, la ciudad es obligatoria y no puede estar vacía, el estadio es opcional pero si se introduce no puede superar los 80 caracteres, y la fecha de fundación es obligatoria y no puede ser futura.

Expón un endpoint POST /equipos que reciba un Equipo en el body, lo valide con @Valid y lo devuelva si es correcto.

Prueba el endpoint enviando datos válidos e inválidos para comprobar el comportamiento automático de las validaciones de Spring Boot.

package es.pulsoft.spring_liga.controller;

import es.pulsoft.spring_liga.model.Equipo;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * Controlador REST encargado de gestionar las operaciones relacionadas con Equipo.
 * En esta fase del proyecto se usa únicamente para probar validaciones Bean Validation.
 */
@RestController
@RequestMapping("/equipos")
public class EquipoController {

    /**
     * Endpoint para crear un equipo.
     * Recibe un objeto Equipo en formato JSON, lo valida automáticamente
     * gracias a @Valid y lo devuelve si los datos son correctos.
     *
     * Si las validaciones fallan, Spring devuelve automáticamente un 400 Bad Request.
     */
    @PostMapping
    public ResponseEntity<Equipo> crearEquipo(@Valid @RequestBody Equipo equipo) {
        return ResponseEntity.ok(equipo);
    }
}
package es.pulsoft.spring_liga.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.time.LocalDate;

/**
 * Entidad que representa un equipo de fútbol dentro del sistema.
 * Se mapea a la tabla "equipo" en la base de datos.
 * Incluye validaciones Bean Validation para asegurar la integridad de los datos de entrada.
 */
@Entity
@Table(name = "equipo")
public class Equipo {

    /**
     * Identificador único del equipo.
     * Se genera automáticamente mediante auto-increment en la base de datos.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * Nombre del equipo.
     * Obligatorio y con longitud entre 3 y 50 caracteres.
     */
    @NotBlank(message = "No puede quedar en blanco")
    @Size(min = 3, max = 50, message = "El nombre tiene que tener entre 3 y 50 caracteres")
    private String nombre;

    /**
     * Ciudad a la que pertenece el equipo.
     * Campo obligatorio.
     */
    @NotBlank(message = "No puede quedar en blanco")
    private String ciudad;

    /**
     * Estadio del equipo.
     * Campo opcional con longitud máxima de 80 caracteres.
     */
    @Size(max = 80, message = "No puede tener más de 80 caracteres")
    private String estadio;

    /**
     * Fecha de fundación del equipo.
     * Debe ser una fecha pasada o presente, nunca futura.
     */
    @PastOrPresent(message = "Tiene que ser una fecha pasada")
    private LocalDate fundacion;

    /**
     * Constructor vacío requerido por JPA.
     */
    public Equipo() {
    }

    /**
     * Constructor para crear instancias del equipo sin id.
     */
    public Equipo(String nombre, String ciudad, String estadio, LocalDate fundacion) {
        this.nombre = nombre;
        this.ciudad = ciudad;
        this.estadio = estadio;
        this.fundacion = fundacion;
    }

    // =========================
    // GETTERS
    // =========================

    public Long getId() {
        return id;
    }

    public String getNombre() {
        return nombre;
    }

    public String getCiudad() {
        return ciudad;
    }

    public String getEstadio() {
        return estadio;
    }

    public LocalDate getFundacion() {
        return fundacion;
    }

    // =========================
    // SETTERS
    // =========================

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public void setCiudad(String ciudad) {
        this.ciudad = ciudad;
    }

    public void setEstadio(String estadio) {
        this.estadio = estadio;
    }

    public void setFundacion(LocalDate fundacion) {
        this.fundacion = fundacion;
    }
}
3.Partido

Crea una entidad Partido para una liga de fútbol que se almacene en base de datos y represente un partido entre dos equipos. Cada partido debe incluir un equipo local y un equipo visitante, ambos obligatorios y validados en cascada con Bean Validation, además de los goles de cada equipo que deben estar entre 0 y 20, y una fecha del partido que no puede ser futura. La entidad debe incluir constructor vacío, constructor con parámetros, getters y setters, y aplicar validaciones usando anotaciones como @NotNull, @Valid, @PositiveOrZero, @Max y @PastOrPresent para garantizar la integridad de los datos antes de persistirlos.

package es.pulsoft.spring_liga.controller;

import es.pulsoft.spring_liga.model.Partido;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * Controlador REST encargado de gestionar los partidos de fútbol.
 * En esta fase del proyecto se utiliza únicamente para probar validaciones Bean Validation.
 */
@RestController
@RequestMapping("/partidos")
public class PartidoController {

    /**
     * Endpoint para crear un partido.
     * Recibe un objeto Partido en formato JSON, lo valida automáticamente con @Valid
     * incluyendo la validación en cascada de los equipos.
     *
     * Si alguna validación falla, Spring devuelve un 400 Bad Request de forma automática.
     */
    @PostMapping
    public ResponseEntity<Partido> crearPartido(@Valid @RequestBody Partido partido) {
        return ResponseEntity.ok(partido);
    }
}
package es.pulsoft.spring_liga.model;

import jakarta.persistence.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import java.time.LocalDate;

/**
 * Entidad que representa un partido de fútbol dentro del sistema.
 * Se mapea a la tabla "partido" en la base de datos.
 * Incluye validaciones Bean Validation y validación en cascada para equipos.
 */
@Entity
@Table(name = "partido")
public class Partido {

    /**
     * Identificador único del partido.
     * Generado automáticamente por la base de datos.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * Equipo local del partido.
     * Validación en cascada (@Valid) para aplicar también las reglas del objeto Equipo.
     * Campo obligatorio.
     */
    @Valid
    @NotNull(message = "El equipo local no puede quedar sin rellenar")
    private Equipo equipoLocal;

    /**
     * Equipo visitante del partido.
     * Validación en cascada (@Valid) para validar el objeto Equipo interno.
     * Campo obligatorio.
     */
    @Valid
    @NotNull(message = "El equipo visitante no puede quedar sin rellenar")
    private Equipo equipoVisitante;

    /**
     * Goles anotados por el equipo local.
     * Debe ser un valor entre 0 y 20.
     */
    @NotNull(message = "Los goles del equipo local son obligatorios")
    @PositiveOrZero(message = "Los goles no pueden ser negativos")
    @Max(value = 20, message = "Los goles no pueden superar 20")
    private Integer golesLocal;

    /**
     * Goles anotados por el equipo visitante.
     * Debe ser un valor entre 0 y 20.
     */
    @NotNull(message = "Los goles del equipo visitante son obligatorios")
    @PositiveOrZero(message = "Los goles no pueden ser negativos")
    @Max(value = 20, message = "Los goles no pueden superar 20")
    private Integer golesVisitante;

    /**
     * Fecha en la que se disputó el partido.
     * Debe ser una fecha pasada o presente, nunca futura.
     */
    @NotNull(message = "La fecha del partido es obligatoria")
    @PastOrPresent(message = "La fecha no puede ser futura")
    private LocalDate fecha;

    /**
     * Constructor vacío requerido por JPA.
     */
    public Partido() {
    }

    /**
     * Constructor para crear instancias de Partido sin id.
     */
    public Partido(Equipo equipoLocal, Equipo equipoVisitante,
                   Integer golesLocal, Integer golesVisitante, LocalDate fecha) {
        this.equipoLocal = equipoLocal;
        this.equipoVisitante = equipoVisitante;
        this.golesLocal = golesLocal;
        this.golesVisitante = golesVisitante;
        this.fecha = fecha;
    }

    // =========================
    // GETTERS
    // =========================

    public Long getId() {
        return id;
    }

    public Equipo getEquipoLocal() {
        return equipoLocal;
    }

    public Equipo getEquipoVisitante() {
        return equipoVisitante;
    }

    public Integer getGolesLocal() {
        return golesLocal;
    }

    public Integer getGolesVisitante() {
        return golesVisitante;
    }

    public LocalDate getFecha() {
        return fecha;
    }

    // =========================
    // SETTERS
    // =========================

    public void setEquipoLocal(Equipo equipoLocal) {
        this.equipoLocal = equipoLocal;
    }

    public void setEquipoVisitante(Equipo equipoVisitante) {
        this.equipoVisitante = equipoVisitante;
    }

    public void setGolesLocal(Integer golesLocal) {
        this.golesLocal = golesLocal;
    }

    public void setGolesVisitante(Integer golesVisitante) {
        this.golesVisitante = golesVisitante;
    }

    public void setFecha(LocalDate fecha) {
        this.fecha = fecha;
    }
}
4.Cliente

Crea una API en Spring Boot para gestionar clientes de un hotel. Debes implementar únicamente la entidad Cliente con Bean Validation y un endpoint POST /clientes que reciba el objeto en JSON y lo devuelva si es válido. El cliente tendrá nombre y apellidos obligatorios con entre 3 y 50 caracteres en el nombre y entre 3 y 80 en los apellidos, email obligatorio con formato válido, teléfono opcional, DNI obligatorio con formato de 9 caracteres, y fecha de nacimiento obligatoria que debe ser una fecha pasada. La validación debe realizarse con @Valid en el controlador y todas las restricciones deben estar definidas en la entidad usando anotaciones de Jakarta Validation.

package es.pulsoft.spring_hotel.controller;

import es.pulsoft.spring_hotel.model.Cliente;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;


// Controlador REST de clientes del hotel
// Se usa para practicar validaciones con Bean Validation

@RestController
@RequestMapping("/clientes")
public class ClienteController {

    @PostMapping
    public ResponseEntity<Cliente> crearCliente(@Valid @RequestBody Cliente cliente) {

        // @Valid activa la validación del modelo Cliente antes de entrar en el método
        // si alguna validación falla, Spring lanza MethodArgumentNotValidException
        // que será gestionada por el ControllerAdvice

        // en esta fase no se persiste en base de datos, solo se devuelve el objeto validado
        return ResponseEntity.ok(cliente);
    }
}
package es.pulsoft.spring_hotel.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

import java.time.LocalDate;

@Entity
@Table(name="cliente")
public class Cliente {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // Nombre del cliente: obligatorio y con longitud controlada para evitar valores inválidos o demasiado cortos/largos
    @NotBlank(message = "El nombre no puede quedar en blanco")
    @Size(min=3, max=50, message = "El nombre tiene que tener entre 3 y 50 caracteres")
    private String nombre;

    // Apellidos del cliente: validación de obligatoriedad y tamaño para asegurar consistencia de datos
    @NotBlank(message = "Los apellidos no pueden quedar en blanco")
    @Size(min=3, max=80, message = "Los apellidos tienen que tener entre 3 y 80 caracteres")
    private String apellidos;

    // Email del cliente: obligatorio y validado con formato estándar de correo electrónico
    @NotBlank(message = "El email no puede quedar en blanco")
    @Email(message = "Formato de email no valido")
    private String email;

    // Teléfono opcional: si se proporciona, debe cumplir formato numérico de 9 dígitos
    @Pattern(regexp = "^[0-9]{9}$", message = "Teléfono no válido")
    private String telefono;

    // DNI obligatorio con validación de formato básico español (8 números + letra)
    @NotBlank(message = "El DNI no puede quedar sin cumplimentar")
    @Pattern(regexp = "^[0-9]{8}[A-Z]$", message = "Formato de DNI no válido")
    private String dni;

    // Fecha de nacimiento obligatoria y debe ser una fecha pasada
    @NotNull(message = "La fecha no puede quedar sin rellenar")
    @Past(message = "La fecha tiene que ser anterior a la fecha actual")
    private LocalDate fechaNacimiento;

    public Cliente() {
        // Constructor vacío requerido por JPA
    }

    public Cliente(String nombre, String apellidos, String email, String telefono, String dni, LocalDate fechaNacimiento) {
        // Constructor utilizado para creación manual de objetos Cliente
        this.nombre = nombre;
        this.apellidos = apellidos;
        this.email = email;
        this.telefono = telefono;
        this.dni = dni;
        this.fechaNacimiento = fechaNacimiento;
    }

    public Long getId() {
        return id;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        // Setter utilizado por frameworks de serialización y JPA
        this.nombre = nombre;
    }

    public String getApellidos() {
        return apellidos;
    }

    public void setApellidos(String apellidos) {
        this.apellidos = apellidos;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getTelefono() {
        return telefono;
    }

    public void setTelefono(String telefono) {
        this.telefono = telefono;
    }

    public String getDni() {
        return dni;
    }

    public void setDni(String dni) {
        this.dni = dni;
    }

    public LocalDate getFechaNacimiento() {
        return fechaNacimiento;
    }

    public void setFechaNacimiento(LocalDate fechaNacimiento) {
        this.fechaNacimiento = fechaNacimiento;
    }
}
5.Habitacion

Crea una API en Spring Boot para gestionar habitaciones de un hotel. Debes implementar únicamente la entidad Habitacion con Bean Validation y un endpoint POST /habitaciones que reciba el objeto en JSON y lo devuelva si es válido. La habitación tendrá un número obligatorio que debe ser único y mayor que 0, un tipo obligatorio con valores como simple, doble o suite, una capacidad obligatoria entre 1 y 6 personas, un precio por noche obligatorio que debe ser mayor que 0, y un campo de disponibilidad que será opcional y por defecto será verdadero. Todas las validaciones deben aplicarse en la entidad mediante anotaciones de Jakarta Validation y el controlador debe usar @Valid para activar la validación automática.

package es.pulsoft.spring_hotel.controller;

import es.pulsoft.spring_hotel.model.Habitacion;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;


//Controlador REST para la gestión de habitaciones del hotel.
 // En esta fase del proyecto se utiliza únicamente para pruebas de validación con Bean Validation.

@RestController
@RequestMapping("/habitaciones")
public class HabitacionController {

    @PostMapping
    public ResponseEntity<Habitacion> crearHabitacion(
            @Valid @RequestBody Habitacion habitacion) {

        // @Valid activa la validación del objeto Habitacion antes de entrar al método
        // si alguna validación falla, Spring lanza MethodArgumentNotValidException
        // que será gestionada por el ControllerAdvice global

        // en esta fase no se guarda en base de datos, solo se devuelve el objeto validado
        return ResponseEntity.ok(habitacion);
    }
}
package es.pulsoft.spring_hotel.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "habitacion")
public class Habitacion {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // Número de habitación: debe ser único en la base de datos y mayor que 0
    @Column(unique = true)
    @Min(value = 1, message = "El numero de habitacion tiene que ser mayor que 0")
    private Integer numero;

    // Tipo de habitación: valor controlado mediante enum para evitar valores inválidos
    @NotNull(message = "El tipo es obligatorio")
    @Enumerated(EnumType.STRING)
    private HabitacionEnum tipo;

    // Precio por noche: obligatorio y con valor mínimo para evitar precios negativos o cero
    @NotNull(message = "El precio es obligatorio")
    @DecimalMin(value = "0.01", message = "El precio debe ser mayor que 0")
    private Double precioNoche;

    // Capacidad de la habitación: número de personas permitido, con límites de negocio
    @NotNull(message = "La capacidad es obligatoria")
    @Min(value = 1, message = "La capacidad minima es de 1 persona")
    @Max(value = 6, message = "La capacidad maxima es de 6 personas")
    private Integer capacidad;

    // Indica si la habitación está disponible para reserva
    private Boolean disponible;

    public Habitacion() {
        // Constructor vacío requerido por JPA
    }

    public Habitacion(Integer numero, HabitacionEnum tipo, Double precioNoche, Integer capacidad) {
        // Constructor para creación manual de habitaciones
        this.numero = numero;
        this.tipo = tipo;
        this.precioNoche = precioNoche;
        this.capacidad = capacidad;
        this.disponible = true;
    }

    public Long getId() {
        return id;
    }

    public Integer getNumero() {
        return numero;
    }

    public void setNumero(Integer numero) {
        // Setter utilizado por frameworks de serialización y JPA
        this.numero = numero;
    }

    public HabitacionEnum getTipo() {
        return tipo;
    }

    public void setTipo(HabitacionEnum tipo) {
        this.tipo = tipo;
    }

    public Double getPrecioNoche() {
        return precioNoche;
    }

    public void setPrecioNoche(Double precioNoche) {
        this.precioNoche = precioNoche;
    }

    public Integer getCapacidad() {
        return capacidad;
    }

    public void setCapacidad(Integer capacidad) {
        // Setter para actualizar capacidad de la habitación
        this.capacidad = capacidad;
    }

    public Boolean getDisponible() {
        return disponible;
    }

    public void setDisponible(Boolean disponible) {
        this.disponible = disponible;
    }
}
6.Reserva

Crea una API en Spring Boot para gestionar reservas de un hotel. Debes implementar únicamente la entidad Reserva y un endpoint POST /reservas que reciba el objeto en JSON y lo devuelva si es válido. La entidad debe servir para practicar Bean Validation, por lo que incluirá un identificador automático, un cliente obligatorio como objeto con validación anidada, una habitación obligatoria también como objeto validado, un número de personas obligatorio que debe estar entre 1 y 6, un precio total obligatorio que debe ser mayor que 0, un estado opcional que será “PENDIENTE” por defecto usando un enum, y dos fechas obligatorias de entrada y salida solo para validar que no sean nulas y que la de entrada no sea anterior a hoy. Todas las validaciones deben implementarse con anotaciones de Jakarta Validation y el controlador debe usar @Valid para activar la validación automática sin lógica adicional.

package es.pulsoft.spring_hotel.controller;

import es.pulsoft.spring_hotel.model.Reserva;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/reservas")
public class ReservaController {

    // Endpoint REST para creación de reservas
    // @Valid activa la validación automática del objeto Reserva
    // antes de entrar en el método del controller
    @PostMapping
    public ResponseEntity<Reserva> crearReserva(
            @Valid @RequestBody Reserva reserva) {

        // En esta fase del proyecto no se persiste en base de datos
        // simplemente se devuelve el objeto validado como respuesta
        return ResponseEntity.ok(reserva);
    }
}
package es.pulsoft.spring_hotel.model;

import jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDate;

public class Reserva {

    // Identificador único de la reserva (gestionado por la base de datos)
    private Long id;

    // Cliente asociado a la reserva, validado en cascada con Bean Validation
    @NotNull(message = "El cliente es obligatorio")
    @Valid
    private Cliente cliente;

    // Habitación asociada a la reserva, también validada en cascada
    @NotNull(message = "La habitacion es obligatoria")
    @Valid
    private Habitacion habitacion;

    // Fecha de entrada: obligatoria y no puede ser anterior a la fecha actual
    @NotNull(message = "Fecha de entrada obligatoria")
    @FutureOrPresent(message = "La fecha de entrada no puede ser anterior a hoy")
    private LocalDate fechaEntrada;

    // Fecha de salida: obligatoria (validación de coherencia entre fechas pendiente si se añade @AssertTrue)
    @NotNull(message = "Fecha de salida obligatoria")
    private LocalDate fechaSalida;

    // Estado de la reserva (por defecto suele ser PENDIENTE)
    private EstadoEnum estado;

    // Precio total de la reserva: obligatorio y con valor mínimo para evitar valores inválidos
    @NotNull(message = "El precio es obligatorio")
    @DecimalMin(value = "0.01", message = "El precio debe de ser mayor que 0")
    private Double total;

    public Reserva() {
        // Constructor vacío requerido por frameworks como Spring y JPA
    }

    public Reserva(Long id, Cliente cliente, Habitacion habitacion, LocalDate fechaEntrada, LocalDate fechaSalida, Double total) {
        // Constructor de conveniencia para creación manual de objetos Reserva
        this.id = id;
        this.cliente = cliente;
        this.habitacion = habitacion;
        this.fechaEntrada = fechaEntrada;
        this.fechaSalida = fechaSalida;
        this.estado = EstadoEnum.PENDIENTE;
        this.total = total;
    }

    public Long getId() {
        return id;
    }

    public Cliente getCliente() {
        return cliente;
    }

    public void setCliente(Cliente cliente) {
        // Setter utilizado por frameworks de serialización y persistencia
        this.cliente = cliente;
    }

    public Habitacion getHabitacion() {
        return habitacion;
    }

    public void setHabitacion(Habitacion habitacion) {
        this.habitacion = habitacion;
    }

    public LocalDate getFechaEntrada() {
        return fechaEntrada;
    }

    public void setFechaEntrada(LocalDate fechaEntrada) {
        this.fechaEntrada = fechaEntrada;
    }

    public LocalDate getFechaSalida() {
        return fechaSalida;
    }

    public void setFechaSalida(LocalDate fechaSalida) {
        this.fechaSalida = fechaSalida;
    }

    public EstadoEnum getEstado() {
        return estado;
    }

    public void setEstado(EstadoEnum estado) {
        this.estado = estado;
    }

    public Double getTotal() {
        return total;
    }

    public void setTotal(Double total) {
        this.total = total;
    }
}