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)