feat: Add frontend modules and REST controller stubs for MAPS Connect
Give the team a working app shell with login/register and per-screen pages, plus backend endpoints so each module can be implemented independently. Co-authored-by: Cursor <cursoragent@cursor.com>
Daniel Silva committed
Aug 18, 2026 at 19:44 UTC
f29d2cc06e9d359e201ac733902eeb1a77616e4c
46 files changed
+994
-111
database/README.md
new
+14
@@ -0,0 +1,14 @@
1
+# Base de datos
2
+
3
+Carpeta reservada para scripts SQL del proyecto.
4
+
5
+## Configuración inicial
6
+
7
+1. Crear las bases de datos en MySQL
8
+2. Modelar las 22 tablas según la especificación del proyecto
9
+3. Actualizar credenciales en `src/main/resources/application.yml`
10
+
11
+## Perfiles
12
+
13
+- **dev:** `application-dev.yml` → `maps_conect_dev`
14
+- **prod:** `application-prod.yml` → `maps_conect`
frontend/README.md
+26
-48
@@ -1,61 +1,39 @@
1
# Frontend - MAPS Connect
2
3
-Estructura lista para desarrollar. **HTML5 | CSS3 | JavaScript Vanilla**
3
+Entorno listo para programar. La estructura de archivos está definida; el contenido de cada módulo lo implementan ustedes.
4
5
-## 📁 Estructura
5
+## Estructura
6
7
```
8
frontend/
9
-├── index.html # Punto de entrada
10
-├── css/
11
-│ └── styles.css # Estilos
9
+├── index.html, login.html, registro.html
10
+├── css/ base.css, layout.css, components.css
11
├── js/
13
-│ └── app.js # Lógica principal + API
14
-├── pages/ # Futuras páginas
15
-└── assets/ # Imágenes
12
+│ ├── api.js Cliente HTTP base
13
+│ ├── auth.js Sesión JWT
14
+│ ├── utils.js Helpers mínimos
15
+│ ├── layout.js Menú lateral
16
+│ └── pages/ Un .js por pantalla
17
+├── pages/ 8 módulos (HTML vacío + layout)
18
+└── assets/ Imágenes
19
```
20
18
-## 🚀 Iniciar
21
+## Iniciar
22
20
-### 1. Abrir en Visual Studio Code
21
-```bash
22
-code frontend/
23
-```
24
-
25
-### 2. Live Server
26
-- Instalar extensión "Live Server" en VS Code
27
-- Clic derecho en `index.html` → "Open with Live Server"
28
-- Abre en `http://localhost:5500`
29
-
30
-### 3. Verificar Backend
31
-```bash
32
-curl http://localhost:8080/api/health
33
-```
34
-
35
-## 🔗 API Base URL
36
-```javascript
37
-http://localhost:8080/api
38
-```
23
+1. Live Server en `index.html` → `http://localhost:5500`
24
+2. Backend en `http://localhost:8080/api`
25
40
-## 📝 Usar API
41
-
42
-```javascript
43
-// En app.js está disponible el objeto API
44
-await API.login(email, password);
45
-await API.register(nombre, email, password, rol);
46
-
47
-// O hacer requests custom
48
-await API.request('/endpoint', { method: 'POST', body: JSON.stringify({...}) });
49
-```
50
-
51
-## 🎨 Colores (en CSS)
52
-```css
53
---primary-color: #1e40af
54
---secondary-color: #7c3aed
55
---success-color: #10b981
56
---danger-color: #ef4444
57
-```
26
+## Por módulo
27
59
----
28
+| Pantalla | HTML | JS |
29
+|----------|------|-----|
30
+| Inicio | `pages/inicio.html` | `js/pages/inicio.js` |
31
+| Perfil | `pages/perfil.html` | `js/pages/perfil.js` |
32
+| Foro | `pages/foro.html` | `js/pages/foro.js` |
33
+| Tips | `pages/tips.html` | `js/pages/tips.js` |
34
+| Apuntes | `pages/recursos.html` | `js/pages/recursos.js` |
35
+| Mensajes | `pages/mensajes.html` | `js/pages/mensajes.js` |
36
+| Empresarial | `pages/empresarial.html` | `js/pages/empresarial.js` |
37
+| Círculos | `pages/circulos.html` | `js/pages/circulos.js` |
38
61
-**¡Listo para programar! 🚀**
39
+El contenido de cada pantalla va en `#main-content`.
frontend/assets/.gitkeep
frontend/css/base.css
new
+56
@@ -0,0 +1,56 @@
1
+/* MAPS Connect - Base */
2
+
3
+:root {
4
+ --primary-color: #1e40af;
5
+ --secondary-color: #7c3aed;
6
+ --success-color: #10b981;
7
+ --danger-color: #ef4444;
8
+ --warning-color: #f59e0b;
9
+ --gray-50: #f9fafb;
10
+ --gray-100: #f3f4f6;
11
+ --gray-200: #e5e7eb;
12
+ --gray-300: #d1d5db;
13
+ --gray-600: #4b5563;
14
+ --gray-800: #1f2937;
15
+ --gray-900: #111827;
16
+ --sidebar-width: 260px;
17
+ --header-height: 60px;
18
+ --border-radius: 4px;
19
+}
20
+
21
+* {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+}
26
+
27
+body {
28
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
29
+ background-color: var(--gray-100);
30
+ color: var(--gray-800);
31
+ line-height: 1.5;
32
+}
33
+
34
+a {
35
+ color: var(--primary-color);
36
+ text-decoration: none;
37
+}
38
+
39
+a:hover {
40
+ text-decoration: underline;
41
+}
42
+
43
+h1, h2, h3 {
44
+ color: var(--gray-900);
45
+ margin-bottom: 0.5rem;
46
+}
47
+
48
+.page-title {
49
+ font-size: 1.5rem;
50
+ margin-bottom: 1.5rem;
51
+}
52
+
53
+.page-description {
54
+ color: var(--gray-600);
55
+ margin-bottom: 1.5rem;
56
+}
frontend/css/components.css
new
+125
@@ -0,0 +1,125 @@
1
+/* MAPS Connect - Components */
2
+
3
+.card {
4
+ background: white;
5
+ border: 1px solid var(--gray-200);
6
+ border-radius: var(--border-radius);
7
+ padding: 1.5rem;
8
+ margin-bottom: 1rem;
9
+}
10
+
11
+.card-title {
12
+ font-size: 1.1rem;
13
+ margin-bottom: 1rem;
14
+}
15
+
16
+.btn {
17
+ display: inline-block;
18
+ padding: 0.625rem 1.25rem;
19
+ border: none;
20
+ border-radius: var(--border-radius);
21
+ font-size: 0.95rem;
22
+ cursor: pointer;
23
+ text-decoration: none;
24
+ text-align: center;
25
+ transition: opacity 0.2s;
26
+}
27
+
28
+.btn:hover {
29
+ opacity: 0.9;
30
+ text-decoration: none;
31
+}
32
+
33
+.btn-primary {
34
+ background: var(--primary-color);
35
+ color: white;
36
+}
37
+
38
+.btn-secondary {
39
+ background: var(--secondary-color);
40
+ color: white;
41
+}
42
+
43
+.btn-outline {
44
+ background: transparent;
45
+ border: 1px solid var(--primary-color);
46
+ color: var(--primary-color);
47
+}
48
+
49
+.btn-danger {
50
+ background: var(--danger-color);
51
+ color: white;
52
+}
53
+
54
+.btn-sm {
55
+ padding: 0.375rem 0.75rem;
56
+ font-size: 0.875rem;
57
+}
58
+
59
+.form-group {
60
+ margin-bottom: 1rem;
61
+}
62
+
63
+.form-group label {
64
+ display: block;
65
+ margin-bottom: 0.375rem;
66
+ font-weight: 500;
67
+ font-size: 0.9rem;
68
+}
69
+
70
+.form-control {
71
+ width: 100%;
72
+ padding: 0.625rem 0.75rem;
73
+ border: 1px solid var(--gray-300);
74
+ border-radius: var(--border-radius);
75
+ font-size: 1rem;
76
+}
77
+
78
+.form-control:focus {
79
+ outline: none;
80
+ border-color: var(--primary-color);
81
+ box-shadow: 0 0 0 2px rgba(30, 64, 175, 0.15);
82
+}
83
+
84
+.form-hint {
85
+ font-size: 0.8rem;
86
+ color: var(--gray-600);
87
+ margin-top: 0.25rem;
88
+}
89
+
90
+.alert {
91
+ padding: 0.75rem 1rem;
92
+ border-radius: var(--border-radius);
93
+ margin-bottom: 1rem;
94
+}
95
+
96
+.alert-error {
97
+ background: #fef2f2;
98
+ color: #991b1b;
99
+ border: 1px solid #fecaca;
100
+}
101
+
102
+.alert-success {
103
+ background: #ecfdf5;
104
+ color: #065f46;
105
+ border: 1px solid #a7f3d0;
106
+}
107
+
108
+.badge {
109
+ display: inline-block;
110
+ padding: 0.2rem 0.5rem;
111
+ font-size: 0.75rem;
112
+ border-radius: var(--border-radius);
113
+ background: var(--gray-200);
114
+ color: var(--gray-800);
115
+}
116
+
117
+.badge-primary {
118
+ background: #dbeafe;
119
+ color: var(--primary-color);
120
+}
121
+
122
+.badge-success {
123
+ background: #d1fae5;
124
+ color: #065f46;
125
+}
frontend/css/layout.css
new
+156
@@ -0,0 +1,156 @@
1
+/* MAPS Connect - Layout (header, sidebar, main) */
2
+
3
+.app-layout {
4
+ display: flex;
5
+ min-height: 100vh;
6
+}
7
+
8
+.sidebar {
9
+ width: var(--sidebar-width);
10
+ background: var(--gray-900);
11
+ color: white;
12
+ flex-shrink: 0;
13
+ transition: transform 0.3s ease;
14
+}
15
+
16
+.sidebar-header {
17
+ padding: 1.25rem;
18
+ border-bottom: 1px solid var(--gray-800);
19
+}
20
+
21
+.sidebar-header h1 {
22
+ color: white;
23
+ font-size: 1.25rem;
24
+ margin: 0;
25
+}
26
+
27
+.sidebar-nav {
28
+ list-style: none;
29
+ padding: 1rem 0;
30
+}
31
+
32
+.sidebar-nav a {
33
+ display: block;
34
+ padding: 0.75rem 1.25rem;
35
+ color: var(--gray-300);
36
+ text-decoration: none;
37
+ border-left: 3px solid transparent;
38
+}
39
+
40
+.sidebar-nav a:hover,
41
+.sidebar-nav a.active {
42
+ background: var(--gray-800);
43
+ color: white;
44
+ text-decoration: none;
45
+}
46
+
47
+.sidebar-nav a.active {
48
+ border-left-color: var(--primary-color);
49
+}
50
+
51
+.main-wrapper {
52
+ flex: 1;
53
+ display: flex;
54
+ flex-direction: column;
55
+ min-width: 0;
56
+}
57
+
58
+.topbar {
59
+ height: var(--header-height);
60
+ background: white;
61
+ border-bottom: 1px solid var(--gray-200);
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: space-between;
65
+ padding: 0 1.5rem;
66
+}
67
+
68
+.topbar-toggle {
69
+ display: none;
70
+ background: none;
71
+ border: none;
72
+ font-size: 1.5rem;
73
+ cursor: pointer;
74
+ color: var(--gray-800);
75
+}
76
+
77
+.main-content {
78
+ flex: 1;
79
+ padding: 1.5rem;
80
+ overflow-y: auto;
81
+}
82
+
83
+.auth-layout {
84
+ min-height: 100vh;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ padding: 1rem;
89
+}
90
+
91
+.auth-card {
92
+ width: 100%;
93
+ max-width: 420px;
94
+}
95
+
96
+.landing-layout {
97
+ min-height: 100vh;
98
+ display: flex;
99
+ flex-direction: column;
100
+}
101
+
102
+.landing-header {
103
+ padding: 1rem 2rem;
104
+ background: white;
105
+ border-bottom: 1px solid var(--gray-200);
106
+ display: flex;
107
+ justify-content: space-between;
108
+ align-items: center;
109
+}
110
+
111
+.landing-hero {
112
+ flex: 1;
113
+ display: flex;
114
+ flex-direction: column;
115
+ align-items: center;
116
+ justify-content: center;
117
+ text-align: center;
118
+ padding: 3rem 1.5rem;
119
+}
120
+
121
+.landing-hero h1 {
122
+ font-size: 2.5rem;
123
+ margin-bottom: 1rem;
124
+}
125
+
126
+.landing-hero p {
127
+ color: var(--gray-600);
128
+ max-width: 600px;
129
+ margin-bottom: 2rem;
130
+}
131
+
132
+.landing-actions {
133
+ display: flex;
134
+ gap: 1rem;
135
+ flex-wrap: wrap;
136
+ justify-content: center;
137
+}
138
+
139
+@media (max-width: 768px) {
140
+ .sidebar {
141
+ position: fixed;
142
+ top: 0;
143
+ left: 0;
144
+ height: 100vh;
145
+ z-index: 100;
146
+ transform: translateX(-100%);
147
+ }
148
+
149
+ .sidebar.open {
150
+ transform: translateX(0);
151
+ }
152
+
153
+ .topbar-toggle {
154
+ display: block;
155
+ }
156
+}
frontend/css/styles.css
deleted
-24
@@ -1,24 +0,0 @@
1
-/* MAPS Connect - Stylesheet */
2
-
3
-:root {
4
- --primary-color: #1e40af;
5
- --secondary-color: #7c3aed;
6
- --success-color: #10b981;
7
- --danger-color: #ef4444;
8
- --gray-100: #f3f4f6;
9
- --gray-200: #e5e7eb;
10
- --gray-800: #1f2937;
11
- --gray-900: #111827;
12
-}
13
-
14
-* {
15
- margin: 0;
16
- padding: 0;
17
- box-sizing: border-box;
18
-}
19
-
20
-body {
21
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
22
- background-color: var(--gray-100);
23
- color: var(--gray-800);
24
-}
frontend/index.html
+20
-3
@@ -4,10 +4,27 @@
4
<meta charset="UTF-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
<title>MAPS Connect</title>
7
- <link rel="stylesheet" href="css/styles.css">
7
+ <link rel="stylesheet" href="css/base.css">
8
+ <link rel="stylesheet" href="css/layout.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
</head>
11
<body>
10
- <div id="app"></div>
11
- <script src="js/app.js"></script>
12
+ <div class="landing-layout">
13
+ <header class="landing-header">
14
+ <strong>MAPS Connect</strong>
15
+ <a href="login.html" class="btn btn-outline btn-sm">Iniciar sesión</a>
16
+ </header>
17
+ <section class="landing-hero">
18
+ <h1>MAPS Connect</h1>
19
+ <div class="landing-actions">
20
+ <a href="registro.html" class="btn btn-primary">Registrarse</a>
21
+ <a href="login.html" class="btn btn-outline">Iniciar sesión</a>
22
+ </div>
23
+ </section>
24
+ </div>
25
+ <script src="js/auth.js"></script>
26
+ <script>
27
+ Auth.redirectIfAuthenticated();
28
+ </script>
29
</body>
30
</html>
frontend/js/api.js
new
+25
@@ -0,0 +1,25 @@
1
+const API_BASE_URL = 'http://localhost:8080/api';
2
+
3
+const API = {
4
+ async request(endpoint, options = {}) {
5
+ const url = `${API_BASE_URL}${endpoint}`;
6
+ const token = localStorage.getItem('token');
7
+
8
+ const config = {
9
+ headers: {
10
+ 'Content-Type': 'application/json',
11
+ ...(token && { Authorization: `Bearer ${token}` })
12
+ },
13
+ ...options
14
+ };
15
+
16
+ const response = await fetch(url, config);
17
+ const data = await response.json().catch(() => null);
18
+
19
+ if (!response.ok) {
20
+ throw new Error(data?.message || `Error ${response.status}`);
21
+ }
22
+
23
+ return data;
24
+ }
25
+};
frontend/js/app.js
deleted
-33
@@ -1,33 +0,0 @@
1
-// MAPS Connect API Configuration
2
-
3
-const API_BASE_URL = 'http://localhost:8080/api';
4
-
5
-const API = {
6
- async request(endpoint, options = {}) {
7
- const url = `${API_BASE_URL}${endpoint}`;
8
- const token = localStorage.getItem('token');
9
-
10
- const config = {
11
- headers: {
12
- 'Content-Type': 'application/json',
13
- ...token && { 'Authorization': `Bearer ${token}` }
14
- },
15
- ...options
16
- };
17
-
18
- const response = await fetch(url, config);
19
- if (!response.ok) throw new Error(`API Error: ${response.status}`);
20
- return response.json();
21
- },
22
-
23
- // Auth endpoints
24
- login: (email, password) => API.request('/auth/login', {
25
- method: 'POST',
26
- body: JSON.stringify({ email, password })
27
- }),
28
-
29
- register: (nombre, email, password, rol) => API.request('/auth/register', {
30
- method: 'POST',
31
- body: JSON.stringify({ nombre, email, password, rol })
32
- })
33
-};
frontend/js/auth.js
new
+59
@@ -0,0 +1,59 @@
1
+/**
2
+ * MAPS Connect - Autenticación y sesión
3
+ */
4
+
5
+const Auth = {
6
+ TOKEN_KEY: 'token',
7
+ USER_KEY: 'user',
8
+
9
+ saveSession(token, user) {
10
+ localStorage.setItem(this.TOKEN_KEY, token);
11
+ localStorage.setItem(this.USER_KEY, JSON.stringify(user));
12
+ },
13
+
14
+ getToken() {
15
+ return localStorage.getItem(this.TOKEN_KEY);
16
+ },
17
+
18
+ getUser() {
19
+ const user = localStorage.getItem(this.USER_KEY);
20
+ return user ? JSON.parse(user) : null;
21
+ },
22
+
23
+ isAuthenticated() {
24
+ return !!this.getToken();
25
+ },
26
+
27
+ logout() {
28
+ localStorage.removeItem(this.TOKEN_KEY);
29
+ localStorage.removeItem(this.USER_KEY);
30
+ window.location.href = Auth.resolvePath('login.html');
31
+ },
32
+
33
+ /** Redirige a login si no hay sesión. Usar en páginas protegidas. */
34
+ requireAuth() {
35
+ if (!this.isAuthenticated()) {
36
+ window.location.href = this.resolvePath('login.html');
37
+ return false;
38
+ }
39
+ return true;
40
+ },
41
+
42
+ /** Redirige al dashboard si ya hay sesión. Usar en login/registro. */
43
+ redirectIfAuthenticated() {
44
+ if (this.isAuthenticated()) {
45
+ window.location.href = this.resolvePath('pages/inicio.html');
46
+ }
47
+ },
48
+
49
+ resolvePath(relativePath) {
50
+ const inPages = window.location.pathname.includes('/pages/');
51
+ if (inPages && !relativePath.startsWith('../') && !relativePath.startsWith('pages/')) {
52
+ return '../' + relativePath;
53
+ }
54
+ if (!inPages && relativePath.startsWith('pages/')) {
55
+ return relativePath;
56
+ }
57
+ return relativePath;
58
+ }
59
+};
frontend/js/layout.js
new
+68
@@ -0,0 +1,68 @@
1
+/**
2
+ * MAPS Connect - Layout compartido (sidebar + topbar)
3
+ */
4
+
5
+const NAV_ITEMS = [
6
+ { href: 'inicio.html', label: 'Inicio' },
7
+ { href: 'perfil.html', label: 'Mi Perfil' },
8
+ { href: 'foro.html', label: 'Foro de Dudas' },
9
+ { href: 'tips.html', label: 'Tips Académicos' },
10
+ { href: 'recursos.html', label: 'Apuntes' },
11
+ { href: 'mensajes.html', label: 'Mensajes' },
12
+ { href: 'empresarial.html', label: 'Semestre Empresarial' },
13
+ { href: 'circulos.html', label: 'Círculos de Estudio' }
14
+];
15
+
16
+const Layout = {
17
+ init(currentPage) {
18
+ if (!Auth.requireAuth()) return;
19
+
20
+ this.renderSidebar(currentPage);
21
+ this.renderTopbar();
22
+ this.bindToggle();
23
+ },
24
+
25
+ renderSidebar(currentPage) {
26
+ const sidebar = document.getElementById('sidebar');
27
+ if (!sidebar) return;
28
+
29
+ const user = Auth.getUser();
30
+ const navLinks = NAV_ITEMS.map(item => {
31
+ const active = item.href === currentPage ? 'active' : '';
32
+ return `<li><a href="${item.href}" class="${active}">${item.label}</a></li>`;
33
+ }).join('');
34
+
35
+ sidebar.innerHTML = `
36
+ <div class="sidebar-header">
37
+ <h1>MAPS Connect</h1>
38
+ <small>${user?.nombre || 'Usuario'}</small>
39
+ </div>
40
+ <ul class="sidebar-nav">${navLinks}</ul>
41
+ `;
42
+ },
43
+
44
+ renderTopbar() {
45
+ const topbar = document.getElementById('topbar');
46
+ if (!topbar) return;
47
+
48
+ topbar.innerHTML = `
49
+ <button class="topbar-toggle" id="sidebar-toggle" aria-label="Menú">Menú</button>
50
+ <span id="topbar-title"></span>
51
+ <button class="btn btn-sm btn-outline" id="btn-logout">Cerrar sesión</button>
52
+ `;
53
+
54
+ document.getElementById('btn-logout')?.addEventListener('click', () => Auth.logout());
55
+ },
56
+
57
+ bindToggle() {
58
+ document.getElementById('sidebar-toggle')?.addEventListener('click', () => {
59
+ document.getElementById('sidebar')?.classList.toggle('open');
60
+ });
61
+ },
62
+
63
+ setPageTitle(title) {
64
+ const el = document.getElementById('topbar-title');
65
+ if (el) el.textContent = title;
66
+ document.title = `${title} | MAPS Connect`;
67
+ }
68
+};
frontend/js/pages/circulos.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('circulos.html');
3
+ Layout.setPageTitle('Círculos de Estudio');
4
+});
frontend/js/pages/empresarial.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('empresarial.html');
3
+ Layout.setPageTitle('Semestre Empresarial');
4
+});
frontend/js/pages/foro.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('foro.html');
3
+ Layout.setPageTitle('Foro de Dudas');
4
+});
frontend/js/pages/inicio.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('inicio.html');
3
+ Layout.setPageTitle('Inicio');
4
+});
frontend/js/pages/login.js
new
+30
@@ -0,0 +1,30 @@
1
+/** Acceso provisional solo para revisar la interfaz (sin backend). */
2
+const DEV_LOGIN = {
3
+ email: 'demo@tecmilenio.mx',
4
+ password: 'Demo1234',
5
+ user: {
6
+ nombre: 'Usuario Demo',
7
+ email: 'demo@tecmilenio.mx',
8
+ rol: 'ESTUDIANTE'
9
+ }
10
+};
11
+
12
+document.addEventListener('DOMContentLoaded', () => {
13
+ Auth.redirectIfAuthenticated();
14
+
15
+ document.getElementById('login-form').addEventListener('submit', (e) => {
16
+ e.preventDefault();
17
+ Utils.clearAlert('alert-container');
18
+
19
+ const email = document.getElementById('email').value.trim();
20
+ const password = document.getElementById('password').value;
21
+
22
+ if (email === DEV_LOGIN.email && password === DEV_LOGIN.password) {
23
+ Auth.saveSession('token-provisional-dev', DEV_LOGIN.user);
24
+ window.location.href = 'pages/inicio.html';
25
+ return;
26
+ }
27
+
28
+ Utils.showAlert('alert-container', 'Correo o contraseña incorrectos. Usa las credenciales provisionales.');
29
+ });
30
+});
frontend/js/pages/mensajes.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('mensajes.html');
3
+ Layout.setPageTitle('Mensajes');
4
+});
frontend/js/pages/perfil.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('perfil.html');
3
+ Layout.setPageTitle('Mi Perfil');
4
+});
frontend/js/pages/recursos.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('recursos.html');
3
+ Layout.setPageTitle('Apuntes');
4
+});
frontend/js/pages/registro.js
new
+3
@@ -0,0 +1,3 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Auth.redirectIfAuthenticated();
3
+});
frontend/js/pages/tips.js
new
+4
@@ -0,0 +1,4 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+ Layout.init('tips.html');
3
+ Layout.setPageTitle('Tips Académicos');
4
+});
frontend/js/utils.js
new
+12
@@ -0,0 +1,12 @@
1
+const Utils = {
2
+ showAlert(containerId, message, type = 'error') {
3
+ const container = document.getElementById(containerId);
4
+ if (!container) return;
5
+ container.innerHTML = `<div class="alert alert-${type}">${message}</div>`;
6
+ },
7
+
8
+ clearAlert(containerId) {
9
+ const container = document.getElementById(containerId);
10
+ if (container) container.innerHTML = '';
11
+ }
12
+};
frontend/login.html
new
+40
@@ -0,0 +1,40 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Iniciar sesión | MAPS Connect</title>
7
+ <link rel="stylesheet" href="css/base.css">
8
+ <link rel="stylesheet" href="css/layout.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+</head>
11
+<body>
12
+ <div class="auth-layout">
13
+ <div class="auth-card card">
14
+ <h1 class="page-title">Iniciar sesión</h1>
15
+ <div id="alert-container"></div>
16
+ <form id="login-form">
17
+ <div class="form-group">
18
+ <label for="email">Correo</label>
19
+ <input type="email" id="email" class="form-control" required>
20
+ </div>
21
+ <div class="form-group">
22
+ <label for="password">Contraseña</label>
23
+ <input type="password" id="password" class="form-control" required>
24
+ </div>
25
+ <button type="submit" class="btn btn-primary" style="width:100%">Entrar</button>
26
+ </form>
27
+ <p class="form-hint" style="margin-top:1rem;text-align:center">
28
+ Acceso provisional: <strong>demo@tecmilenio.mx</strong> / <strong>Demo1234</strong>
29
+ </p>
30
+ <p style="margin-top:0.5rem;text-align:center">
31
+ <a href="registro.html">Crear cuenta</a>
32
+ </p>
33
+ </div>
34
+ </div>
35
+ <script src="js/api.js"></script>
36
+ <script src="js/auth.js"></script>
37
+ <script src="js/utils.js"></script>
38
+ <script src="js/pages/login.js"></script>
39
+</body>
40
+</html>
frontend/pages/circulos.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Círculos | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/circulos.js"></script>
24
+</body>
25
+</html>
frontend/pages/empresarial.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Semestre Empresarial | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/empresarial.js"></script>
24
+</body>
25
+</html>
frontend/pages/foro.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Foro | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/foro.js"></script>
24
+</body>
25
+</html>
frontend/pages/inicio.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Inicio | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/inicio.js"></script>
24
+</body>
25
+</html>
frontend/pages/mensajes.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mensajes | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/mensajes.js"></script>
24
+</body>
25
+</html>
frontend/pages/perfil.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mi Perfil | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/perfil.js"></script>
24
+</body>
25
+</html>
frontend/pages/recursos.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Apuntes | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/recursos.js"></script>
24
+</body>
25
+</html>
frontend/pages/tips.html
new
+25
@@ -0,0 +1,25 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tips | MAPS Connect</title>
7
+ <link rel="stylesheet" href="../css/base.css">
8
+ <link rel="stylesheet" href="../css/layout.css">
9
+ <link rel="stylesheet" href="../css/components.css">
10
+</head>
11
+<body>
12
+ <div class="app-layout">
13
+ <aside class="sidebar" id="sidebar"></aside>
14
+ <div class="main-wrapper">
15
+ <header class="topbar" id="topbar"></header>
16
+ <main class="main-content" id="main-content"></main>
17
+ </div>
18
+ </div>
19
+ <script src="../js/api.js"></script>
20
+ <script src="../js/auth.js"></script>
21
+ <script src="../js/utils.js"></script>
22
+ <script src="../js/layout.js"></script>
23
+ <script src="../js/pages/tips.js"></script>
24
+</body>
25
+</html>
frontend/registro.html
new
+27
@@ -0,0 +1,27 @@
1
+<!DOCTYPE html>
2
+<html lang="es">
3
+<head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Registro | MAPS Connect</title>
7
+ <link rel="stylesheet" href="css/base.css">
8
+ <link rel="stylesheet" href="css/layout.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+</head>
11
+<body>
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>
21
+ </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>
26
+</body>
27
+</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:3000", "http://localhost:5173")
13
+ .allowedOrigins("http://localhost:5500", "http://localhost:3000", "http://localhost:5173")
14
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
15
.allowedHeaders("*")
16
.allowCredentials(true)
src/main/java/com/tecmilenio/mapsconect/config/SecurityConfig.java
+2
-2
@@ -32,8 +32,8 @@ public class SecurityConfig {
32
.csrf(csrf -> csrf.disable())
33
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
34
.authorizeHttpRequests(authz -> authz
35
- .requestMatchers("/api/auth/**").permitAll()
36
- .requestMatchers("/api/health").permitAll()
35
+ .requestMatchers("/auth/**").permitAll()
36
+ .requestMatchers("/health").permitAll()
37
.anyRequest().authenticated()
38
)
39
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
src/main/java/com/tecmilenio/mapsconect/controller/CarreraController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/carreras")
8
+public class CarreraController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/CertificadoController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/certificados")
8
+public class CertificadoController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/CirculosController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/circulos")
8
+public class CirculosController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/EmpresarialController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/empresarial")
8
+public class EmpresarialController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/EstudianteController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/estudiantes")
8
+public class EstudianteController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/ForoController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/foro")
8
+public class ForoController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/MateriaController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/materias")
8
+public class MateriaController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/MensajesController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/mensajes")
8
+public class MensajesController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/RecursosController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/recursos")
8
+public class RecursosController {
9
+}
src/main/java/com/tecmilenio/mapsconect/controller/TipsController.java
new
+9
@@ -0,0 +1,9 @@
1
+package com.tecmilenio.mapsconect.controller;
2
+
3
+import org.springframework.web.bind.annotation.RequestMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+@RestController
7
+@RequestMapping("/tips")
8
+public class TipsController {
9
+}
src/main/java/com/tecmilenio/mapsconect/dto/ApiResponse.java
+8
@@ -16,6 +16,10 @@ public class ApiResponse<T> {
16
private T data;
17
private String timestamp;
18
19
+ public static <T> ApiResponse<T> success(T data) {
20
+ return success(data, "OK");
21
+ }
22
+
23
public static <T> ApiResponse<T> success(T data, String message) {
24
return ApiResponse.<T>builder()
25
.status(200)
@@ -25,6 +29,10 @@ public class ApiResponse<T> {
29
.build();
30
}
31
32
+ public static <T> ApiResponse<T> error(String message) {
33
+ return error(501, message);
34
+ }
35
+
36
public static <T> ApiResponse<T> error(int status, String message) {
37
return ApiResponse.<T>builder()
38
.status(status)