From 64d82921a4827bfc790bdf67f6c392b5df9fa7c9 Mon Sep 17 00:00:00 2001
From: hugcubi <hugo.cubino@estudiantes.uva.es>
Date: Sat, 14 Dec 2024 17:58:18 +0100
Subject: [PATCH] =?UTF-8?q?Separaci=C3=B3n=20microservicio=20user?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .trunk/.gitignore                             |   9 +
 .trunk/configs/.hadolint.yaml                 |   4 +
 .trunk/configs/.markdownlint.yaml             |   2 +
 .trunk/configs/.shellcheckrc                  |   7 +
 .trunk/configs/.yamllint.yaml                 |   7 +
 .trunk/configs/svgo.config.js                 |  14 +
 .trunk/trunk.yaml                             |  40 +++
 java/roomBooking/pom.xml                      |  24 +-
 .../uva/monolith/RoomBookingApplication.java  |   2 +-
 .../uva/monolith/config/SecurityConfig.java   |   6 +-
 .../exceptions/GlobalExceptionHandler.java    |   2 +-
 .../exceptions/HotelNotFoundException.java    |   2 +-
 .../exceptions/InvalidDateRangeException.java |   2 +-
 .../exceptions/InvalidRequestException.java   |   2 +-
 .../filter/JwtAuthenticationFilter.java       | 126 ++++-----
 .../controllers/BookingController.java        |   6 +-
 .../services/bookings/models/Booking.java     |   6 +-
 .../repositories/BookingRepository.java       |   4 +-
 .../bookings/services/BookingService.java     |  16 +-
 .../hotels/controllers/HotelController.java   |  22 +-
 .../services/hotels/models/Address.java       |   2 +-
 .../services/hotels/models/Hotel.java         |   4 +-
 .../monolith/services/hotels/models/Room.java |   4 +-
 .../services/hotels/models/RoomType.java      |   2 +-
 .../hotels/repositories/HotelRepository.java  |   4 +-
 .../hotels/repositories/RoomRepository.java   |   4 +-
 .../users/controllers/UserController.java     |  10 +-
 .../services/users/models/AuthResponse.java   |   2 +-
 .../services/users/models/Client.java         |   4 +-
 .../services/users/models/HotelManager.java   |   4 +-
 .../monolith/services/users/models/User.java  |   3 +-
 .../services/users/models/UserRol.java        |   2 +-
 .../services/users/models/UserStatus.java     |   2 +-
 .../users/repositories/ClientRepository.java  |   4 +-
 .../repositories/HotelManagerRepository.java  |   4 +-
 .../users/repositories/UserRepository.java    |   4 +-
 .../services/users/services/UserService.java  |  59 ++--
 .../src/main/resources/application.properties |   5 +
 java/services/auth/pom.xml                    |   8 +-
 .../com/uva/authentication/api/UserAPI.java   |  70 ++---
 .../config/RestTemplateConfig.java            |  16 +-
 .../controllers/AuthController.java           |   9 +-
 .../interceptor/AuthHttpInterceptor.java      |  48 ++++
 .../authentication/models/LoginRequest.java   |  31 +--
 .../models/RegisterRequest.java               |  26 +-
 .../models/remote/Response.java               |  16 ++
 .../authentication/services/AuthService.java  |  83 ++----
 .../com/uva/authentication/utils/JwtUtil.java |   9 +-
 .../src/main/resources/application.properties |   2 +-
 java/services/users/.gitignore                |  33 +++
 .../.mvn/wrapper/maven-wrapper.properties     |  19 ++
 java/services/users/Dockerfile                |  10 +
 java/services/users/mvnw                      | 259 ++++++++++++++++++
 java/services/users/mvnw.cmd                  | 149 ++++++++++
 java/services/users/pom.xml                   |  77 ++++++
 .../java/com/uva/api/UsersApplication.java    |  12 +
 .../main/java/com/uva/api/apis/HotelApi.java  |   5 +
 .../com/uva/api/config/SecurityConfig.java    |  54 ++++
 .../uva/api/controllers/UserController.java   | 112 ++++++++
 .../exceptions/GlobalExceptionHandler.java    |  50 ++++
 .../exceptions/HotelNotFoundException.java    |  11 +
 .../exceptions/InvalidDateRangeException.java |   7 +
 .../exceptions/InvalidRequestException.java   |  11 +
 .../api/filter/JwtAuthenticationFilter.java   | 116 ++++++++
 .../java/com/uva/api/models/AuthResponse.java |  51 ++++
 .../main/java/com/uva/api/models/Booking.java |  12 +
 .../main/java/com/uva/api/models/Client.java  |  64 +++++
 .../main/java/com/uva/api/models/Hotel.java   |   5 +
 .../java/com/uva/api/models/HotelManager.java |  37 +++
 .../main/java/com/uva/api/models/User.java    |  94 +++++++
 .../main/java/com/uva/api/models/UserRol.java |   5 +
 .../java/com/uva/api/models/UserStatus.java   |   5 +
 .../api/repositories/ClientRepository.java    |  10 +
 .../repositories/HotelManagerRepository.java  |  10 +
 .../uva/api/repositories/UserRepository.java  |  11 +
 .../com/uva/api/services/UserService.java     | 132 +++++++++
 .../src/main/resources/application.properties |  18 ++
 .../RoomBookingApplicationTests.java          |  13 +
 78 files changed, 1823 insertions(+), 313 deletions(-)
 create mode 100644 .trunk/.gitignore
 create mode 100644 .trunk/configs/.hadolint.yaml
 create mode 100644 .trunk/configs/.markdownlint.yaml
 create mode 100644 .trunk/configs/.shellcheckrc
 create mode 100644 .trunk/configs/.yamllint.yaml
 create mode 100644 .trunk/configs/svgo.config.js
 create mode 100644 .trunk/trunk.yaml
 create mode 100644 java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java
 create mode 100644 java/services/auth/src/main/java/com/uva/authentication/models/remote/Response.java
 create mode 100644 java/services/users/.gitignore
 create mode 100644 java/services/users/.mvn/wrapper/maven-wrapper.properties
 create mode 100644 java/services/users/Dockerfile
 create mode 100755 java/services/users/mvnw
 create mode 100644 java/services/users/mvnw.cmd
 create mode 100644 java/services/users/pom.xml
 create mode 100644 java/services/users/src/main/java/com/uva/api/UsersApplication.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/apis/HotelApi.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/controllers/UserController.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/AuthResponse.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/Booking.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/Client.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/Hotel.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/HotelManager.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/User.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/UserRol.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/models/UserStatus.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/repositories/ClientRepository.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/repositories/HotelManagerRepository.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/repositories/UserRepository.java
 create mode 100644 java/services/users/src/main/java/com/uva/api/services/UserService.java
 create mode 100644 java/services/users/src/main/resources/application.properties
 create mode 100644 java/services/users/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java

diff --git a/.trunk/.gitignore b/.trunk/.gitignore
new file mode 100644
index 0000000..15966d0
--- /dev/null
+++ b/.trunk/.gitignore
@@ -0,0 +1,9 @@
+*out
+*logs
+*actions
+*notifications
+*tools
+plugins
+user_trunk.yaml
+user.yaml
+tmp
diff --git a/.trunk/configs/.hadolint.yaml b/.trunk/configs/.hadolint.yaml
new file mode 100644
index 0000000..98bf0cd
--- /dev/null
+++ b/.trunk/configs/.hadolint.yaml
@@ -0,0 +1,4 @@
+# Following source doesn't work in most setups
+ignored:
+  - SC1090
+  - SC1091
diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml
new file mode 100644
index 0000000..b40ee9d
--- /dev/null
+++ b/.trunk/configs/.markdownlint.yaml
@@ -0,0 +1,2 @@
+# Prettier friendly markdownlint config (all formatting rules disabled)
+extends: markdownlint/style/prettier
diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc
new file mode 100644
index 0000000..8c7b1ad
--- /dev/null
+++ b/.trunk/configs/.shellcheckrc
@@ -0,0 +1,7 @@
+enable=all
+source-path=SCRIPTDIR
+disable=SC2154
+
+# If you're having issues with shellcheck following source, disable the errors via:
+# disable=SC1090
+# disable=SC1091
diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml
new file mode 100644
index 0000000..184e251
--- /dev/null
+++ b/.trunk/configs/.yamllint.yaml
@@ -0,0 +1,7 @@
+rules:
+  quoted-strings:
+    required: only-when-needed
+    extra-allowed: ["{|}"]
+  key-duplicates: {}
+  octal-values:
+    forbid-implicit-octal: true
diff --git a/.trunk/configs/svgo.config.js b/.trunk/configs/svgo.config.js
new file mode 100644
index 0000000..b257d13
--- /dev/null
+++ b/.trunk/configs/svgo.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+  plugins: [
+    {
+      name: "preset-default",
+      params: {
+        overrides: {
+          removeViewBox: false, // https://github.com/svg/svgo/issues/1128
+          sortAttrs: true,
+          removeOffCanvasPaths: true,
+        },
+      },
+    },
+  ],
+};
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
new file mode 100644
index 0000000..06a7294
--- /dev/null
+++ b/.trunk/trunk.yaml
@@ -0,0 +1,40 @@
+# This file controls the behavior of Trunk: https://docs.trunk.io/cli
+# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
+version: 0.1
+cli:
+  version: 1.22.8
+# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
+plugins:
+  sources:
+    - id: trunk
+      ref: v1.6.6
+      uri: https://github.com/trunk-io/plugins
+# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
+runtimes:
+  enabled:
+    - go@1.21.0
+    - node@18.20.5
+    - python@3.10.8
+# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
+lint:
+  disabled:
+    - git-diff-check
+  enabled:
+    - checkov@3.2.334
+    - dotenv-linter@3.3.0
+    - hadolint@2.12.1-beta
+    - markdownlint@0.43.0
+    - osv-scanner@1.9.1
+    - prettier@3.4.2
+    - shellcheck@0.10.0
+    - shfmt@3.6.0
+    - svgo@3.3.2
+    - trufflehog@3.86.1
+    - yamllint@1.35.1
+actions:
+  disabled:
+    - trunk-announce
+    - trunk-check-pre-push
+    - trunk-fmt-pre-commit
+  enabled:
+    - trunk-upgrade-available
diff --git a/java/roomBooking/pom.xml b/java/roomBooking/pom.xml
index f6fa75b..30de244 100644
--- a/java/roomBooking/pom.xml
+++ b/java/roomBooking/pom.xml
@@ -54,26 +54,14 @@
     <artifactId>spring-boot-starter-security</artifactId>
 	</dependency>
 	<dependency>
-		<groupId>io.jsonwebtoken</groupId>
-		<artifactId>jjwt-api</artifactId>
-		<version>0.11.5</version>
+		<groupId>com.auth0</groupId>
+		<artifactId>java-jwt</artifactId>
+		<version>4.4.0</version>
 	</dependency>
 	<dependency>
-		<groupId>io.jsonwebtoken</groupId>
-		<artifactId>jjwt-impl</artifactId>
-		<version>0.11.5</version>
-		<scope>runtime</scope>
-	</dependency>
-	<dependency>
-		<groupId>io.jsonwebtoken</groupId>
-		<artifactId>jjwt-jackson</artifactId>
-		<version>0.11.5</version>
-		<scope>runtime</scope>
-	</dependency>
-	<dependency>
-        <groupId>jakarta.servlet</groupId>
-        <artifactId>jakarta.servlet-api</artifactId>
-        <scope>provided</scope>
+		<groupId>jakarta.servlet</groupId>
+		<artifactId>jakarta.servlet-api</artifactId>
+		<scope>provided</scope>
     </dependency>
 	</dependencies>
 
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java b/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java
index 0a5db24..732bfa2 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java
@@ -1,4 +1,4 @@
-package com.uva.monolith;
+package com.uva.api;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java b/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java
index 8264f84..914469e 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.config;
+package com.uva.api.config;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -8,8 +8,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
-import com.uva.monolith.filter.JwtAuthenticationFilter;
-import com.uva.monolith.services.users.models.UserRol;
+import com.uva.api.filter.JwtAuthenticationFilter;
+import com.uva.api.services.users.models.UserRol;
 
 @Configuration
 @EnableWebSecurity
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java
index 9428c51..5681a60 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.exceptions;
+package com.uva.api.exceptions;
 
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java
index 129a0b1..dc466f6 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.exceptions;
+package com.uva.api.exceptions;
 
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.ResponseStatus;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java
index 5fea986..58bf97d 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.exceptions;
+package com.uva.api.exceptions;
 
 public class InvalidDateRangeException extends RuntimeException {
     public InvalidDateRangeException(String message) {
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java
index ca09e05..677cc4b 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.exceptions;
+package com.uva.api.exceptions;
 
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.ResponseStatus;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java b/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java
index 9b31d1a..8833440 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java
@@ -1,21 +1,19 @@
-package com.uva.monolith.filter;
+package com.uva.api.filter;
 
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.ExpiredJwtException;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.MalformedJwtException;
-import io.jsonwebtoken.UnsupportedJwtException;
-import io.jsonwebtoken.io.Decoders;
-import io.jsonwebtoken.security.Keys;
-import io.jsonwebtoken.security.SignatureException;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.exceptions.JWTVerificationException;
 
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.stereotype.Component;
 
-import com.uva.monolith.services.users.models.UserRol;
+import com.uva.api.services.users.models.UserRol;
 
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
@@ -23,71 +21,56 @@ import jakarta.servlet.ServletRequest;
 import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.Filter;
+
 import java.io.IOException;
-import java.security.Key;
 import java.time.LocalDateTime;
 import java.util.Collections;
-import java.util.Date;
 
 @Component
 public class JwtAuthenticationFilter implements Filter {
 
-    private final String SECRET_KEY = "3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7567c272e007b";
+    @Value("${security.jwt.secret-key}")
+    private String secretKey;
+
+    @Value("${security.jwt.kid}")
+    private String kid;
 
-    private Key getSignInKey() {
-        byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
-        return Keys.hmacShaKeyFor(keyBytes);
+    @Value("${security.jwt.expiration-time}")
+    private long jwtExpiration;
+
+    private Algorithm getSigningAlgorithm() {
+        return Algorithm.HMAC256(secretKey); // Usar HMAC256 con la clave secreta
     }
 
     private String getTokenFromRequest(HttpServletRequest request) {
         String authHeader = request.getHeader("Authorization");
-        if (authHeader == null || !authHeader.startsWith("Bearer "))
+        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
             return null;
+        }
         return authHeader.substring(7);
     }
 
-    private Claims getClaimsFromToken(String token) {
-        return Jwts.parserBuilder()
-                .setSigningKey(getSignInKey())
-                .build()
-                .parseClaimsJws(token)
-                .getBody();
-    }
-
-    private boolean validateToken(String token) {
-        if (token == null)
-            return false;// no token
+    private DecodedJWT validateAndDecodeToken(String token) {
         try {
-            // Verifica y analiza el token
-            Claims claims = getClaimsFromToken(token);
-
-            // Verifica que el token no esté expirado
-            return claims.getExpiration().after(new Date());
-        } catch (ExpiredJwtException e) {
-            System.out.println("[" + LocalDateTime.now().toString() + "] Token expirado: " + e.getMessage());
-        } catch (UnsupportedJwtException e) {
-            System.out.println("[" + LocalDateTime.now().toString() + "] Token no soportado: " + e.getMessage());
-        } catch (MalformedJwtException e) {
-            System.out.println("[" + LocalDateTime.now().toString() + "] Token malformado: " + e.getMessage());
-        } catch (SignatureException e) {
-            System.out.println("[" + LocalDateTime.now().toString() + "] Firma inválida: " + e.getMessage());
-        } catch (IllegalArgumentException e) {
-            System.out.println("[" + LocalDateTime.now().toString() + "] Token vacío o nulo: " + e.getMessage());
+            JWTVerifier verifier = JWT.require(getSigningAlgorithm()).build();
+            return verifier.verify(token); // Verifica y decodifica el token
+        } catch (JWTVerificationException ex) {
+            System.out.println(
+                    "[" + LocalDateTime.now().toString() + "] Error de verificación del token: " + ex.getMessage());
+            return null;
         }
-        return false; // Si ocurre cualquier excepción, el token es inválido
-
     }
 
-    private String getEmailFromToken(String token) {
-        return getClaimsFromToken(token).getSubject();
+    private String getEmailFromToken(DecodedJWT jwt) {
+        return jwt.getClaim("email").asString();
     }
 
-    private UserRol getRoleFromToken(String token) {
-        String rol = getClaimsFromToken(token).get("rol", String.class);
-        return UserRol.valueOf(rol);
+    private UserRol getRoleFromToken(DecodedJWT jwt) {
+        String role = jwt.getClaim("rol").asString();
+        return UserRol.valueOf(role);
     }
 
-    public static String getRol(UserRol rol) {
+    private String formatRole(UserRol rol) {
         return String.format("ROLE_%s", rol.toString());
     }
 
@@ -97,28 +80,37 @@ public class JwtAuthenticationFilter implements Filter {
         HttpServletRequest httpRequest = (HttpServletRequest) request;
         String token = getTokenFromRequest(httpRequest);
 
-        System.out.println("[" + LocalDateTime.now().toString() + "] TOKEN " + token);
+        boolean aproved = false;
 
-        if (validateToken(token)) {
+        System.out.print("[" + LocalDateTime.now().toString() + "] TOKEN: " + token);
 
-            String email = getEmailFromToken(token);
-            UserRol role = getRoleFromToken(token); // Extraer el rol del token
+        if (token != null) {
+            DecodedJWT jwt = validateAndDecodeToken(token);
+            System.out.print(" " + jwt.toString() + " ");
+            if (jwt != null) {
+                String email = getEmailFromToken(jwt);
+                UserRol role = getRoleFromToken(jwt);
+                System.out.print(" email=" + email + " role=" + role + " ");
 
-            if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) {
-                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
-                        email, null, null);
-                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
-                SecurityContextHolder.getContext().setAuthentication(authentication);
-            }
+                if (email != null && role != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+                    // Crear la autoridad con el rol del token
+                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(formatRole(role));
+
+                    // Crear autenticación
+                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email,
+                            null, Collections.singletonList(authority));
+                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
 
-            // Agregar el rol como autoridad
-            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(getRol(role));
-            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email, null,
-                    Collections.singletonList(authority));
-            SecurityContextHolder.getContext().setAuthentication(authentication);
+                    // Establecer autenticación en el contexto de seguridad
+                    SecurityContextHolder.getContext().setAuthentication(authentication);
+                    aproved = true;
+                }
+            }
         }
 
+        System.out.println(" APROVED: " + aproved);
+
+        // Continuar con el resto de filtros
         chain.doFilter(request, response);
     }
-
 }
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java
index 602f16a..b454a36 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java
@@ -1,7 +1,7 @@
-package com.uva.monolith.services.bookings.controllers;
+package com.uva.api.services.bookings.controllers;
 
-import com.uva.monolith.services.bookings.models.Booking;
-import com.uva.monolith.services.bookings.services.BookingService;
+import com.uva.api.services.bookings.models.Booking;
+import com.uva.api.services.bookings.services.BookingService;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java
index 533ee0c..a1df84f 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.bookings.models;
+package com.uva.api.services.bookings.models;
 
 import jakarta.persistence.Basic;
 import jakarta.persistence.CascadeType;
@@ -13,8 +13,8 @@ import jakarta.persistence.ManyToOne;
 import jakarta.persistence.Table;
 import java.time.LocalDate;
 
-import com.uva.monolith.services.hotels.models.Room;
-import com.uva.monolith.services.users.models.Client;
+import com.uva.api.services.hotels.models.Room;
+import com.uva.api.services.users.models.Client;
 
 @Entity
 @Table(name = "bookings")
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java
index b5ace65..50af181 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java
@@ -1,5 +1,5 @@
 // BookingRepository.java
-package com.uva.monolith.services.bookings.repositories;
+package com.uva.api.services.bookings.repositories;
 
 import jakarta.transaction.Transactional;
 
@@ -11,7 +11,7 @@ import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 
-import com.uva.monolith.services.bookings.models.Booking;
+import com.uva.api.services.bookings.models.Booking;
 
 public interface BookingRepository extends JpaRepository<Booking, Integer> {
         @Query("SELECT b FROM Booking b WHERE b.userId.id = ?1")
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java
index 176f49d..2554413 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java
@@ -1,11 +1,11 @@
-package com.uva.monolith.services.bookings.services;
-
-import com.uva.monolith.services.bookings.models.Booking;
-import com.uva.monolith.services.bookings.repositories.BookingRepository;
-import com.uva.monolith.services.hotels.models.Room;
-import com.uva.monolith.services.hotels.repositories.RoomRepository;
-import com.uva.monolith.services.users.models.Client;
-import com.uva.monolith.services.users.repositories.ClientRepository;
+package com.uva.api.services.bookings.services;
+
+import com.uva.api.services.bookings.models.Booking;
+import com.uva.api.services.bookings.repositories.BookingRepository;
+import com.uva.api.services.hotels.models.Room;
+import com.uva.api.services.hotels.repositories.RoomRepository;
+import com.uva.api.services.users.models.Client;
+import com.uva.api.services.users.repositories.ClientRepository;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java
index 781cb62..3321df6 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.hotels.controllers;
+package com.uva.api.services.hotels.controllers;
 
 import java.util.List;
 import java.util.Map;
@@ -11,16 +11,16 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
-import com.uva.monolith.exceptions.HotelNotFoundException;
-import com.uva.monolith.exceptions.InvalidDateRangeException;
-import com.uva.monolith.exceptions.InvalidRequestException;
-import com.uva.monolith.services.bookings.repositories.BookingRepository;
-import com.uva.monolith.services.hotels.models.Hotel;
-import com.uva.monolith.services.hotels.models.Room;
-import com.uva.monolith.services.hotels.repositories.HotelRepository;
-import com.uva.monolith.services.hotels.repositories.RoomRepository;
-import com.uva.monolith.services.users.models.HotelManager;
-import com.uva.monolith.services.users.repositories.HotelManagerRepository;
+import com.uva.api.exceptions.HotelNotFoundException;
+import com.uva.api.exceptions.InvalidDateRangeException;
+import com.uva.api.exceptions.InvalidRequestException;
+import com.uva.api.services.bookings.repositories.BookingRepository;
+import com.uva.api.services.hotels.models.Hotel;
+import com.uva.api.services.hotels.models.Room;
+import com.uva.api.services.hotels.repositories.HotelRepository;
+import com.uva.api.services.hotels.repositories.RoomRepository;
+import com.uva.api.services.users.models.HotelManager;
+import com.uva.api.services.users.repositories.HotelManagerRepository;
 
 @RestController
 @RequestMapping("hotels")
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java
index 5f31a2a..61fbf60 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.hotels.models;
+package com.uva.api.services.hotels.models;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java
index 21f5cec..71e9c04 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java
@@ -1,8 +1,8 @@
-package com.uva.monolith.services.hotels.models;
+package com.uva.api.services.hotels.models;
 
 import java.util.List;
 
-import com.uva.monolith.services.users.models.HotelManager;
+import com.uva.api.services.users.models.HotelManager;
 
 import jakarta.persistence.Basic;
 import jakarta.persistence.CascadeType;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java
index 72a6a72..368e6d7 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java
@@ -1,9 +1,9 @@
-package com.uva.monolith.services.hotels.models;
+package com.uva.api.services.hotels.models;
 
 import java.util.List;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.uva.monolith.services.bookings.models.Booking;
+import com.uva.api.services.bookings.models.Booking;
 
 import jakarta.persistence.Basic;
 import jakarta.persistence.CascadeType;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java
index b9e8258..7f85313 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.hotels.models;
+package com.uva.api.services.hotels.models;
 
 public enum RoomType {
     SINGLE,
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java
index eddb664..320adb8 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java
@@ -1,11 +1,11 @@
-package com.uva.monolith.services.hotels.repositories;
+package com.uva.api.services.hotels.repositories;
 
 import java.util.List;
 
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
 
-import com.uva.monolith.services.hotels.models.Hotel;
+import com.uva.api.services.hotels.models.Hotel;
 
 public interface HotelRepository extends JpaRepository<Hotel, Integer> {
     @Query("SELECT h FROM Hotel h WHERE h.hotelManager.id = ?1")
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java
index 15cc3d3..1f2a3fd 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java
@@ -1,9 +1,9 @@
-package com.uva.monolith.services.hotels.repositories;
+package com.uva.api.services.hotels.repositories;
 
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
 
-import com.uva.monolith.services.hotels.models.Room;
+import com.uva.api.services.hotels.models.Room;
 
 import java.time.LocalDate;
 import java.util.List;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java
index 070fb57..ab6a975 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.users.controllers;
+package com.uva.api.services.users.controllers;
 
 import java.util.List;
 import java.util.Map;
@@ -19,9 +19,9 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.client.HttpClientErrorException;
 
-import com.uva.monolith.services.users.models.User;
-import com.uva.monolith.services.users.models.UserStatus;
-import com.uva.monolith.services.users.services.UserService;
+import com.uva.api.services.users.models.User;
+import com.uva.api.services.users.models.UserStatus;
+import com.uva.api.services.users.services.UserService;
 
 @RestController
 @RequestMapping("users")
@@ -50,7 +50,7 @@ public class UserController {
 
   @PostMapping
   public ResponseEntity<?> addUser(@RequestBody User user) {
-    userService.addUser(user);
+    userService.registerNewUser(user);
     return new ResponseEntity<>(HttpStatus.ACCEPTED);
   }
 
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java
index 8f33481..9a4564d 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.users.models;
+package com.uva.api.services.users.models;
 
 public class AuthResponse {
 
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java
index e106ecd..1814b0b 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java
@@ -1,11 +1,11 @@
-package com.uva.monolith.services.users.models;
+package com.uva.api.services.users.models;
 
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.List;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.uva.monolith.services.bookings.models.Booking;
+import com.uva.api.services.bookings.models.Booking;
 
 import jakarta.persistence.Basic;
 import jakarta.persistence.CascadeType;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java
index 0e6f4b0..e185fa5 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java
@@ -1,9 +1,9 @@
-package com.uva.monolith.services.users.models;
+package com.uva.api.services.users.models;
 
 import java.util.ArrayList;
 import java.util.List;
 import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.uva.monolith.services.hotels.models.Hotel;
+import com.uva.api.services.hotels.models.Hotel;
 import jakarta.persistence.CascadeType;
 import jakarta.persistence.Entity;
 import jakarta.persistence.FetchType;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java
index 45decd6..e6262ce 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.users.models;
+package com.uva.api.services.users.models;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
@@ -33,7 +33,6 @@ public class User {
   @Column(nullable = false, unique = true)
   private String email;
 
-  @JsonIgnore
   @Basic(optional = false)
   @Column(nullable = false)
   private String password;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java
index f408ba5..0a02cb6 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.users.models;
+package com.uva.api.services.users.models;
 
 public enum UserRol {
   ADMIN, HOTEL_ADMIN, CLIENT
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java
index 362b868..883dc43 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.users.models;
+package com.uva.api.services.users.models;
 
 public enum UserStatus {
   NO_BOOKINGS, WITH_ACTIVE_BOOKINGS, WITH_INACTIVE_BOOKINGS;
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java
index 1c1b46f..7b56de6 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java
@@ -1,9 +1,9 @@
-package com.uva.monolith.services.users.repositories;
+package com.uva.api.services.users.repositories;
 
 import java.util.Optional;
 import org.springframework.data.jpa.repository.JpaRepository;
 
-import com.uva.monolith.services.users.models.Client;
+import com.uva.api.services.users.models.Client;
 
 public interface ClientRepository extends JpaRepository<Client, Integer> {
   Optional<Client> findByEmail(String email);
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java
index 092a251..5a6f077 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java
@@ -1,9 +1,9 @@
-package com.uva.monolith.services.users.repositories;
+package com.uva.api.services.users.repositories;
 
 import java.util.Optional;
 import org.springframework.data.jpa.repository.JpaRepository;
 
-import com.uva.monolith.services.users.models.HotelManager;
+import com.uva.api.services.users.models.HotelManager;
 
 public interface HotelManagerRepository extends JpaRepository<HotelManager, Integer> {
   Optional<HotelManager> findByEmail(String email);
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java
index e5b44c9..fa55563 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java
@@ -1,10 +1,10 @@
-package com.uva.monolith.services.users.repositories;
+package com.uva.api.services.users.repositories;
 
 import java.util.Optional;
 
 import org.springframework.data.jpa.repository.JpaRepository;
 
-import com.uva.monolith.services.users.models.User;
+import com.uva.api.services.users.models.User;
 
 public interface UserRepository extends JpaRepository<User, Integer> {
   Optional<User> findByEmail(String email);
diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java
index 193d736..b81c35c 100644
--- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java
+++ b/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java
@@ -1,4 +1,4 @@
-package com.uva.monolith.services.users.services;
+package com.uva.api.services.users.services;
 
 import java.time.LocalDate;
 import java.util.List;
@@ -10,12 +10,15 @@ import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.HttpClientErrorException;
 
-import com.uva.monolith.services.users.models.AuthResponse;
-import com.uva.monolith.services.users.models.Client;
-import com.uva.monolith.services.users.models.User;
-import com.uva.monolith.services.users.models.UserStatus;
-import com.uva.monolith.services.users.repositories.ClientRepository;
-import com.uva.monolith.services.users.repositories.UserRepository;
+import com.uva.api.services.users.models.AuthResponse;
+import com.uva.api.services.users.models.Client;
+import com.uva.api.services.users.models.HotelManager;
+import com.uva.api.services.users.models.User;
+import com.uva.api.services.users.models.UserRol;
+import com.uva.api.services.users.models.UserStatus;
+import com.uva.api.services.users.repositories.ClientRepository;
+import com.uva.api.services.users.repositories.HotelManagerRepository;
+import com.uva.api.services.users.repositories.UserRepository;
 
 @Service
 public class UserService {
@@ -26,6 +29,9 @@ public class UserService {
   @Autowired
   private ClientRepository clientRepository;
 
+  @Autowired
+  private HotelManagerRepository hotelManagerRepository;
+
   public List<User> getAllUsers() {
     return userRepository.findAll();
   }
@@ -42,19 +48,38 @@ public class UserService {
     User u = assertUser(userRepository.findByEmail(email));
     AuthResponse auth = new AuthResponse();
     BeanUtils.copyProperties(u, auth);
-    auth.setUsername(u.getName());
     return auth;
   }
 
-  public User addUser(User user) {
-    // Actualmente está en el servicio AUTH
-    // TODO adaptar adecuadamente
-    throw new HttpClientErrorException(HttpStatus.MOVED_PERMANENTLY, "servicio actual en http://localhost:8101/login");
-    // user.setStatus(UserStatus.NO_BOOKINGS);
-    // if (user.getRol() == null) // Rol por defecto
-    // user.setRol(UserRol.CONSUMER);
-    // // Guardamos
-    // return userRepository.save(user);
+  public User registerNewUser(User registerRequest) {
+    User newUser;
+
+    // Aseguramos que tenga un rol, por defecto es cliente
+    if (registerRequest.getRol() == null)
+      registerRequest.setRol(UserRol.CLIENT);
+
+    switch (registerRequest.getRol()) {
+      case HOTEL_ADMIN:
+        HotelManager hm = new HotelManager();
+        BeanUtils.copyProperties(registerRequest, hm);
+        newUser = hotelManagerRepository.save(hm);
+        break;
+
+      case ADMIN:
+        User admin = new User();
+        BeanUtils.copyProperties(registerRequest, admin);
+        newUser = admin; // userAPI.save(admin);
+        break;
+
+      case CLIENT: // Por defecto cliente normal
+      default:
+        Client client = new Client();
+        BeanUtils.copyProperties(registerRequest, client);
+        client.setRol(UserRol.CLIENT);
+        newUser = clientRepository.save(client);
+        break;
+    }
+    return newUser;
   }
 
   public User updateUserData(int id, String name, String email) {
diff --git a/java/roomBooking/src/main/resources/application.properties b/java/roomBooking/src/main/resources/application.properties
index 89ab253..e9b75a3 100644
--- a/java/roomBooking/src/main/resources/application.properties
+++ b/java/roomBooking/src/main/resources/application.properties
@@ -11,3 +11,8 @@ spring.security.user.password=admin
 # Usar esto para alternar entre las exposición del room repository ya que no es necesario su uso pero por defecto, al no cubrir su ruta, se expone
 # spring.data.rest.base-path=false
 external.services.auth.host=localhost:8101
+
+security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot
+# 1h in millisecond
+security.jwt.expiration-time=3600000
+security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui
\ No newline at end of file
diff --git a/java/services/auth/pom.xml b/java/services/auth/pom.xml
index a4c0d81..9ebd095 100644
--- a/java/services/auth/pom.xml
+++ b/java/services/auth/pom.xml
@@ -38,7 +38,6 @@
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-data-rest</artifactId>
 		</dependency>
-
 		<dependency>
 			<groupId>com.mysql</groupId>
 			<artifactId>mysql-connector-j</artifactId>
@@ -60,6 +59,13 @@
 			<artifactId>java-jwt</artifactId>
 			<version>4.4.0</version>
 		</dependency>
+		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<version>1.18.36</version>
+			<scope>provided</scope>
+		</dependency>
 	</dependencies>
 
 	<build>
diff --git a/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java b/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java
index 4216aad..2714036 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java
@@ -1,9 +1,7 @@
-// TODO eliminar si realmente no necesitamos comunicar un servicio con otro
 package com.uva.authentication.api;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
@@ -11,9 +9,8 @@ import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.RestTemplate;
 
 import com.uva.authentication.models.RegisterRequest;
+import com.uva.authentication.models.remote.Response;
 import com.uva.authentication.models.remote.User;
-import com.uva.authentication.models.remote.UserRol;
-import com.uva.authentication.utils.JwtUtil;
 
 @Component
 public class UserAPI {
@@ -21,34 +18,32 @@ public class UserAPI {
   @Autowired
   private RestTemplate restTemplate;
 
-  @Value("${external.services.users.baseurl}")
+  @Value("${external.services.users.url}")
   private String USER_API_URL;
 
-  @Autowired
-  private JwtUtil jwtUtil;
-
-  private String token;
-  private final User USER = new User(-1, "admin", null, null, UserRol.ADMIN);
-
-  private String getAccessToken() {
-    if (token == null || token.isEmpty()) {
-      token = jwtUtil.generateToken(USER);
-    }
-    return token;
+  /**
+   * Check if the email it's already register
+   *
+   * @param email
+   * @return email is register
+   * @throws HttpClientErrorException
+   */
+  public boolean isEmailInUse(String email) {
+    return getUserByEmail(email) != null;
   }
 
-  public User getUserByEmail(String email) {
-
-    // Implementación para acceder con autentificación
-    // String token = getAccessToken();
-    // HttpHeaders headers = new HttpHeaders();
-    // headers.set("Authorization", "Bearer " + token);
-    // HttpEntity<Void> entity = new HttpEntity<>(headers);
-
-    String url = USER_API_URL + "?email={" + email + "}";
+  /**
+   * Get the user by email
+   *
+   * @param email
+   * @return User or null if not exists
+   * @throws HttpClientErrorException
+   */
+  public Response getUserByEmail(String email) {
+    String url = USER_API_URL + "?email={email}";
+    System.err.println(url);
     try {
-      ResponseEntity<User> userResponse = restTemplate.getForEntity(url, User.class, email);
-      // restTemplate.exchange(url, HttpMethod.GET, entity, User.class);
+      ResponseEntity<Response> userResponse = restTemplate.getForEntity(url, Response.class, email);
       return userResponse.getBody();
     } catch (HttpClientErrorException e) {
       if (e.getStatusCode() != HttpStatus.NOT_FOUND)
@@ -57,16 +52,21 @@ public class UserAPI {
     }
   }
 
+  /**
+   * Register the user if isn't register yet
+   *
+   * @param registerRequest
+   * @return register result
+   * @throws HttpClientErrorException
+   * @throws HttpClientErrorException
+   */
   public User registerUser(RegisterRequest registerRequest) {
-
-    String token = getAccessToken();
-    HttpHeaders headers = new HttpHeaders();
-    headers.set("Authorization", "Bearer " + token);
-
     String url = USER_API_URL;
-    ResponseEntity<User> userResponse = restTemplate.postForEntity(url, registerRequest, User.class, headers);
-    if (!userResponse.getStatusCode().is2xxSuccessful())
-      throw new RuntimeException("Failed to register user");
+    ResponseEntity<User> userResponse = restTemplate.postForEntity(url, registerRequest, User.class);
+    if (!userResponse.getStatusCode().is2xxSuccessful()) {
+      String errorMessage = "Failed to register user: " + userResponse.getStatusCode() + ". " + userResponse.getBody();
+      throw new HttpClientErrorException(userResponse.getStatusCode(), errorMessage);
+    }
 
     return userResponse.getBody();
   }
diff --git a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java
index ad00e74..9ab4fa4 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java
@@ -1,14 +1,26 @@
 package com.uva.authentication.config;
 
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.client.RestTemplate;
 
+import com.uva.authentication.interceptor.AuthHttpInterceptor;
+
 @Configuration
 public class RestTemplateConfig {
 
+    @Autowired
+    private AuthHttpInterceptor jwtInterceptor;
+
     @Bean
-    public RestTemplate restTemplate() {
-        return new RestTemplate();
+    RestTemplate restTemplate() {
+        RestTemplate restTemplate = new RestTemplate();
+        restTemplate.setInterceptors(List.of(jwtInterceptor));
+
+        return restTemplate;
+
     }
 }
diff --git a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java
index 23625a6..b8bba9b 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java
@@ -5,6 +5,7 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.client.HttpClientErrorException;
+
 import com.uva.authentication.models.*;
 import com.uva.authentication.services.AuthService;
 
@@ -31,12 +32,8 @@ public class AuthController {
     @PostMapping("/register")
     public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
         try {
-            LoginRequest loginRequest = new LoginRequest();
-            loginRequest.setEmail(registerRequest.getEmail());
-            loginRequest.setPassword(registerRequest.getPassword());
-
-            authService.register(registerRequest);
-            return login(loginRequest);
+            String token = authService.register(registerRequest);
+            return ResponseEntity.ok(new JwtAuthResponse(token));
         } catch (HttpClientErrorException e) {
             if (e.getStatusCode() == HttpStatus.CONFLICT) {
                 return new ResponseEntity<String>(e.getMessage(), HttpStatus.CONFLICT);
diff --git a/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java b/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java
new file mode 100644
index 0000000..094cb5a
--- /dev/null
+++ b/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java
@@ -0,0 +1,48 @@
+package com.uva.authentication.interceptor;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.stereotype.Component;
+
+import com.uva.authentication.models.remote.Response;
+import com.uva.authentication.models.remote.UserRol;
+import com.uva.authentication.utils.JwtUtil;
+
+import java.io.IOException;
+
+@Component
+public class AuthHttpInterceptor implements ClientHttpRequestInterceptor {
+
+  @Autowired
+  private JwtUtil jwtUtil;
+
+  private String token;
+  private final Response USER = new Response(-1, "auth", "auth@dev.com", null, UserRol.ADMIN);
+
+  private String getAccessToken() {
+    if (token == null || token.isEmpty()) {
+      // TODO cambiar también si el token ha caducado
+      token = jwtUtil.generateToken(USER);
+    }
+    return token;
+
+  }
+
+  @Override
+  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
+      throws IOException {
+    // Generar o cargar el JWT token desde el bean JwtUtil
+    String jwtToken = getAccessToken();
+
+    // System.out.println("Using token " + jwtToken);
+
+    // Agregar el token al encabezado Authorization
+    request.getHeaders().add("Authorization", "Bearer " + jwtToken);
+
+    // Continuar con la ejecución de la solicitud
+    return execution.execute(request, body);
+  }
+}
diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/LoginRequest.java b/java/services/auth/src/main/java/com/uva/authentication/models/LoginRequest.java
index d829de9..0aba769 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/models/LoginRequest.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/models/LoginRequest.java
@@ -1,26 +1,15 @@
 package com.uva.authentication.models;
 
-import java.util.Objects;
-
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@ToString
+@EqualsAndHashCode
+@NoArgsConstructor
 public class LoginRequest {
   private String email;
-  private String password = String.valueOf(Objects.hashCode("hi"));
-
-  // Getters and setters
-  public String getEmail() {
-    return email;
-  }
-
-  public void setEmail(String email) {
-    this.email = email;
-  }
-
-  public String getPassword() {
-    return password;
-  }
-
-  public void setPassword(String password) {
-    this.password = password;
-  }
-
+  private String password;
 }
diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/RegisterRequest.java b/java/services/auth/src/main/java/com/uva/authentication/models/RegisterRequest.java
index b3c98e8..648abd1 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/models/RegisterRequest.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/models/RegisterRequest.java
@@ -2,24 +2,16 @@ package com.uva.authentication.models;
 
 import com.uva.authentication.models.remote.UserRol;
 
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@ToString
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
 public class RegisterRequest extends LoginRequest {
   private UserRol rol;
   private String name;
-
-  public UserRol getRol() {
-    return this.rol;
-  }
-
-  public void setRol(UserRol rol) {
-    this.rol = rol;
-  }
-
-  public String getName() {
-    return this.name;
-  }
-
-  public void setName(String name) {
-    this.name = name;
-  }
-
 }
diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/remote/Response.java b/java/services/auth/src/main/java/com/uva/authentication/models/remote/Response.java
new file mode 100644
index 0000000..566a210
--- /dev/null
+++ b/java/services/auth/src/main/java/com/uva/authentication/models/remote/Response.java
@@ -0,0 +1,16 @@
+package com.uva.authentication.models.remote;
+
+import com.uva.authentication.models.RegisterRequest;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class Response extends RegisterRequest {
+  private int id;
+}
diff --git a/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java b/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java
index 3ed67ec..bccba7f 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java
@@ -1,22 +1,16 @@
 package com.uva.authentication.services;
 
-import java.util.Optional;
-
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.HttpClientErrorException;
 
+import com.uva.authentication.api.UserAPI;
 import com.uva.authentication.models.LoginRequest;
 import com.uva.authentication.models.RegisterRequest;
-import com.uva.authentication.models.remote.Client;
-import com.uva.authentication.models.remote.HotelManager;
+import com.uva.authentication.models.remote.Response;
 import com.uva.authentication.models.remote.User;
-import com.uva.authentication.models.remote.UserRol;
-import com.uva.authentication.repositories.ClientRepository;
-import com.uva.authentication.repositories.HotelManagerRepository;
-import com.uva.authentication.repositories.UserRepository;
 import com.uva.authentication.utils.JwtUtil;
 import com.uva.authentication.utils.SecurityUtils;
 
@@ -27,24 +21,26 @@ public class AuthService {
   private JwtUtil jwtUtil;
 
   @Autowired
-  private HotelManagerRepository hotelManagerRepository;
-
-  @Autowired
-  private ClientRepository clientRepository;
+  private UserAPI userAPI;
 
-  @Autowired
-  private UserRepository userRepository;
-
-  private boolean authenticateUser(LoginRequest request, User user) {
-    if (user == null)
-      return false;
-    return SecurityUtils.checkPassword(request.getPassword(), user.getPassword());
+  private boolean authenticateUser(LoginRequest request, Response user) {
+    System.err.println(user.getPassword() + " " + request.getPassword());
+    return (user != null)
+        ? SecurityUtils.checkPassword(request.getPassword(), user.getPassword())
+        : false;
   }
 
+  /**
+   * Log the user
+   * 
+   * @param loginRequest
+   * @return token for identify the user
+   * @throws HttpClientErrorException(FORBIDDEN) if the credentials are invalid
+   */
   public String login(LoginRequest loginRequest) {
-    User user = userRepository.findByEmail(loginRequest.getEmail())
-        .orElseThrow(() -> new HttpClientErrorException(HttpStatus.FORBIDDEN,
-            "Invalid credentials"));
+    Response user = userAPI.getUserByEmail(loginRequest.getEmail());
+    System.err.println(user.getName() + ", " + user.getEmail() + ", " +
+        user.getRol() + ", " + user.getPassword());
 
     if (!authenticateUser(loginRequest, user)) {
       throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials");
@@ -53,46 +49,15 @@ public class AuthService {
     return jwtUtil.generateToken(user);
   }
 
-  public User register(RegisterRequest registerRequest) {
-    Optional<User> user = userRepository.findByEmail(registerRequest.getEmail());
-    if (user.isPresent())
-      throw new HttpClientErrorException(HttpStatus.CONFLICT, "Email already in use");
-
-    return registerNewUser(registerRequest);
-  }
-
-  private User registerNewUser(RegisterRequest registerRequest) {
-    User newUser;
-
+  public String register(RegisterRequest registerRequest) {
     // Ciframos la contraseña
     String hashPass = SecurityUtils.encrypt(registerRequest.getPassword());
     registerRequest.setPassword(hashPass);
+    // Registramos el usuario
+    User user = userAPI.registerUser(registerRequest);
+    LoginRequest logReq = new LoginRequest();
+    BeanUtils.copyProperties(user, logReq);
 
-    // Aseguramos que tenga un rol, por defecto es cliente
-    if (registerRequest.getRol() == null)
-      registerRequest.setRol(UserRol.CLIENT);
-
-    switch (registerRequest.getRol()) {
-      case HOTEL_ADMIN:
-        HotelManager hm = new HotelManager();
-        BeanUtils.copyProperties(registerRequest, hm);
-        newUser = hotelManagerRepository.save(hm);
-        break;
-
-      case ADMIN:
-        User admin = new User();
-        BeanUtils.copyProperties(registerRequest, admin);
-        newUser = userRepository.save(admin);
-        break;
-
-      case CLIENT: // Por defecto cliente normal
-      default:
-        Client client = new Client();
-        BeanUtils.copyProperties(registerRequest, client);
-        client.setRol(UserRol.CLIENT);
-        newUser = clientRepository.save(client);
-        break;
-    }
-    return newUser;
+    return login(logReq);
   }
 }
diff --git a/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java b/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java
index 44d5d31..76cda7e 100644
--- a/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java
+++ b/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java
@@ -1,15 +1,13 @@
 package com.uva.authentication.utils;
 
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import com.auth0.jwt.JWT;
 import com.auth0.jwt.algorithms.Algorithm;
-import com.uva.authentication.models.remote.User;
+import com.uva.authentication.models.remote.Response;
 
 @Component
 public class JwtUtil {
@@ -27,7 +25,7 @@ public class JwtUtil {
     return jwtExpiration;
   }
 
-  public String generateToken(User user) {
+  public String generateToken(Response user) {
     Algorithm algorithm = Algorithm.HMAC256(secretKey);
     return JWT
         .create()
@@ -40,4 +38,7 @@ public class JwtUtil {
         .withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration * 1000))
         .sign(algorithm);
   }
+
+  // TODO estaría guapo recuperar métodos de validación para el token de petición
+  // para este servicio
 }
diff --git a/java/services/auth/src/main/resources/application.properties b/java/services/auth/src/main/resources/application.properties
index 576d72d..ce8e489 100644
--- a/java/services/auth/src/main/resources/application.properties
+++ b/java/services/auth/src/main/resources/application.properties
@@ -14,4 +14,4 @@ security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot
 security.jwt.expiration-time=3600000
 security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui
 
-external.services.users.baseurl=http://localhost:8080/users
\ No newline at end of file
+external.services.users.url=http://localhost:8080/users
\ No newline at end of file
diff --git a/java/services/users/.gitignore b/java/services/users/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/java/services/users/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/java/services/users/.mvn/wrapper/maven-wrapper.properties b/java/services/users/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d58dfb7
--- /dev/null
+++ b/java/services/users/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/java/services/users/Dockerfile b/java/services/users/Dockerfile
new file mode 100644
index 0000000..8d0b79d
--- /dev/null
+++ b/java/services/users/Dockerfile
@@ -0,0 +1,10 @@
+FROM maven:3-openjdk-17 AS maven
+WORKDIR /app
+COPY ./ ./
+RUN mvn -Dmaven.test.skip clean package
+FROM openjdk:17-jdk-oracle
+ARG JAR_FILE=/app/target/*.jar
+COPY --from=maven ${JAR_FILE} app.jar
+ENV PORT 8080
+EXPOSE $PORT
+ENTRYPOINT ["java","-jar", "/app.jar"]
\ No newline at end of file
diff --git a/java/services/users/mvnw b/java/services/users/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/java/services/users/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+#   JAVA_HOME - location of a JDK home dir, required when download maven via java source
+#   MVNW_REPOURL - repo url base for downloading maven distribution
+#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+  [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+  native_path() { cygpath --path --windows "$1"; }
+  ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+  if [ -n "${JAVA_HOME-}" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+      JAVACCMD="$JAVA_HOME/jre/sh/javac"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+      JAVACCMD="$JAVA_HOME/bin/javac"
+
+      if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+        echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+        echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+        return 1
+      fi
+    fi
+  else
+    JAVACMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v java
+    )" || :
+    JAVACCMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v javac
+    )" || :
+
+    if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+      echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+      return 1
+    fi
+  fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+  str="${1:-}" h=0
+  while [ -n "$str" ]; do
+    char="${str%"${str#?}"}"
+    h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+    str="${str#?}"
+  done
+  printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+  printf %s\\n "$1" >&2
+  exit 1
+}
+
+trim() {
+  # MWRAPPER-139:
+  #   Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+  #   Needed for removing poorly interpreted newline sequences when running in more
+  #   exotic environments such as mingw bash on Windows.
+  printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+  case "${key-}" in
+  distributionUrl) distributionUrl=$(trim "${value-}") ;;
+  distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+  esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+  case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+  *)
+    echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+    distributionPlatform=linux-amd64
+    ;;
+  esac
+  distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+  ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+  exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+  verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+  clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+  trap clean HUP INT TERM EXIT
+else
+  die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+  distributionUrl="${distributionUrl%.zip}.tar.gz"
+  distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+  verbose "Found wget ... using wget"
+  wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+  verbose "Found curl ... using curl"
+  curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+  verbose "Falling back to use Java to download"
+  javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+  targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+  cat >"$javaSource" <<-END
+	public class Downloader extends java.net.Authenticator
+	{
+	  protected java.net.PasswordAuthentication getPasswordAuthentication()
+	  {
+	    return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+	  }
+	  public static void main( String[] args ) throws Exception
+	  {
+	    setDefault( new Downloader() );
+	    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+	  }
+	}
+	END
+  # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+  verbose " - Compiling Downloader.java ..."
+  "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+  verbose " - Running Downloader.java ..."
+  "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+  distributionSha256Result=false
+  if [ "$MVN_CMD" = mvnd.sh ]; then
+    echo "Checksum validation is not supported for maven-mvnd." >&2
+    echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  elif command -v sha256sum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  elif command -v shasum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+    echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  fi
+  if [ $distributionSha256Result = false ]; then
+    echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+    echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+  unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+  tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/java/services/users/mvnw.cmd b/java/services/users/mvnw.cmd
new file mode 100644
index 0000000..249bdf3
--- /dev/null
+++ b/java/services/users/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+  $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/java/services/users/pom.xml b/java/services/users/pom.xml
new file mode 100644
index 0000000..7ee0d6a
--- /dev/null
+++ b/java/services/users/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>3.3.4</version>
+		<relativePath/> <!-- lookup parent from repository -->
+	</parent>
+	<groupId>com.uva</groupId>
+	<artifactId>users</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<name>users</name>
+	<description>User rest</description>
+	<url/>
+	<licenses>
+		<license/>
+	</licenses>
+	<developers>
+		<developer/>
+	</developers>
+	<scm>
+		<connection/>
+		<developerConnection/>
+		<tag/>
+		<url/>
+	</scm>
+	<properties>
+		<java.version>17</java.version>
+	</properties>
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-jpa</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-rest</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.mysql</groupId>
+			<artifactId>mysql-connector-j</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter-security</artifactId>
+	</dependency>
+	<dependency>
+		<groupId>com.auth0</groupId>
+		<artifactId>java-jwt</artifactId>
+		<version>4.4.0</version>
+	</dependency>
+	<dependency>
+		<groupId>jakarta.servlet</groupId>
+		<artifactId>jakarta.servlet-api</artifactId>
+		<scope>provided</scope>
+    </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
\ No newline at end of file
diff --git a/java/services/users/src/main/java/com/uva/api/UsersApplication.java b/java/services/users/src/main/java/com/uva/api/UsersApplication.java
new file mode 100644
index 0000000..ec976f0
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/UsersApplication.java
@@ -0,0 +1,12 @@
+package com.uva.api;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class UsersApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(UsersApplication.class, args);
+	}
+}
diff --git a/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java b/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java
new file mode 100644
index 0000000..f6e3899
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java
@@ -0,0 +1,5 @@
+package com.uva.api.apis;
+
+public class HotelApi {
+
+}
diff --git a/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java b/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java
new file mode 100644
index 0000000..9e4c9b4
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java
@@ -0,0 +1,54 @@
+package com.uva.api.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import com.uva.api.filter.JwtAuthenticationFilter;
+import com.uva.api.models.UserRol;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+        private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+        public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
+                this.jwtAuthenticationFilter = jwtAuthenticationFilter;
+        }
+
+        @Bean
+        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+                http.csrf(csrf -> csrf.disable())
+                                .authorizeHttpRequests(authorize -> authorize
+                                                // Permitir OPTIONS sin autenticación
+                                                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+                                                // Acceso restringido a usuarios y administradores
+                                                .requestMatchers("users", "users/**").hasAnyRole(
+                                                                UserRol.CLIENT.toString(),
+                                                                UserRol.HOTEL_ADMIN.toString(),
+                                                                UserRol.ADMIN.toString())
+                                                // Acceso restringido a gestores de hoteles y administradores
+                                                .requestMatchers(HttpMethod.GET, "hotels", "hotels/*").hasAnyRole(
+                                                                UserRol.CLIENT.toString(),
+                                                                UserRol.HOTEL_ADMIN.toString(),
+                                                                UserRol.ADMIN.toString())
+
+                                                .requestMatchers("hotels", "hotels/**")
+                                                .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString())
+                                                // Acceso restringido a cualquier usuario del sistema
+                                                .requestMatchers("bookings", "bookings/**")
+                                                .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString(),
+                                                                UserRol.CLIENT.toString())
+                                                // Rechazar el resto
+                                                .anyRequest().denyAll())
+                                // Registra el filtro antes del filtro estándar de autenticación
+                                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+                return http.build();
+        }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/controllers/UserController.java b/java/services/users/src/main/java/com/uva/api/controllers/UserController.java
new file mode 100644
index 0000000..e148daf
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/controllers/UserController.java
@@ -0,0 +1,112 @@
+package com.uva.api.controllers;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.HttpClientErrorException;
+
+import com.uva.api.models.User;
+import com.uva.api.models.UserStatus;
+import com.uva.api.services.UserService;
+
+@RestController
+@RequestMapping("users")
+@CrossOrigin(origins = "*")
+public class UserController {
+
+  @Autowired
+  private UserService userService;
+
+  @GetMapping
+  public ResponseEntity<List<User>> getAllUsers() {
+    List<User> users = userService.getAllUsers();
+    return ResponseEntity.ok(users);
+  }
+
+  @GetMapping(params = { "email" })
+  public ResponseEntity<?> getUserByEmail(@RequestParam String email) {
+    try {
+      return ResponseEntity.ok(userService.getUserByEmail(email));
+    } catch (HttpClientErrorException e) {
+      if (e.getStatusCode() == HttpStatus.NOT_FOUND)
+        return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
+      throw e;
+    }
+  }
+
+  @PostMapping
+  public ResponseEntity<?> addUser(@RequestBody User user) {
+    userService.registerNewUser(user);
+    return new ResponseEntity<>(HttpStatus.ACCEPTED);
+  }
+
+  @GetMapping("/{id}")
+  public ResponseEntity<?> getUserById(@PathVariable int id) {
+    return ResponseEntity.ok(userService.getUserById(id));
+  }
+
+  @PutMapping("/{id}")
+  public ResponseEntity<?> updateUserData(@PathVariable int id, @RequestBody Map<String, String> json) {
+    System.err.println(json.entrySet().size());
+    json.keySet().forEach(k -> System.err.println(k));
+    String name = json.get("name");
+    String email = json.get("email");
+    if (name == null || email == null) {
+      return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST);
+    }
+    try {
+      User user = userService.updateUserData(id, name, email);
+      return new ResponseEntity<User>(user, HttpStatus.OK);
+    } catch (HttpClientErrorException e) {
+      if (e.getStatusCode() == HttpStatus.NOT_FOUND)
+        return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
+      throw e;
+    }
+  }
+
+  @PatchMapping("/{id}")
+  public ResponseEntity<?> updateUserState(@PathVariable int id, @RequestBody Map<String, String> json) {
+
+    String strStatus = json.get("status");
+    if (strStatus == null) {
+      return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST);
+    }
+    try {
+      UserStatus userStatus = UserStatus.valueOf(strStatus);
+      return ResponseEntity.ok(userService.updateUserStatus(id, userStatus));
+    } catch (IllegalArgumentException e) {
+      return new ResponseEntity<String>("Unknown user state", HttpStatus.BAD_REQUEST);
+    } catch (HttpClientErrorException e) {
+      if (e.getStatusCode() == HttpStatus.NOT_FOUND)
+        return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
+      throw e;
+    }
+
+  }
+
+  @DeleteMapping("/{id}")
+  public ResponseEntity<?> deleteUser(@PathVariable Integer id) {
+    try {
+      return ResponseEntity.ok(userService.deleteUserById(id));
+    } catch (HttpClientErrorException e) {
+      if (e.getStatusCode() == HttpStatus.NOT_FOUND)
+        return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
+      throw e;
+    }
+  }
+
+}
diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java b/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java
new file mode 100644
index 0000000..5681a60
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java
@@ -0,0 +1,50 @@
+package com.uva.api.exceptions;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(HotelNotFoundException.class)
+    public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("timestamp", LocalDateTime.now());
+        body.put("message", ex.getMessage());
+
+        return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
+    }
+
+    @ExceptionHandler(InvalidRequestException.class)
+    public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("timestamp", LocalDateTime.now());
+        body.put("message", ex.getMessage());
+
+        return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler(InvalidDateRangeException.class)
+    public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("timestamp", LocalDateTime.now());
+        body.put("message", ex.getMessage());
+
+        return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Map<String, Object>> handleGeneralException(Exception ex) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("timestamp", LocalDateTime.now());
+        body.put("message", "An unexpected error occurred: " + ex.getMessage());
+
+        return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java b/java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java
new file mode 100644
index 0000000..dc466f6
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java
@@ -0,0 +1,11 @@
+package com.uva.api.exceptions;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción
+public class HotelNotFoundException extends RuntimeException {
+    public HotelNotFoundException(int id) {
+        super("Hotel not found with id: " + id);
+    }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java b/java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java
new file mode 100644
index 0000000..58bf97d
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java
@@ -0,0 +1,7 @@
+package com.uva.api.exceptions;
+
+public class InvalidDateRangeException extends RuntimeException {
+    public InvalidDateRangeException(String message) {
+        super(message);
+    }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java b/java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java
new file mode 100644
index 0000000..677cc4b
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java
@@ -0,0 +1,11 @@
+package com.uva.api.exceptions;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.BAD_REQUEST)
+public class InvalidRequestException extends RuntimeException {
+    public InvalidRequestException(String message) {
+        super(message);
+    }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java b/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..f6c313d
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java
@@ -0,0 +1,116 @@
+package com.uva.api.filter;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+
+import com.uva.api.models.UserRol;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.Filter;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.Collections;
+
+@Component
+public class JwtAuthenticationFilter implements Filter {
+
+    @Value("${security.jwt.secret-key}")
+    private String secretKey;
+
+    @Value("${security.jwt.kid}")
+    private String kid;
+
+    @Value("${security.jwt.expiration-time}")
+    private long jwtExpiration;
+
+    private Algorithm getSigningAlgorithm() {
+        return Algorithm.HMAC256(secretKey); // Usar HMAC256 con la clave secreta
+    }
+
+    private String getTokenFromRequest(HttpServletRequest request) {
+        String authHeader = request.getHeader("Authorization");
+        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+            return null;
+        }
+        return authHeader.substring(7);
+    }
+
+    private DecodedJWT validateAndDecodeToken(String token) {
+        try {
+            JWTVerifier verifier = JWT.require(getSigningAlgorithm()).build();
+            return verifier.verify(token); // Verifica y decodifica el token
+        } catch (JWTVerificationException ex) {
+            System.out.println(
+                    "[" + LocalDateTime.now().toString() + "] Error de verificación del token: " + ex.getMessage());
+            return null;
+        }
+    }
+
+    private String getEmailFromToken(DecodedJWT jwt) {
+        return jwt.getClaim("email").asString();
+    }
+
+    private UserRol getRoleFromToken(DecodedJWT jwt) {
+        String role = jwt.getClaim("rol").asString();
+        return UserRol.valueOf(role);
+    }
+
+    private String formatRole(UserRol rol) {
+        return String.format("ROLE_%s", rol.toString());
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        String token = getTokenFromRequest(httpRequest);
+
+        boolean aproved = false;
+
+        System.out.print("[" + LocalDateTime.now().toString() + "] TOKEN: " + token);
+
+        if (token != null) {
+            DecodedJWT jwt = validateAndDecodeToken(token);
+            System.out.print(" " + jwt.toString() + " ");
+            if (jwt != null) {
+                String email = getEmailFromToken(jwt);
+                UserRol role = getRoleFromToken(jwt);
+                System.out.print(" email=" + email + " role=" + role + " ");
+
+                if (email != null && role != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+                    // Crear la autoridad con el rol del token
+                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(formatRole(role));
+
+                    // Crear autenticación
+                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email,
+                            null, Collections.singletonList(authority));
+                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
+
+                    // Establecer autenticación en el contexto de seguridad
+                    SecurityContextHolder.getContext().setAuthentication(authentication);
+                    aproved = true;
+                }
+            }
+        }
+
+        System.out.println(" APROVED: " + aproved);
+
+        // Continuar con el resto de filtros
+        chain.doFilter(request, response);
+    }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/AuthResponse.java b/java/services/users/src/main/java/com/uva/api/models/AuthResponse.java
new file mode 100644
index 0000000..d7ea088
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/AuthResponse.java
@@ -0,0 +1,51 @@
+package com.uva.api.models;
+
+public class AuthResponse {
+
+  private int id;
+  private String username;
+  private String email;
+  private String password;
+  private UserRol rol;
+
+  public int getId() {
+    return this.id;
+  }
+
+  public void setId(int id) {
+    this.id = id;
+  }
+
+  public String getUsername() {
+    return this.username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getEmail() {
+    return this.email;
+  }
+
+  public void setEmail(String email) {
+    this.email = email;
+  }
+
+  public String getPassword() {
+    return this.password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  public UserRol getRol() {
+    return this.rol;
+  }
+
+  public void setRol(UserRol rol) {
+    this.rol = rol;
+  }
+
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/Booking.java b/java/services/users/src/main/java/com/uva/api/models/Booking.java
new file mode 100644
index 0000000..88367c6
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/Booking.java
@@ -0,0 +1,12 @@
+package com.uva.api.models;
+
+import java.time.LocalDate;
+
+public class Booking {
+
+  public LocalDate getEndDate() {
+    // TODO Auto-generated method stub
+    throw new UnsupportedOperationException("Unimplemented method 'getEndDate'");
+  }
+
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/Client.java b/java/services/users/src/main/java/com/uva/api/models/Client.java
new file mode 100644
index 0000000..238e23d
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/Client.java
@@ -0,0 +1,64 @@
+package com.uva.api.models;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import jakarta.persistence.Basic;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "user_client")
+public class Client extends User {
+
+  @Basic(optional = false)
+  @Column(nullable = false)
+  @Enumerated(EnumType.STRING)
+  private UserStatus status;
+
+  @JsonIgnore
+  @OneToMany(mappedBy = "userId", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
+  private List<Booking> bookings;
+
+  public Client() {
+    super();
+    bookings = new ArrayList<>();
+    status = UserStatus.NO_BOOKINGS;
+  }
+
+  public Client(int id, String name, String email, String password, UserStatus status,
+      List<Booking> bookings) {
+    super(id, name, email, password, UserRol.CLIENT);
+    setStatus(status);
+    setBookings(bookings);
+  }
+
+  public UserStatus getStatus() {
+    if (getBookings() == null || getBookings().isEmpty())
+      return UserStatus.NO_BOOKINGS;
+    boolean activeBookings = getBookings().stream()
+        .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora
+    return activeBookings ? UserStatus.WITH_ACTIVE_BOOKINGS : UserStatus.WITH_INACTIVE_BOOKINGS;
+  }
+
+  public void setStatus(UserStatus status) {
+    this.status = status;
+  }
+
+  public List<Booking> getBookings() {
+    return this.bookings;
+  }
+
+  public void setBookings(List<Booking> bookings) {
+    this.bookings = bookings;
+  }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/Hotel.java b/java/services/users/src/main/java/com/uva/api/models/Hotel.java
new file mode 100644
index 0000000..29adbbe
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/Hotel.java
@@ -0,0 +1,5 @@
+package com.uva.api.models;
+
+public class Hotel {
+
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/HotelManager.java b/java/services/users/src/main/java/com/uva/api/models/HotelManager.java
new file mode 100644
index 0000000..46570f3
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/HotelManager.java
@@ -0,0 +1,37 @@
+package com.uva.api.models;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "hotel_manager_user")
+public class HotelManager extends User {
+
+  @JsonIgnore
+  @OneToMany(mappedBy = "hotelManager", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
+  private List<Hotel> hotels;
+
+  public HotelManager() {
+    super();
+    hotels = new ArrayList<>();
+  }
+
+  public HotelManager(int id, String name, String email, String password, List<Hotel> hotels) {
+    super(id, name, email, password, UserRol.HOTEL_ADMIN);
+    setHotels(hotels);
+  }
+
+  public List<Hotel> getHotels() {
+    return this.hotels;
+  }
+
+  public void setHotels(List<Hotel> hotels) {
+    this.hotels = hotels;
+  }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/User.java b/java/services/users/src/main/java/com/uva/api/models/User.java
new file mode 100644
index 0000000..bdcb1b5
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/User.java
@@ -0,0 +1,94 @@
+package com.uva.api.models;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import jakarta.persistence.Basic;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Inheritance;
+import jakarta.persistence.InheritanceType;
+import jakarta.persistence.Table;
+
+@Entity
+@Inheritance(strategy = InheritanceType.JOINED)
+@Table(name = "users")
+public class User {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Basic(optional = false)
+  @Column(nullable = false)
+  private int id;
+
+  @Basic(optional = false)
+  @Column(nullable = false)
+  private String name;
+
+  @Basic(optional = false)
+  @Column(nullable = false, unique = true)
+  private String email;
+
+  @Basic(optional = false)
+  @Column(nullable = false)
+  private String password;
+
+  @Basic(optional = false)
+  @Column(nullable = false)
+  @Enumerated(EnumType.STRING)
+  private UserRol rol = UserRol.CLIENT;
+
+  public User() {
+  }
+
+  public User(int id, String name, String email, String password, UserRol rol) {
+    setId(id);
+    setName(name);
+    setEmail(email);
+    setRol(rol);
+  }
+
+  public int getId() {
+    return this.id;
+  }
+
+  public void setId(int id) {
+    this.id = id;
+  }
+
+  public String getName() {
+    return this.name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getEmail() {
+    return this.email;
+  }
+
+  public void setEmail(String email) {
+    this.email = email;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String rawPassword) {
+    this.password = rawPassword;
+  }
+
+  public UserRol getRol() {
+    return this.rol;
+  }
+
+  public void setRol(UserRol rol) {
+    this.rol = rol;
+  }
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/UserRol.java b/java/services/users/src/main/java/com/uva/api/models/UserRol.java
new file mode 100644
index 0000000..025489b
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/UserRol.java
@@ -0,0 +1,5 @@
+package com.uva.api.models;
+
+public enum UserRol {
+  ADMIN, HOTEL_ADMIN, CLIENT
+}
diff --git a/java/services/users/src/main/java/com/uva/api/models/UserStatus.java b/java/services/users/src/main/java/com/uva/api/models/UserStatus.java
new file mode 100644
index 0000000..6538015
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/models/UserStatus.java
@@ -0,0 +1,5 @@
+package com.uva.api.models;
+
+public enum UserStatus {
+  NO_BOOKINGS, WITH_ACTIVE_BOOKINGS, WITH_INACTIVE_BOOKINGS;
+}
diff --git a/java/services/users/src/main/java/com/uva/api/repositories/ClientRepository.java b/java/services/users/src/main/java/com/uva/api/repositories/ClientRepository.java
new file mode 100644
index 0000000..494b927
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/repositories/ClientRepository.java
@@ -0,0 +1,10 @@
+package com.uva.api.repositories;
+
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import com.uva.api.models.Client;
+
+public interface ClientRepository extends JpaRepository<Client, Integer> {
+  Optional<Client> findByEmail(String email);
+}
\ No newline at end of file
diff --git a/java/services/users/src/main/java/com/uva/api/repositories/HotelManagerRepository.java b/java/services/users/src/main/java/com/uva/api/repositories/HotelManagerRepository.java
new file mode 100644
index 0000000..00fdba5
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/repositories/HotelManagerRepository.java
@@ -0,0 +1,10 @@
+package com.uva.api.repositories;
+
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import com.uva.api.models.HotelManager;
+
+public interface HotelManagerRepository extends JpaRepository<HotelManager, Integer> {
+  Optional<HotelManager> findByEmail(String email);
+}
\ No newline at end of file
diff --git a/java/services/users/src/main/java/com/uva/api/repositories/UserRepository.java b/java/services/users/src/main/java/com/uva/api/repositories/UserRepository.java
new file mode 100644
index 0000000..5a301bc
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/repositories/UserRepository.java
@@ -0,0 +1,11 @@
+package com.uva.api.repositories;
+
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import com.uva.api.models.User;
+
+public interface UserRepository extends JpaRepository<User, Integer> {
+  Optional<User> findByEmail(String email);
+}
diff --git a/java/services/users/src/main/java/com/uva/api/services/UserService.java b/java/services/users/src/main/java/com/uva/api/services/UserService.java
new file mode 100644
index 0000000..820880f
--- /dev/null
+++ b/java/services/users/src/main/java/com/uva/api/services/UserService.java
@@ -0,0 +1,132 @@
+package com.uva.api.services;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
+
+import com.uva.api.models.AuthResponse;
+import com.uva.api.models.Client;
+import com.uva.api.models.HotelManager;
+import com.uva.api.models.User;
+import com.uva.api.models.UserRol;
+import com.uva.api.models.UserStatus;
+import com.uva.api.repositories.ClientRepository;
+import com.uva.api.repositories.HotelManagerRepository;
+import com.uva.api.repositories.UserRepository;
+
+@Service
+public class UserService {
+
+  @Autowired
+  private UserRepository userRepository;
+
+  @Autowired
+  private ClientRepository clientRepository;
+
+  @Autowired
+  private HotelManagerRepository hotelManagerRepository;
+
+  public List<User> getAllUsers() {
+    return userRepository.findAll();
+  }
+
+  private User assertUser(Optional<? extends User> opUser) {
+    return opUser.orElseThrow(() -> new HttpClientErrorException(HttpStatus.NOT_FOUND));
+  }
+
+  public User getUserById(int id) {
+    return assertUser(userRepository.findById(id));
+  }
+
+  public AuthResponse getUserByEmail(String email) {
+    User u = assertUser(userRepository.findByEmail(email));
+    AuthResponse auth = new AuthResponse();
+    BeanUtils.copyProperties(u, auth);
+    return auth;
+  }
+
+  public User registerNewUser(User registerRequest) {
+    User newUser;
+
+    // Aseguramos que tenga un rol, por defecto es cliente
+    if (registerRequest.getRol() == null)
+      registerRequest.setRol(UserRol.CLIENT);
+
+    switch (registerRequest.getRol()) {
+      case HOTEL_ADMIN:
+        HotelManager hm = new HotelManager();
+        BeanUtils.copyProperties(registerRequest, hm);
+        newUser = hotelManagerRepository.save(hm);
+        break;
+
+      case ADMIN:
+        User admin = new User();
+        BeanUtils.copyProperties(registerRequest, admin);
+        newUser = admin; // userAPI.save(admin);
+        break;
+
+      case CLIENT: // Por defecto cliente normal
+      default:
+        Client client = new Client();
+        BeanUtils.copyProperties(registerRequest, client);
+        client.setRol(UserRol.CLIENT);
+        newUser = clientRepository.save(client);
+        break;
+    }
+    return newUser;
+  }
+
+  public User updateUserData(int id, String name, String email) {
+    User user = getUserById(id);
+    user.setName(name);
+    user.setEmail(email);
+    return userRepository.save(user);
+  }
+
+  public User updateUserStatus(int id, UserStatus status) {
+
+    Client user = (Client) assertUser(clientRepository.findById(id));
+
+    boolean activeBookings = user.getBookings().stream()
+        .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora
+    boolean inactiveBookings = user.getBookings().stream()
+        .anyMatch(booking -> booking.getEndDate().isBefore(LocalDate.now())); // reserva < ahora
+
+    switch (status) {
+      case NO_BOOKINGS:
+        if (!user.getBookings().isEmpty())
+          throw new IllegalArgumentException("Invalid State: The user has at least one booking");
+        break;
+      case WITH_ACTIVE_BOOKINGS:
+        if (user.getBookings().isEmpty())
+          throw new IllegalArgumentException("Invalid State: The user don't has bookings");
+        if (!activeBookings)
+          throw new IllegalArgumentException("Invalid State: The user don't has active bookings");
+        break;
+      case WITH_INACTIVE_BOOKINGS:
+        if (user.getBookings().isEmpty())
+          throw new IllegalArgumentException("Invalid State: The user don't has bookings");
+        if (!inactiveBookings)
+          throw new IllegalArgumentException("Invalid State: The user don't has inactive bookings");
+        break;
+      default:
+        break;
+    }
+    user.setStatus(status);
+    return userRepository.save(user);
+  }
+
+  public User deleteUserById(int id) {
+    User user = getUserById(id);
+    // TODO eliminar reservas de usuario ahora mismo no por el modo cascada pero a
+    // futuro sí, después de la disgregación en microservicios
+    userRepository.deleteById(id);
+    return user;
+  }
+}
diff --git a/java/services/users/src/main/resources/application.properties b/java/services/users/src/main/resources/application.properties
new file mode 100644
index 0000000..e9b75a3
--- /dev/null
+++ b/java/services/users/src/main/resources/application.properties
@@ -0,0 +1,18 @@
+spring.application.name=roomBooking
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
+spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/RoomsBooking?createDatabaseIfNotExist=true
+spring.datasource.username=user
+spring.datasource.password=password
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.security.user.name=admin
+spring.security.user.password=admin
+
+# Usar esto para alternar entre las exposición del room repository ya que no es necesario su uso pero por defecto, al no cubrir su ruta, se expone
+# spring.data.rest.base-path=false
+external.services.auth.host=localhost:8101
+
+security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot
+# 1h in millisecond
+security.jwt.expiration-time=3600000
+security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui
\ No newline at end of file
diff --git a/java/services/users/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java b/java/services/users/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java
new file mode 100644
index 0000000..3b50599
--- /dev/null
+++ b/java/services/users/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java
@@ -0,0 +1,13 @@
+package com.uva.roomBooking;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class RoomBookingApplicationTests {
+
+	@Test
+	void contextLoads() {
+	}
+
+}
-- 
GitLab