Integrar base de datos MySQL y completar registro de usuarios

- database/schema.sql: DDL completo de las 22 tablas del proyecto MAPS Connect (3FN), con FKs, CHECK constraints, índices y 3 triggers para reglas de integridad (límite de 3 certificados, verificación docente, semestre 1-12). - Corrección de la API JJWT 0.12.x en JwtTokenProvider (parser(), verifyWith(), parseSignedClaims(), getPayload()). - Nuevo CustomUserDetailsService y bean PasswordEncoder (BCrypt) en SecurityConfig, requeridos por Spring Security. - Formulario de registro (registro.html + registro.js) implementado y conectado a POST /auth/registrar, con validación de correo institucional (@tecmilenio.mx) y contraseñas. - CorsConfig: se agregó el origen http://localhost:63342 (servidor de IntelliJ) a los orígenes permitidos. - Corrección de mapeo entidad Usuario ↔ esquema real: columnas id_usuario/ correo/contrasena_hash, tipo Integer para el id (antes Long/BigInt vs Int en BD). - application-dev.yml: ddl-auto cambiado de create-drop a validate para respetar el esquema ya creado manualmente. - @Builder.Default agregado a activo y fechaCreacion en Usuario (Lombok ignoraba los valores por defecto en el builder). - Clave secreta JWT alargada a ≥512 bits, requerido por el algoritmo HS512. Resultado: flujo completo de registro funcionando end-to-end (frontend → backend → MySQL → JWT).

dantereach committed Aug 27, 2026 at 15:27 UTC aa62e1791571ab77295c3098e359fdcf7bbf1646
10 files changed +113 -30
frontend/js/pages/registro.js
+51
@@ -1,3 +1,54 @@
1 +const EMAIL_INSTITUCIONAL_REGEX = /^[^\s@]+@(tecmilenio\.mx|servicios\.tecmilenio\.mx)$/i;
2 +
3 document.addEventListener('DOMContentLoaded', () => {
4 Auth.redirectIfAuthenticated();
5 +
6 + document.getElementById('registro-form').addEventListener('submit', async (e) => {
7 + e.preventDefault();
8 + Utils.clearAlert('alert-container');
9 +
10 + const nombre = document.getElementById('nombre').value.trim();
11 + const apellido = document.getElementById('apellido').value.trim();
12 + const email = document.getElementById('email').value.trim();
13 + const contrasena = document.getElementById('contrasena').value;
14 + const confirmarContrasena = document.getElementById('confirmar-contrasena').value;
15 +
16 + if (!nombre || !apellido || !email || !contrasena || !confirmarContrasena) {
17 + Utils.showAlert('alert-container', 'Completa todos los campos.');
18 + return;
19 + }
20 +
21 + if (!EMAIL_INSTITUCIONAL_REGEX.test(email)) {
22 + Utils.showAlert('alert-container', 'Usa tu correo institucional (@tecmilenio.mx o @servicios.tecmilenio.mx).');
23 + return;
24 + }
25 +
26 + if (contrasena.length < 6) {
27 + Utils.showAlert('alert-container', 'La contraseña debe tener al menos 6 caracteres.');
28 + return;
29 + }
30 +
31 + if (contrasena !== confirmarContrasena) {
32 + Utils.showAlert('alert-container', 'Las contraseñas no coinciden.');
33 + return;
34 + }
35 +
36 + const submitBtn = e.target.querySelector('button[type="submit"]');
37 + submitBtn.disabled = true;
38 + submitBtn.textContent = 'Creando cuenta...';
39 +
40 + try {
41 + const response = await API.request('/auth/registrar', {
42 + method: 'POST',
43 + body: JSON.stringify({ nombre, apellido, email, contrasena })
44 + });
45 +
46 + Auth.saveSession(response.data.token, response.data.usuario);
47 + window.location.href = Auth.resolvePath('pages/inicio.html');
48 + } catch (error) {
49 + Utils.showAlert('alert-container', error.message || 'No se pudo completar el registro.');
50 + submitBtn.disabled = false;
51 + submitBtn.textContent = 'Crear cuenta';
52 + }
53 + });
54 });
frontend/registro.html
+37 -13
@@ -9,19 +9,43 @@
9 <link rel="stylesheet" href="css/components.css">
10 </head>
11 <body class="register-page">
12 - <div class="auth-layout">
13 - <div class="auth-card card">
14 - <h1 class="page-title">Registro</h1>
15 - <div id="alert-container"></div>
16 - <form id="registro-form"></form>
17 - <p style="margin-top:1rem;text-align:center">
18 - <a href="login.html">Ya tengo cuenta</a>
19 - </p>
20 - </div>
12 +<div class="auth-layout">
13 + <div class="auth-card card">
14 + <h1 class="page-title">Registro</h1>
15 + <div id="alert-container"></div>
16 + <form id="registro-form">
17 + <div class="form-group">
18 + <label for="nombre">Nombre</label>
19 + <input type="text" id="nombre" class="form-control" required>
20 + </div>
21 + <div class="form-group">
22 + <label for="apellido">Apellido</label>
23 + <input type="text" id="apellido" class="form-control" required>
24 + </div>
25 + <div class="form-group">
26 + <label for="email">Correo institucional</label>
27 + <input type="email" id="email" class="form-control" required placeholder="usuario@tecmilenio.mx">
28 + <div class="form-hint">Debe ser un correo @tecmilenio.mx o @servicios.tecmilenio.mx</div>
29 + </div>
30 + <div class="form-group">
31 + <label for="contrasena">Contraseña</label>
32 + <input type="password" id="contrasena" class="form-control" required minlength="6">
33 + <div class="form-hint">Mínimo 6 caracteres</div>
34 + </div>
35 + <div class="form-group">
36 + <label for="confirmar-contrasena">Confirmar contraseña</label>
37 + <input type="password" id="confirmar-contrasena" class="form-control" required minlength="6">
38 + </div>
39 + <button type="submit" class="btn btn-primary" style="width:100%">Crear cuenta</button>
40 + </form>
41 + <p style="margin-top:1rem;text-align:center">
42 + <a href="login.html">Ya tengo cuenta</a>
43 + </p>
44 </div>
22 - <script src="js/api.js"></script>
23 - <script src="js/auth.js"></script>
24 - <script src="js/utils.js"></script>
25 - <script src="js/pages/registro.js"></script>
45 +</div>
46 +<script src="js/api.js"></script>
47 +<script src="js/auth.js"></script>
48 +<script src="js/utils.js"></script>
49 +<script src="js/pages/registro.js"></script>
50 </body>
51 </html>
src/main/java/com/tecmilenio/mapsconect/config/CorsConfig.java
+1 -1
@@ -10,7 +10,7 @@ public class CorsConfig implements WebMvcConfigurer {
10 @Override
11 public void addCorsMappings(CorsRegistry registry) {
12 registry.addMapping("/**")
13 - .allowedOrigins("http://localhost:5500", "http://localhost:3000", "http://localhost:5173")
13 + .allowedOrigins("http://localhost:5500", "http://localhost:3000", "http://localhost:5173", "http://localhost:63342")
14 .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
15 .allowedHeaders("*")
16 .allowCredentials(true)
src/main/java/com/tecmilenio/mapsconect/config/SecurityConfig.java
+6
@@ -9,6 +9,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
9 import org.springframework.security.config.http.SessionCreationPolicy;
10 import org.springframework.security.web.SecurityFilterChain;
11 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
12 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
13 +import org.springframework.security.crypto.password.PasswordEncoder;
14 +
15
16 import com.tecmilenio.mapsconect.security.JwtAuthenticationFilter;
17
@@ -19,6 +22,9 @@ public class SecurityConfig {
22 @Bean
23 public JwtAuthenticationFilter jwtAuthenticationFilter() {
24 return new JwtAuthenticationFilter();
25 + } @Bean
26 + public PasswordEncoder passwordEncoder() {
27 + return new BCryptPasswordEncoder();
28 }
29
30 @Bean
src/main/java/com/tecmilenio/mapsconect/dto/UsuarioDTO.java
+1 -1
@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
11 @Builder
12 public class UsuarioDTO {
13
14 - private Long id;
14 + private Integer id;
15 private String email;
16 private String nombre;
17 private String apellido;
src/main/java/com/tecmilenio/mapsconect/entity/Usuario.java
+6 -3
@@ -18,9 +18,10 @@ public class Usuario {
18
19 @Id
20 @GeneratedValue(strategy = GenerationType.IDENTITY)
21 - private Long id;
21 + @Column(name = "id_usuario")
22 + private Integer id;
23
23 - @Column(nullable = false, unique = true, length = 100)
24 + @Column(name = "correo", nullable = false, unique = true, length = 100)
25 private String email;
26
27 @Column(nullable = false, length = 100)
@@ -29,15 +30,17 @@ public class Usuario {
30 @Column(nullable = false, length = 100)
31 private String apellido;
32
32 - @Column(nullable = false)
33 + @Column(name = "contrasena_hash", nullable = false)
34 private String contrasena;
35
36 @Enumerated(EnumType.STRING)
37 private Rol rol;
38
39 + @Builder.Default
40 @Column(nullable = false)
41 private Boolean activo = true;
42
43 + @Builder.Default
44 @Column(nullable = false)
45 private LocalDateTime fechaCreacion = LocalDateTime.now();
46
src/main/java/com/tecmilenio/mapsconect/repository/UsuarioRepository.java
+1 -1
@@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository;
7 import java.util.Optional;
8
9 @Repository
10 -public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
10 +public interface UsuarioRepository extends JpaRepository<Usuario, Integer> {
11
12 Optional<Usuario> findByEmail(String email);
13
src/main/java/com/tecmilenio/mapsconect/security/JwtTokenProvider.java
+7 -8
@@ -54,10 +54,10 @@ public class JwtTokenProvider {
54
55 public boolean validateToken(String token) {
56 try {
57 - Jwts.parserBuilder()
58 - .setSigningKey(getSigningKey())
57 + Jwts.parser()
58 + .verifyWith(getSigningKey())
59 .build()
60 - .parseClaimsJws(token);
60 + .parseSignedClaims(token);
61 return true;
62 } catch (Exception e) {
63 return false;
@@ -65,11 +65,10 @@ public class JwtTokenProvider {
65 }
66
67 private Claims getClaimsFromToken(String token) {
68 - return Jwts.parserBuilder()
69 - .setSigningKey(getSigningKey())
68 + return Jwts.parser()
69 + .verifyWith(getSigningKey())
70 .build()
71 - .parseClaimsJws(token)
72 - .getBody();
71 + .parseSignedClaims(token)
72 + .getPayload();
73 }
74 -
74 }
src/main/resources/application-dev.yml
+2 -2
@@ -2,11 +2,11 @@ spring:
2 datasource:
3 url: jdbc:mysql://localhost:3306/maps_conect_dev
4 username: root
5 - password:
5 + password: Haloymega59!
6
7 jpa:
8 hibernate:
9 - ddl-auto: create-drop
9 + ddl-auto: validate
10 show-sql: true
11 properties:
12 hibernate:
src/main/resources/application.yml
+1 -1
@@ -37,7 +37,7 @@ logging:
37 console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
38
39 jwt:
40 - secret: your-secret-key-change-in-production
40 + secret: mapsconnect-tecmilenio-super-secret-key-2024-desarrollo-local-minimo-64-caracteres-seguro
41 expiration: 86400000
42
43 app: