refactor: Clean frontend - leave only base structure for development
Remove all pre-built code and leave clean environment: - Remove complex HTML template - Remove extensive CSS/responsive code - Remove complete authentication logic - Keep only minimal base files New setup: - index.html (minimal entry point) - css/styles.css (base variables only) - js/app.js (API client skeleton) - frontend/README.md (quick start guide) Structure ready for custom development: ✅ Live Server ready ✅ API configuration available ✅ Monorepo structure intact ✅ Clean workspace for programming Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Daniel Silva committed
Aug 17, 2026 at 18:27 UTC
2ddaf7465c9f8d9ca410447e112f8cb5777081bc
8 files changed
+71
-1806
frontend/README.md
+29
-262
@@ -1,294 +1,61 @@
1
-# MAPS Connect - Frontend
1
+# Frontend - MAPS Connect
2
3
-Frontend minimalista con **HTML5**, **CSS3** y **JavaScript Vanilla** para la plataforma MAPS Connect.
3
+Estructura lista para desarrollar. **HTML5 | CSS3 | JavaScript Vanilla**
4
5
-## 📁 Estructura del Proyecto
5
+## 📁 Estructura
6
7
```
8
frontend/
9
-├── index.html # Página principal (login/dashboard)
9
+├── index.html # Punto de entrada
10
├── css/
11
-│ ├── styles.css # Estilos principales
12
-│ └── responsive.css # Estilos responsivos
11
+│ └── styles.css # Estilos
12
├── js/
14
-│ ├── config.js # Configuración y utilidades
15
-│ ├── auth.js # Lógica de autenticación
16
-│ └── main.js # Lógica principal de la app
17
-├── pages/
18
-│ ├── foro.html # Módulo de foro
19
-│ ├── tips.html # Módulo de tips
20
-│ ├── recursos.html # Módulo de recursos
21
-│ ├── circulos.html # Módulo de círculos
22
-│ ├── mensajes.html # Módulo de mensajería
23
-│ ├── empresarial.html # Módulo empresarial
24
-│ └── perfil.html # Página de perfil
25
-├── assets/ # Imágenes y recursos estáticos
26
-└── README.md # Este archivo
13
+│ └── app.js # Lógica principal + API
14
+├── pages/ # Futuras páginas
15
+└── assets/ # Imágenes
16
```
17
29
-## 🎨 Diseño y Características
30
-
31
-### Minimalista
32
-- ✅ Contornos rectos (sin bordes redondeados)
33
-- ✅ Paleta de colores profesional
34
-- ✅ Interfaz limpia y enfocada
35
-
36
-### Responsive
37
-- ✅ Tablets (768px y menores)
38
-- ✅ Mobile (480px y menores)
39
-- ✅ Sidebar colapsable en dispositivos pequeños
40
-
41
-### Accesibilidad
42
-- ✅ Validaciones de formularios
43
-- ✅ Mensajes de error claros
44
-- ✅ Navegación intuitiva
45
-
46
-## 🚀 Cómo Usar
18
+## 🚀 Iniciar
19
20
### 1. Abrir en Visual Studio Code
49
-
21
```bash
51
-# Desde el directorio raíz del proyecto
22
code frontend/
23
```
24
55
-### 2. Iniciar Servidor Local
56
-
57
-**Opción 1: Usar Live Server (Extensión VS Code)**
58
-- Instalar extensión "Live Server"
59
-- Hacer clic derecho en `index.html`
60
-- Seleccionar "Open with Live Server"
61
-- Se abrirá automáticamente en `http://localhost:5500`
62
-
63
-**Opción 2: Python**
64
-```bash
65
-# Python 3
66
-python -m http.server 8000
67
-
68
-# Luego acceder a http://localhost:8000
69
-```
70
-
71
-**Opción 3: Node.js**
72
-```bash
73
-# Instalar http-server
74
-npm install -g http-server
75
-
76
-# Iniciar servidor
77
-http-server
78
-```
79
-
80
-### 3. Verificar que el Backend está ejecutándose
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
86
-## 📝 Archivos Principales
87
-
88
-### index.html
89
-Página principal con:
90
-- Formulario de login y registro
91
-- Dashboard con estadísticas
92
-- Navegación principal
93
-- Manejo de autenticación
94
-
95
-### css/styles.css
96
-Estilos base:
97
-- Variables CSS (colores, espaciados)
98
-- Componentes reutilizables
99
-- Temas de formularios, botones, tarjetas
100
-- Animaciones
101
-
102
-### css/responsive.css
103
-Estilos responsivos:
104
-- Media queries para tablets
105
-- Media queries para mobile
106
-- Sidebar colapsable
107
-- Grid adaptable
108
-
109
-### js/config.js
110
-Configuración y utilidades:
111
-- Endpoints de API
112
-- Función `fetchAPI()` con manejo de errores
113
-- Manejo de JWT tokens
114
-- Validaciones (email, contraseña, nombre)
115
-- Funciones de utilidad
116
-
117
-### js/auth.js
118
-Autenticación:
119
-- Lógica de login
120
-- Lógica de registro
121
-- Toggle entre modos
122
-- Validaciones de formulario
123
-
124
-### js/main.js
125
-Lógica principal:
126
-- Inicialización de la app
127
-- Control de vistas (auth vs dashboard)
128
-- Logout
129
-- Manejo global de errores
130
-
131
-## 🔐 Autenticación
132
-
133
-### Flujo de Login
134
-
135
-1. Usuario ingresa email y contraseña
136
-2. Frontend valida formato (@tecmilenio.mx)
137
-3. Se envía POST a `/auth/login`
138
-4. Backend retorna JWT token
139
-5. Token se almacena en `localStorage`
140
-6. Se carga el dashboard
141
-
142
-### Flujo de Registro
143
-
144
-1. Usuario completa formulario de registro
145
-2. Validaciones en frontend (email, contraseña, nombre)
146
-3. Se envía POST a `/auth/register`
147
-4. Backend crea usuario y retorna token
148
-5. Auto-login posterior
149
-
150
-### Tokens JWT
151
-
152
-El token se envía en todas las requests:
35
+## 🔗 API Base URL
36
```javascript
154
-Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
155
-```
156
-
157
-Se valida automáticamente en cada petición y se renueva si es necesario.
158
-
159
-## 🎨 Colores y Paleta
160
-
161
-```css
162
---primary-color: #1e40af; /* Azul profesional */
163
---secondary-color: #7c3aed; /* Púrpura */
164
---success-color: #10b981; /* Verde */
165
---warning-color: #f59e0b; /* Ámbar */
166
---danger-color: #ef4444; /* Rojo */
167
---gray-100 a gray-900: /* Escala de grises */
37
+http://localhost:8080/api
38
```
39
170
-## 📱 Breakpoints Responsivos
171
-
172
-- **Desktop**: > 768px (Sidebar 250px)
173
-- **Tablet**: ≤ 768px (Sidebar 70px)
174
-- **Mobile**: ≤ 480px (Sidebar 60px)
175
-
176
-## 🔧 Desarrollo
177
-
178
-### Agregar Nuevas Páginas
179
-
180
-1. Crear archivo HTML en `pages/`
181
-```html
182
-<!DOCTYPE html>
183
-<html lang="es">
184
-<head>
185
- <meta charset="UTF-8">
186
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
187
- <title>Página Nueva - MAPS Connect</title>
188
- <link rel="stylesheet" href="../css/styles.css">
189
- <link rel="stylesheet" href="../css/responsive.css">
190
-</head>
191
-<body>
192
- <!-- Incluir sidebar -->
193
- <!-- Contenido -->
194
- <script src="../js/config.js"></script>
195
- <script src="../js/main.js"></script>
196
-</body>
197
-</html>
198
-```
199
-
200
-2. Actualizar link en `index.html`:
201
-```html
202
-<li><a href="pages/nueva-pagina.html" class="nav-link">Nueva Página</a></li>
203
-```
204
-
205
-### Llamar Endpoints de API
40
+## 📝 Usar API
41
42
```javascript
208
-// GET
209
-const data = await fetchAPI('/endpoint', 'GET');
210
-
211
-// POST
212
-const response = await fetchAPI('/endpoint', 'POST', {
213
- campo1: 'valor1',
214
- campo2: 'valor2'
215
-});
216
-
217
-// PUT
218
-const updated = await fetchAPI('/endpoint/1', 'PUT', {
219
- nombre: 'nuevo nombre'
220
-});
43
+// En app.js está disponible el objeto API
44
+await API.login(email, password);
45
+await API.register(nombre, email, password, rol);
46
222
-// DELETE
223
-await fetchAPI('/endpoint/1', 'DELETE');
47
+// O hacer requests custom
48
+await API.request('/endpoint', { method: 'POST', body: JSON.stringify({...}) });
49
```
50
226
-### Validaciones Disponibles
227
-
228
-```javascript
229
-// Email institucional
230
-isValidInstitutionalEmail('usuario@tecmilenio.mx'); // true
231
-
232
-// Contraseña (8+ chars, mayúscula, número)
233
-isValidPassword('Password123'); // true
234
-
235
-// Nombre completo
236
-isValidName('Juan Pérez'); // true
237
-```
238
-
239
-## 🐛 Debugging
240
-
241
-### Verificar Conexión con Backend
242
-
243
-```javascript
244
-// En la consola del navegador
245
-fetch('http://localhost:8080/api/health')
246
- .then(r => r.json())
247
- .then(d => console.log(d))
248
- .catch(e => console.error(e));
249
-```
250
-
251
-### Ver Token JWT
252
-
253
-```javascript
254
-// En la consola
255
-localStorage.getItem('authToken');
256
-```
257
-
258
-### Ver Datos de Usuario
259
-
260
-```javascript
261
-// En la consola
262
-localStorage.getItem('currentUser');
51
+## 🎨 Colores (en CSS)
52
+```css
53
+--primary-color: #1e40af
54
+--secondary-color: #7c3aed
55
+--success-color: #10b981
56
+--danger-color: #ef4444
57
```
58
265
-## 📋 Próximas Implementaciones
266
-
267
-- [ ] Módulo de Foro (dudas y respuestas)
268
-- [ ] Módulo de Tips (consejos académicos)
269
-- [ ] Repositorio de Recursos (apuntes)
270
-- [ ] Círculos de Estudio
271
-- [ ] Mensajería privada
272
-- [ ] Semestre Empresarial
273
-- [ ] Gestión de Perfil
274
-- [ ] Búsqueda y filtros
275
-- [ ] Notificaciones
276
-- [ ] Paginación
277
-
278
-## 🚦 Estado de Desarrollo
279
-
280
-**Actual**: Autenticación básica (Login/Registro)
281
-**Próximo**: Módulos principales
282
-
283
-## 📞 Soporte
284
-
285
-Para problemas o preguntas, consulta:
286
-- README.md del proyecto (raíz)
287
-- Documentación de API Backend
288
-- Especificación del Proyecto
289
-
59
---
60
292
-**Última actualización**: Agosto 2024
293
-**Versión**: 1.0.0
294
-**Tecnología**: HTML5 | CSS3 | JavaScript Vanilla
61
+**¡Listo para programar! 🚀**
frontend/css/responsive.css
deleted
-157
@@ -1,157 +0,0 @@
1
-/* ========================
2
- RESPONSIVE DESIGN
3
- ======================== */
4
-
5
-/* Tablets (768px and below) */
6
-@media (max-width: 768px) {
7
- .sidebar {
8
- width: 70px;
9
- padding: 20px 0;
10
- }
11
-
12
- .logo {
13
- font-size: 20px;
14
- }
15
-
16
- .nav-link {
17
- padding: 12px 8px;
18
- text-align: center;
19
- font-size: 12px;
20
- }
21
-
22
- .nav-link:hover,
23
- .nav-link.active {
24
- border-left: 3px solid var(--primary-color);
25
- padding-left: 5px;
26
- }
27
-
28
- .container {
29
- margin-left: 70px;
30
- }
31
-
32
- main {
33
- padding: 20px;
34
- }
35
-
36
- .header h1 {
37
- font-size: 24px;
38
- }
39
-
40
- .subtitle {
41
- font-size: 14px;
42
- }
43
-
44
- .stats-grid {
45
- grid-template-columns: repeat(2, 1fr);
46
- }
47
-
48
- .footer {
49
- margin-left: 70px;
50
- }
51
-
52
- .global-error {
53
- right: 10px;
54
- left: 10px;
55
- max-width: none;
56
- }
57
-}
58
-
59
-/* Mobile (480px and below) */
60
-@media (max-width: 480px) {
61
- .sidebar {
62
- width: 60px;
63
- }
64
-
65
- .sidebar-header {
66
- flex-direction: column;
67
- gap: 15px;
68
- }
69
-
70
- .logo {
71
- font-size: 18px;
72
- text-align: center;
73
- width: 100%;
74
- }
75
-
76
- .toggle-btn {
77
- display: block;
78
- }
79
-
80
- .nav-link {
81
- padding: 10px 6px;
82
- font-size: 11px;
83
- }
84
-
85
- .container {
86
- margin-left: 60px;
87
- }
88
-
89
- main {
90
- padding: 15px;
91
- }
92
-
93
- .header {
94
- margin-bottom: 20px;
95
- }
96
-
97
- .header h1 {
98
- font-size: 20px;
99
- }
100
-
101
- .subtitle {
102
- font-size: 12px;
103
- }
104
-
105
- .auth-box {
106
- padding: 20px 15px;
107
- }
108
-
109
- .stats-grid {
110
- grid-template-columns: 1fr;
111
- gap: 15px;
112
- }
113
-
114
- .stat-card {
115
- padding: 20px 15px;
116
- }
117
-
118
- .stat-card h3 {
119
- font-size: 24px;
120
- }
121
-
122
- .activity-section {
123
- padding: 20px 15px;
124
- }
125
-
126
- .activity-section h3 {
127
- font-size: 16px;
128
- }
129
-
130
- .footer {
131
- margin-left: 60px;
132
- font-size: 12px;
133
- }
134
-
135
- .global-error {
136
- top: 10px;
137
- right: 10px;
138
- left: 10px;
139
- max-width: none;
140
- font-size: 12px;
141
- }
142
-}
143
-
144
-/* Toggle Sidebar on Mobile */
145
-@media (max-width: 768px) {
146
- .sidebar.collapsed {
147
- width: 60px;
148
- }
149
-
150
- .sidebar.collapsed .nav-link {
151
- font-size: 0;
152
- }
153
-
154
- .sidebar.collapsed .logo {
155
- font-size: 16px;
156
- }
157
-}
frontend/css/styles.css
+6
-526
@@ -1,27 +1,14 @@
1
-/* ========================
2
- MAPS CONNECT - STYLES
3
- Minimalista con contornos rectos
4
- ======================== */
1
+/* MAPS Connect - Stylesheet */
2
3
:root {
7
- --primary-color: #1e40af; /* Azul profesional */
8
- --secondary-color: #7c3aed; /* Púrpura */
9
- --success-color: #10b981; /* Verde */
10
- --warning-color: #f59e0b; /* Ámbar */
11
- --danger-color: #ef4444; /* Rojo */
4
+ --primary-color: #1e40af;
5
+ --secondary-color: #7c3aed;
6
+ --success-color: #10b981;
7
+ --danger-color: #ef4444;
8
--gray-100: #f3f4f6;
9
--gray-200: #e5e7eb;
14
- --gray-300: #d1d5db;
15
- --gray-400: #9ca3af;
16
- --gray-500: #6b7280;
17
- --gray-600: #4b5563;
18
- --gray-700: #374151;
10
--gray-800: #1f2937;
11
--gray-900: #111827;
21
-
22
- --border-radius: 0; /* Sin bordes redondeados */
23
- --box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
24
- --transition: all 0.3s ease;
12
}
13
14
* {
@@ -30,515 +17,8 @@
17
box-sizing: border-box;
18
}
19
33
-html {
34
- scroll-behavior: smooth;
35
-}
36
-
20
body {
38
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
39
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
40
- sans-serif;
41
- -webkit-font-smoothing: antialiased;
42
- -moz-osx-font-smoothing: grayscale;
21
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
22
background-color: var(--gray-100);
23
color: var(--gray-800);
45
- line-height: 1.6;
46
- display: flex;
47
-}
48
-
49
-/* ========================
50
- SIDEBAR NAVIGATION
51
- ======================== */
52
-
53
-.sidebar {
54
- width: 250px;
55
- background-color: var(--gray-900);
56
- color: white;
57
- position: fixed;
58
- left: 0;
59
- top: 0;
60
- height: 100vh;
61
- overflow-y: auto;
62
- padding: 20px 0;
63
- box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
64
- transition: var(--transition);
65
- z-index: 100;
66
-}
67
-
68
-.sidebar-header {
69
- display: flex;
70
- justify-content: space-between;
71
- align-items: center;
72
- padding: 0 20px 30px 20px;
73
- border-bottom: 1px solid var(--gray-700);
74
-}
75
-
76
-.logo {
77
- font-size: 28px;
78
- font-weight: 700;
79
- letter-spacing: 2px;
80
- color: var(--primary-color);
81
-}
82
-
83
-.toggle-btn {
84
- display: none;
85
- background: none;
86
- border: none;
87
- color: white;
88
- font-size: 20px;
89
- cursor: pointer;
90
-}
91
-
92
-.nav-menu {
93
- list-style: none;
94
- padding: 0 10px;
95
- margin-bottom: auto;
96
-}
97
-
98
-.nav-menu li {
99
- margin-bottom: 5px;
100
-}
101
-
102
-.nav-link {
103
- display: block;
104
- padding: 12px 15px;
105
- color: var(--gray-300);
106
- text-decoration: none;
107
- border: 1px solid transparent;
108
- transition: var(--transition);
109
-}
110
-
111
-.nav-link:hover {
112
- background-color: var(--gray-800);
113
- color: white;
114
- border-left: 3px solid var(--primary-color);
115
- padding-left: 12px;
116
-}
117
-
118
-.nav-link.active {
119
- background-color: var(--primary-color);
120
- color: white;
121
- border-left: 3px solid var(--secondary-color);
122
- padding-left: 12px;
123
-}
124
-
125
-.sidebar-footer {
126
- padding: 20px 10px;
127
- border-top: 1px solid var(--gray-700);
128
-}
129
-
130
-.logout-btn {
131
- width: 100%;
132
- padding: 10px 15px;
133
- background-color: var(--danger-color);
134
- color: white;
135
- border: none;
136
- cursor: pointer;
137
- font-size: 14px;
138
- font-weight: 600;
139
- transition: var(--transition);
140
-}
141
-
142
-.logout-btn:hover {
143
- background-color: #dc2626;
144
-}
145
-
146
-/* ========================
147
- MAIN CONTAINER
148
- ======================== */
149
-
150
-.container {
151
- flex: 1;
152
- margin-left: 250px;
153
- background-color: var(--gray-100);
154
- min-height: 100vh;
155
- display: flex;
156
- flex-direction: column;
157
-}
158
-
159
-main {
160
- flex: 1;
161
- padding: 40px;
162
- max-width: 1200px;
163
- margin: 0 auto;
164
- width: 100%;
165
-}
166
-
167
-/* ========================
168
- HEADER
169
- ======================== */
170
-
171
-.header {
172
- margin-bottom: 40px;
173
- text-align: center;
174
-}
175
-
176
-.header h1 {
177
- font-size: 32px;
178
- margin-bottom: 10px;
179
- color: var(--gray-900);
180
-}
181
-
182
-.subtitle {
183
- font-size: 16px;
184
- color: var(--gray-500);
185
-}
186
-
187
-/* ========================
188
- AUTHENTICATION SECTION
189
- ======================== */
190
-
191
-.auth-section {
192
- display: flex;
193
- align-items: center;
194
- justify-content: center;
195
- min-height: 80vh;
196
-}
197
-
198
-.auth-container {
199
- width: 100%;
200
- max-width: 400px;
201
-}
202
-
203
-.auth-box {
204
- background-color: white;
205
- padding: 40px 30px;
206
- box-shadow: var(--box-shadow);
207
- border: 1px solid var(--gray-200);
208
-}
209
-
210
-.auth-box h2 {
211
- font-size: 24px;
212
- margin-bottom: 30px;
213
- color: var(--gray-900);
214
- text-align: center;
215
-}
216
-
217
-/* ========================
218
- FORM STYLES
219
- ======================== */
220
-
221
-.form {
222
- width: 100%;
223
-}
224
-
225
-.form-group {
226
- margin-bottom: 20px;
227
- display: flex;
228
- flex-direction: column;
229
-}
230
-
231
-.form-group label {
232
- font-size: 14px;
233
- font-weight: 600;
234
- margin-bottom: 8px;
235
- color: var(--gray-700);
236
-}
237
-
238
-.form-group input,
239
-.form-group select,
240
-.form-group textarea {
241
- padding: 10px 12px;
242
- border: 1px solid var(--gray-300);
243
- background-color: white;
244
- font-size: 14px;
245
- font-family: inherit;
246
- transition: var(--transition);
247
-}
248
-
249
-.form-group input:focus,
250
-.form-group select:focus,
251
-.form-group textarea:focus {
252
- outline: none;
253
- border-color: var(--primary-color);
254
- box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
255
-}
256
-
257
-.error-message {
258
- font-size: 12px;
259
- color: var(--danger-color);
260
- margin-top: 4px;
261
- display: none;
262
-}
263
-
264
-.error-message.show {
265
- display: block;
266
-}
267
-
268
-.error-box {
269
- background-color: #fee2e2;
270
- border: 1px solid #fca5a5;
271
- color: var(--danger-color);
272
- padding: 12px;
273
- margin-bottom: 20px;
274
- font-size: 14px;
275
-}
276
-
277
-/* ========================
278
- BUTTONS
279
- ======================== */
280
-
281
-.btn {
282
- padding: 10px 16px;
283
- border: none;
284
- cursor: pointer;
285
- font-size: 14px;
286
- font-weight: 600;
287
- transition: var(--transition);
288
- border: 1px solid transparent;
289
-}
290
-
291
-.btn-primary {
292
- background-color: var(--primary-color);
293
- color: white;
294
- width: 100%;
295
- margin-top: 10px;
296
-}
297
-
298
-.btn-primary:hover {
299
- background-color: #1e3a8a;
300
-}
301
-
302
-.btn-primary:disabled {
303
- background-color: var(--gray-400);
304
- cursor: not-allowed;
305
-}
306
-
307
-.btn-secondary {
308
- background-color: var(--gray-200);
309
- color: var(--gray-800);
310
-}
311
-
312
-.btn-secondary:hover {
313
- background-color: var(--gray-300);
314
-}
315
-
316
-.btn-success {
317
- background-color: var(--success-color);
318
- color: white;
319
-}
320
-
321
-.btn-success:hover {
322
- background-color: #059669;
323
-}
324
-
325
-.link-btn {
326
- background: none;
327
- border: none;
328
- color: var(--primary-color);
329
- cursor: pointer;
330
- font-weight: 600;
331
- padding: 0;
332
- text-decoration: underline;
333
- font-size: 14px;
334
-}
335
-
336
-.link-btn:hover {
337
- color: #1e3a8a;
338
-}
339
-
340
-/* ========================
341
- AUTH TOGGLE
342
- ======================== */
343
-
344
-.auth-toggle {
345
- text-align: center;
346
- margin-top: 20px;
347
- padding-top: 20px;
348
- border-top: 1px solid var(--gray-200);
349
- font-size: 14px;
350
- color: var(--gray-600);
351
-}
352
-
353
-/* ========================
354
- LOADING SPINNER
355
- ======================== */
356
-
357
-.loading-spinner {
358
- display: flex;
359
- flex-direction: column;
360
- align-items: center;
361
- gap: 15px;
362
- margin-top: 20px;
363
-}
364
-
365
-.spinner {
366
- width: 30px;
367
- height: 30px;
368
- border: 3px solid var(--gray-200);
369
- border-top-color: var(--primary-color);
370
- border-radius: 50%;
371
- animation: spin 1s linear infinite;
372
-}
373
-
374
-@keyframes spin {
375
- to {
376
- transform: rotate(360deg);
377
- }
378
-}
379
-
380
-/* ========================
381
- DASHBOARD SECTION
382
- ======================== */
383
-
384
-.dashboard-section {
385
- padding: 40px;
386
-}
387
-
388
-.welcome-box {
389
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
390
- color: white;
391
- padding: 30px;
392
- margin-bottom: 40px;
393
- border: 1px solid rgba(255, 255, 255, 0.1);
394
-}
395
-
396
-.welcome-box h2 {
397
- font-size: 28px;
398
- margin-bottom: 10px;
399
-}
400
-
401
-.welcome-box p {
402
- font-size: 16px;
403
- opacity: 0.9;
404
-}
405
-
406
-/* ========================
407
- STATS GRID
408
- ======================== */
409
-
410
-.stats-grid {
411
- display: grid;
412
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
413
- gap: 20px;
414
- margin-bottom: 40px;
415
-}
416
-
417
-.stat-card {
418
- background-color: white;
419
- padding: 30px;
420
- border: 1px solid var(--gray-200);
421
- box-shadow: var(--box-shadow);
422
- text-align: center;
423
-}
424
-
425
-.stat-card h3 {
426
- font-size: 32px;
427
- color: var(--primary-color);
428
- margin-bottom: 10px;
429
-}
430
-
431
-.stat-card p {
432
- font-size: 14px;
433
- color: var(--gray-600);
434
-}
435
-
436
-/* ========================
437
- ACTIVITY SECTION
438
- ======================== */
439
-
440
-.activity-section {
441
- background-color: white;
442
- padding: 30px;
443
- border: 1px solid var(--gray-200);
444
- box-shadow: var(--box-shadow);
445
-}
446
-
447
-.activity-section h3 {
448
- margin-bottom: 20px;
449
- font-size: 18px;
450
- color: var(--gray-900);
451
- border-bottom: 2px solid var(--primary-color);
452
- padding-bottom: 10px;
453
-}
454
-
455
-.activity-list {
456
- display: flex;
457
- flex-direction: column;
458
- gap: 15px;
459
-}
460
-
461
-.activity-item {
462
- padding: 15px;
463
- background-color: var(--gray-50);
464
- border-left: 3px solid var(--primary-color);
465
-}
466
-
467
-.empty-state {
468
- text-align: center;
469
- color: var(--gray-400);
470
- padding: 40px 20px;
471
-}
472
-
473
-/* ========================
474
- FOOTER
475
- ======================== */
476
-
477
-.footer {
478
- background-color: var(--gray-900);
479
- color: white;
480
- text-align: center;
481
- padding: 20px;
482
- font-size: 14px;
483
- margin-left: 250px;
484
-}
485
-
486
-/* ========================
487
- GLOBAL ERROR
488
- ======================== */
489
-
490
-.global-error {
491
- position: fixed;
492
- top: 20px;
493
- right: 20px;
494
- background-color: var(--danger-color);
495
- color: white;
496
- padding: 15px 20px;
497
- border: 1px solid #dc2626;
498
- max-width: 400px;
499
- z-index: 1000;
500
- animation: slideIn 0.3s ease;
501
-}
502
-
503
-@keyframes slideIn {
504
- from {
505
- transform: translateX(400px);
506
- opacity: 0;
507
- }
508
- to {
509
- transform: translateX(0);
510
- opacity: 1;
511
- }
512
-}
513
-
514
-/* ========================
515
- UTILITIES
516
- ======================== */
517
-
518
-.text-center {
519
- text-align: center;
520
-}
521
-
522
-.mt-20 {
523
- margin-top: 20px;
524
-}
525
-
526
-.mb-20 {
527
- margin-bottom: 20px;
528
-}
529
-
530
-.p-20 {
531
- padding: 20px;
532
-}
533
-
534
-.text-muted {
535
- color: var(--gray-500);
536
-}
537
-
538
-.text-danger {
539
- color: var(--danger-color);
540
-}
541
-
542
-.text-success {
543
- color: var(--success-color);
24
}
frontend/index.html
+3
-187
@@ -3,195 +3,11 @@
3
<head>
4
<meta charset="UTF-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="description" content="MAPS Connect - Plataforma Académica y de Mentoría">
7
- <title>MAPS Connect - Universidad Tecmilenio</title>
8
-
6
+ <title>MAPS Connect</title>
7
<link rel="stylesheet" href="css/styles.css">
10
- <link rel="stylesheet" href="css/responsive.css">
8
</head>
9
<body>
13
- <!-- Sidebar Navigation -->
14
- <nav class="sidebar" id="sidebar">
15
- <div class="sidebar-header">
16
- <h1 class="logo">MAPS</h1>
17
- <button class="toggle-btn" id="toggleBtn">☰</button>
18
- </div>
19
-
20
- <ul class="nav-menu">
21
- <li><a href="index.html" class="nav-link active">Home</a></li>
22
- <li><a href="pages/foro.html" class="nav-link">Foro</a></li>
23
- <li><a href="pages/tips.html" class="nav-link">Tips</a></li>
24
- <li><a href="pages/recursos.html" class="nav-link">Recursos</a></li>
25
- <li><a href="pages/circulos.html" class="nav-link">Círculos</a></li>
26
- <li><a href="pages/mensajes.html" class="nav-link">Mensajes</a></li>
27
- <li><a href="pages/empresarial.html" class="nav-link">Empresarial</a></li>
28
- <li><a href="pages/perfil.html" class="nav-link">Perfil</a></li>
29
- </ul>
30
-
31
- <div class="sidebar-footer">
32
- <button id="logoutBtn" class="logout-btn">Cerrar Sesión</button>
33
- </div>
34
- </nav>
35
-
36
- <!-- Main Content -->
37
- <main class="container">
38
- <!-- Header -->
39
- <header class="header">
40
- <h1>Bienvenido a MAPS Connect</h1>
41
- <p class="subtitle">Plataforma Académica y de Mentoría - Universidad Tecmilenio</p>
42
- </header>
43
-
44
- <!-- Auth Section (Hidden when logged in) -->
45
- <section id="authSection" class="auth-section">
46
- <div class="auth-container">
47
- <div class="auth-box">
48
- <h2 id="authTitle">Iniciar Sesión</h2>
49
-
50
- <form id="authForm" class="form">
51
- <!-- Login Fields -->
52
- <div id="loginFields">
53
- <div class="form-group">
54
- <label for="loginEmail">Email Institucional</label>
55
- <input
56
- type="email"
57
- id="loginEmail"
58
- placeholder="usuario@tecmilenio.mx"
59
- required
60
- >
61
- <span class="error-message" id="loginEmailError"></span>
62
- </div>
63
-
64
- <div class="form-group">
65
- <label for="loginPassword">Contraseña</label>
66
- <input
67
- type="password"
68
- id="loginPassword"
69
- placeholder="••••••••"
70
- required
71
- >
72
- <span class="error-message" id="loginPasswordError"></span>
73
- </div>
74
- </div>
75
-
76
- <!-- Register Fields (Hidden by default) -->
77
- <div id="registerFields" style="display: none;">
78
- <div class="form-group">
79
- <label for="registerName">Nombre Completo</label>
80
- <input
81
- type="text"
82
- id="registerName"
83
- placeholder="Juan Pérez Gómez"
84
- required
85
- >
86
- <span class="error-message" id="registerNameError"></span>
87
- </div>
88
-
89
- <div class="form-group">
90
- <label for="registerEmail">Email Institucional</label>
91
- <input
92
- type="email"
93
- id="registerEmail"
94
- placeholder="usuario@tecmilenio.mx"
95
- required
96
- >
97
- <span class="error-message" id="registerEmailError"></span>
98
- </div>
99
-
100
- <div class="form-group">
101
- <label for="registerPassword">Contraseña</label>
102
- <input
103
- type="password"
104
- id="registerPassword"
105
- placeholder="••••••••"
106
- required
107
- >
108
- <span class="error-message" id="registerPasswordError"></span>
109
- </div>
110
-
111
- <div class="form-group">
112
- <label for="registerRole">Tipo de Usuario</label>
113
- <select id="registerRole" required>
114
- <option value="">Seleccionar...</option>
115
- <option value="STUDENT">Estudiante</option>
116
- <option value="PROFESSOR">Profesor</option>
117
- </select>
118
- <span class="error-message" id="registerRoleError"></span>
119
- </div>
120
- </div>
121
-
122
- <!-- Form Error -->
123
- <div id="formError" class="error-box" style="display: none;"></div>
124
-
125
- <!-- Submit Button -->
126
- <button type="submit" class="btn btn-primary" id="authSubmitBtn">
127
- Iniciar Sesión
128
- </button>
129
-
130
- <!-- Loading State -->
131
- <div id="loadingSpinner" class="loading-spinner" style="display: none;">
132
- <div class="spinner"></div>
133
- <p>Cargando...</p>
134
- </div>
135
- </form>
136
-
137
- <!-- Toggle Auth Form -->
138
- <div class="auth-toggle">
139
- <p id="toggleText">¿No tienes cuenta?
140
- <button type="button" id="toggleAuthBtn" class="link-btn">Regístrate</button>
141
- </p>
142
- </div>
143
- </div>
144
- </div>
145
- </section>
146
-
147
- <!-- Dashboard Section (Hidden when logged out) -->
148
- <section id="dashboardSection" class="dashboard-section" style="display: none;">
149
- <div class="welcome-box">
150
- <h2>Bienvenido, <span id="userName">Usuario</span></h2>
151
- <p>Conecta con tu comunidad académica</p>
152
- </div>
153
-
154
- <!-- Quick Stats -->
155
- <div class="stats-grid">
156
- <div class="stat-card">
157
- <h3>15</h3>
158
- <p>Dudas Resueltas</p>
159
- </div>
160
- <div class="stat-card">
161
- <h3>42</h3>
162
- <p>Tips Compartidos</p>
163
- </div>
164
- <div class="stat-card">
165
- <h3>8</h3>
166
- <p>Círculos Activos</p>
167
- </div>
168
- <div class="stat-card">
169
- <h3>120</h3>
170
- <p>Puntos de Reputación</p>
171
- </div>
172
- </div>
173
-
174
- <!-- Recent Activity -->
175
- <div class="activity-section">
176
- <h3>Actividad Reciente</h3>
177
- <div id="activityContainer" class="activity-list">
178
- <p class="empty-state">Cargando actividad...</p>
179
- </div>
180
- </div>
181
- </section>
182
-
183
- <!-- Error Message -->
184
- <div id="globalError" class="global-error" style="display: none;"></div>
185
- </main>
186
-
187
- <!-- Footer -->
188
- <footer class="footer">
189
- <p>© 2024 MAPS Connect - Universidad Tecmilenio. Todos los derechos reservados.</p>
190
- </footer>
191
-
192
- <!-- Scripts -->
193
- <script src="js/config.js"></script>
194
- <script src="js/auth.js"></script>
195
- <script src="js/main.js"></script>
10
+ <div id="app"></div>
11
+ <script src="js/app.js"></script>
12
</body>
13
</html>
frontend/js/app.js
new
+33
@@ -0,0 +1,33 @@
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
deleted
-249
@@ -1,249 +0,0 @@
1
-/* ========================
2
- AUTHENTICATION LOGIC
3
- ======================== */
4
-
5
-const authForm = document.getElementById('authForm');
6
-const authTitle = document.getElementById('authTitle');
7
-const authSubmitBtn = document.getElementById('authSubmitBtn');
8
-const toggleAuthBtn = document.getElementById('toggleAuthBtn');
9
-const toggleText = document.getElementById('toggleText');
10
-const formError = document.getElementById('formError');
11
-const loadingSpinner = document.getElementById('loadingSpinner');
12
-
13
-// Form fields elements
14
-const loginFields = document.getElementById('loginFields');
15
-const registerFields = document.getElementById('registerFields');
16
-const loginEmail = document.getElementById('loginEmail');
17
-const loginPassword = document.getElementById('loginPassword');
18
-const registerName = document.getElementById('registerName');
19
-const registerEmail = document.getElementById('registerEmail');
20
-const registerPassword = document.getElementById('registerPassword');
21
-const registerRole = document.getElementById('registerRole');
22
-
23
-let isLoginMode = true;
24
-
25
-/**
26
- * Alterna entre modo login y registro
27
- */
28
-function toggleAuthMode() {
29
- isLoginMode = !isLoginMode;
30
-
31
- if (isLoginMode) {
32
- authTitle.textContent = 'Iniciar Sesión';
33
- authSubmitBtn.textContent = 'Iniciar Sesión';
34
- toggleText.innerHTML = '¿No tienes cuenta? <button type="button" id="toggleAuthBtn" class="link-btn">Regístrate</button>';
35
- show(loginFields);
36
- hide(registerFields);
37
- } else {
38
- authTitle.textContent = 'Crear Cuenta';
39
- authSubmitBtn.textContent = 'Registrarse';
40
- toggleText.innerHTML = '¿Ya tienes cuenta? <button type="button" id="toggleAuthBtn" class="link-btn">Inicia Sesión</button>';
41
- hide(loginFields);
42
- show(registerFields);
43
- }
44
-
45
- clearForm(authForm);
46
- formError.style.display = 'none';
47
-
48
- // Re-attach event listener al nuevo botón
49
- document.getElementById('toggleAuthBtn').addEventListener('click', (e) => {
50
- e.preventDefault();
51
- toggleAuthMode();
52
- });
53
-}
54
-
55
-/**
56
- * Valida formulario de login
57
- * @returns {boolean}
58
- */
59
-function validateLoginForm() {
60
- let isValid = true;
61
- const emailError = document.getElementById('loginEmailError');
62
- const passwordError = document.getElementById('loginPasswordError');
63
-
64
- // Validar email
65
- if (!loginEmail.value.trim()) {
66
- emailError.textContent = 'El email es requerido';
67
- emailError.classList.add('show');
68
- isValid = false;
69
- } else if (!isValidInstitutionalEmail(loginEmail.value)) {
70
- emailError.textContent = 'Debe usar email institucional (@tecmilenio.mx)';
71
- emailError.classList.add('show');
72
- isValid = false;
73
- } else {
74
- emailError.classList.remove('show');
75
- }
76
-
77
- // Validar password
78
- if (!loginPassword.value.trim()) {
79
- passwordError.textContent = 'La contraseña es requerida';
80
- passwordError.classList.add('show');
81
- isValid = false;
82
- } else {
83
- passwordError.classList.remove('show');
84
- }
85
-
86
- return isValid;
87
-}
88
-
89
-/**
90
- * Valida formulario de registro
91
- * @returns {boolean}
92
- */
93
-function validateRegisterForm() {
94
- let isValid = true;
95
- const nameError = document.getElementById('registerNameError');
96
- const emailError = document.getElementById('registerEmailError');
97
- const passwordError = document.getElementById('registerPasswordError');
98
- const roleError = document.getElementById('registerRoleError');
99
-
100
- // Validar nombre
101
- if (!registerName.value.trim()) {
102
- nameError.textContent = 'El nombre es requerido';
103
- nameError.classList.add('show');
104
- isValid = false;
105
- } else if (!isValidName(registerName.value)) {
106
- nameError.textContent = 'Ingresa tu nombre completo (mínimo 3 caracteres)';
107
- nameError.classList.add('show');
108
- isValid = false;
109
- } else {
110
- nameError.classList.remove('show');
111
- }
112
-
113
- // Validar email
114
- if (!registerEmail.value.trim()) {
115
- emailError.textContent = 'El email es requerido';
116
- emailError.classList.add('show');
117
- isValid = false;
118
- } else if (!isValidInstitutionalEmail(registerEmail.value)) {
119
- emailError.textContent = 'Debe usar email institucional (@tecmilenio.mx)';
120
- emailError.classList.add('show');
121
- isValid = false;
122
- } else {
123
- emailError.classList.remove('show');
124
- }
125
-
126
- // Validar password
127
- if (!registerPassword.value.trim()) {
128
- passwordError.textContent = 'La contraseña es requerida';
129
- passwordError.classList.add('show');
130
- isValid = false;
131
- } else if (!isValidPassword(registerPassword.value)) {
132
- passwordError.textContent = 'Mínimo 8 caracteres, incluir mayúscula y número';
133
- passwordError.classList.add('show');
134
- isValid = false;
135
- } else {
136
- passwordError.classList.remove('show');
137
- }
138
-
139
- // Validar rol
140
- if (!registerRole.value) {
141
- roleError.textContent = 'Selecciona un tipo de usuario';
142
- roleError.classList.add('show');
143
- isValid = false;
144
- } else {
145
- roleError.classList.remove('show');
146
- }
147
-
148
- return isValid;
149
-}
150
-
151
-/**
152
- * Realiza login
153
- * @param {string} email - Email del usuario
154
- * @param {string} password - Contraseña
155
- */
156
-async function handleLogin(email, password) {
157
- try {
158
- show(loadingSpinner);
159
- hide(authForm);
160
- formError.style.display = 'none';
161
-
162
- const response = await fetchAPI(
163
- API_CONFIG.ENDPOINTS.AUTH.LOGIN,
164
- 'POST',
165
- { email, password }
166
- );
167
-
168
- // Guardar token y datos
169
- setAuthToken(response.token, response.expiresIn);
170
- setStoredUser(response.usuario);
171
-
172
- // Mostrar dashboard
173
- showDashboard(response.usuario);
174
- } catch (error) {
175
- console.error('Error de login:', error);
176
- formError.textContent = error.message || 'Error al iniciar sesión. Verifica tus credenciales.';
177
- formError.style.display = 'block';
178
- hide(loadingSpinner);
179
- show(authForm);
180
- }
181
-}
182
-
183
-/**
184
- * Realiza registro
185
- * @param {object} data - Datos del usuario
186
- */
187
-async function handleRegister(data) {
188
- try {
189
- show(loadingSpinner);
190
- hide(authForm);
191
- formError.style.display = 'none';
192
-
193
- const response = await fetchAPI(
194
- API_CONFIG.ENDPOINTS.AUTH.REGISTER,
195
- 'POST',
196
- data
197
- );
198
-
199
- // Auto-login después de registro
200
- await handleLogin(data.email, data.password);
201
- } catch (error) {
202
- console.error('Error de registro:', error);
203
- let errorMessage = 'Error al registrarse.';
204
-
205
- if (error.status === 400) {
206
- errorMessage = error.data?.message || 'Email ya registrado o datos inválidos.';
207
- }
208
-
209
- formError.textContent = errorMessage;
210
- formError.style.display = 'block';
211
- hide(loadingSpinner);
212
- show(authForm);
213
- }
214
-}
215
-
216
-/**
217
- * Maneja el envío del formulario de autenticación
218
- */
219
-authForm.addEventListener('submit', async (e) => {
220
- e.preventDefault();
221
-
222
- if (isLoginMode) {
223
- if (validateLoginForm()) {
224
- await handleLogin(loginEmail.value, loginPassword.value);
225
- }
226
- } else {
227
- if (validateRegisterForm()) {
228
- await handleRegister({
229
- nombre: registerName.value,
230
- email: registerEmail.value,
231
- password: registerPassword.value,
232
- rol: registerRole.value
233
- });
234
- }
235
- }
236
-});
237
-
238
-/**
239
- * Toggle botón
240
- */
241
-document.addEventListener('DOMContentLoaded', () => {
242
- const btn = document.getElementById('toggleAuthBtn');
243
- if (btn) {
244
- btn.addEventListener('click', (e) => {
245
- e.preventDefault();
246
- toggleAuthMode();
247
- });
248
- }
249
-});
frontend/js/config.js
deleted
-259
@@ -1,259 +0,0 @@
1
-/* ========================
2
- API CONFIGURATION
3
- ======================== */
4
-
5
-const API_CONFIG = {
6
- BASE_URL: 'http://localhost:8080/api',
7
- TIMEOUT: 5000,
8
- ENDPOINTS: {
9
- AUTH: {
10
- LOGIN: '/auth/login',
11
- REGISTER: '/auth/register',
12
- LOGOUT: '/auth/logout',
13
- REFRESH: '/auth/refresh'
14
- },
15
- HEALTH: '/health',
16
- USERS: '/users',
17
- FORUM: '/forum',
18
- TIPS: '/tips',
19
- RESOURCES: '/resources',
20
- CHAT: '/chat',
21
- COMMUNITIES: '/communities',
22
- COMPANIES: '/companies'
23
- }
24
-};
25
-
26
-/* ========================
27
- UTILITY FUNCTIONS
28
- ======================== */
29
-
30
-/**
31
- * Realiza peticiones HTTP a la API
32
- * @param {string} endpoint - Endpoint de la API
33
- * @param {string} method - GET, POST, PUT, DELETE
34
- * @param {object} data - Datos a enviar (opcional)
35
- * @returns {Promise}
36
- */
37
-async function fetchAPI(endpoint, method = 'GET', data = null) {
38
- const url = `${API_CONFIG.BASE_URL}${endpoint}`;
39
- const options = {
40
- method,
41
- headers: {
42
- 'Content-Type': 'application/json'
43
- }
44
- };
45
-
46
- // Añadir token JWT si existe
47
- const token = localStorage.getItem('authToken');
48
- if (token) {
49
- options.headers['Authorization'] = `Bearer ${token}`;
50
- }
51
-
52
- // Añadir body si es POST, PUT o PATCH
53
- if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {
54
- options.body = JSON.stringify(data);
55
- }
56
-
57
- try {
58
- const controller = new AbortController();
59
- const timeoutId = setTimeout(() => controller.abort(), API_CONFIG.TIMEOUT);
60
-
61
- const response = await fetch(url, {
62
- ...options,
63
- signal: controller.signal
64
- });
65
-
66
- clearTimeout(timeoutId);
67
-
68
- // Parsear respuesta
69
- const contentType = response.headers.get('content-type');
70
- let result;
71
-
72
- if (contentType && contentType.includes('application/json')) {
73
- result = await response.json();
74
- } else {
75
- result = await response.text();
76
- }
77
-
78
- // Validar status code
79
- if (!response.ok) {
80
- throw {
81
- status: response.status,
82
- data: result,
83
- message: result?.message || response.statusText
84
- };
85
- }
86
-
87
- return result;
88
- } catch (error) {
89
- if (error instanceof TypeError) {
90
- throw new Error('Error de conexión. Verifica que el backend esté ejecutándose.');
91
- }
92
- throw error;
93
- }
94
-}
95
-
96
-/**
97
- * Almacena el token JWT
98
- * @param {string} token - JWT token
99
- * @param {number} expiresIn - Tiempo de expiración en milisegundos
100
- */
101
-function setAuthToken(token, expiresIn) {
102
- localStorage.setItem('authToken', token);
103
- if (expiresIn) {
104
- const expiryTime = Date.now() + expiresIn;
105
- localStorage.setItem('tokenExpiry', expiryTime);
106
- }
107
-}
108
-
109
-/**
110
- * Obtiene el token JWT almacenado
111
- * @returns {string|null}
112
- */
113
-function getAuthToken() {
114
- return localStorage.getItem('authToken');
115
-}
116
-
117
-/**
118
- * Elimina el token JWT
119
- */
120
-function clearAuthToken() {
121
- localStorage.removeItem('authToken');
122
- localStorage.removeItem('tokenExpiry');
123
-}
124
-
125
-/**
126
- * Verifica si el token es válido
127
- * @returns {boolean}
128
- */
129
-function isTokenValid() {
130
- const token = getAuthToken();
131
- const expiry = localStorage.getItem('tokenExpiry');
132
-
133
- if (!token || !expiry) return false;
134
- return Date.now() < parseInt(expiry);
135
-}
136
-
137
-/**
138
- * Obtiene datos del usuario almacenados
139
- * @returns {object|null}
140
- */
141
-function getStoredUser() {
142
- const user = localStorage.getItem('currentUser');
143
- return user ? JSON.parse(user) : null;
144
-}
145
-
146
-/**
147
- * Almacena datos del usuario
148
- * @param {object} user - Objeto usuario
149
- */
150
-function setStoredUser(user) {
151
- localStorage.setItem('currentUser', JSON.stringify(user));
152
-}
153
-
154
-/**
155
- * Elimina datos del usuario
156
- */
157
-function clearStoredUser() {
158
- localStorage.removeItem('currentUser');
159
-}
160
-
161
-/**
162
- * Muestra un mensaje de error global
163
- * @param {string} message - Mensaje de error
164
- */
165
-function showError(message) {
166
- const errorElement = document.getElementById('globalError');
167
- if (errorElement) {
168
- errorElement.textContent = message;
169
- errorElement.style.display = 'block';
170
- setTimeout(() => {
171
- errorElement.style.display = 'none';
172
- }, 5000);
173
- }
174
-}
175
-
176
-/**
177
- * Valida formato de email institucional
178
- * @param {string} email - Email a validar
179
- * @returns {boolean}
180
- */
181
-function isValidInstitutionalEmail(email) {
182
- const institutionalDomains = ['@tecmilenio.mx', '@servicios.tecmilenio.mx'];
183
- return institutionalDomains.some(domain => email.endsWith(domain));
184
-}
185
-
186
-/**
187
- * Valida contraseña (mínimo 8 caracteres, mayúscula, número)
188
- * @param {string} password - Contraseña a validar
189
- * @returns {boolean}
190
- */
191
-function isValidPassword(password) {
192
- const minLength = 8;
193
- const hasUpperCase = /[A-Z]/.test(password);
194
- const hasNumber = /[0-9]/.test(password);
195
-
196
- return password.length >= minLength && hasUpperCase && hasNumber;
197
-}
198
-
199
-/**
200
- * Valida nombre completo
201
- * @param {string} name - Nombre a validar
202
- * @returns {boolean}
203
- */
204
-function isValidName(name) {
205
- return name.trim().length >= 3 && name.trim().split(' ').length >= 2;
206
-}
207
-
208
-/**
209
- * Formatea fecha para mostrar
210
- * @param {string|Date} date - Fecha
211
- * @returns {string}
212
- */
213
-function formatDate(date) {
214
- const d = new Date(date);
215
- return d.toLocaleDateString('es-MX', {
216
- year: 'numeric',
217
- month: 'long',
218
- day: 'numeric',
219
- hour: '2-digit',
220
- minute: '2-digit'
221
- });
222
-}
223
-
224
-/**
225
- * Trunca texto a N caracteres
226
- * @param {string} text - Texto
227
- * @param {number} length - Longitud máxima
228
- * @returns {string}
229
- */
230
-function truncateText(text, length = 100) {
231
- return text.length > length ? text.substring(0, length) + '...' : text;
232
-}
233
-
234
-/**
235
- * Limpia formulario
236
- * @param {HTMLFormElement} form - Formulario
237
- */
238
-function clearForm(form) {
239
- form.reset();
240
- form.querySelectorAll('.error-message').forEach(el => {
241
- el.classList.remove('show');
242
- });
243
-}
244
-
245
-/**
246
- * Muestra elemento
247
- * @param {HTMLElement} element - Elemento
248
- */
249
-function show(element) {
250
- if (element) element.style.display = '';
251
-}
252
-
253
-/**
254
- * Oculta elemento
255
- * @param {HTMLElement} element - Elemento
256
- */
257
-function hide(element) {
258
- if (element) element.style.display = 'none';
259
-}
frontend/js/main.js
deleted
-166
@@ -1,166 +0,0 @@
1
-/* ========================
2
- MAIN APPLICATION LOGIC
3
- ======================== */
4
-
5
-const authSection = document.getElementById('authSection');
6
-const dashboardSection = document.getElementById('dashboardSection');
7
-const userNameElement = document.getElementById('userName');
8
-const logoutBtn = document.getElementById('logoutBtn');
9
-const sidebar = document.getElementById('sidebar');
10
-const toggleBtn = document.getElementById('toggleBtn');
11
-
12
-/**
13
- * Muestra la sección de dashboard
14
- * @param {object} user - Datos del usuario
15
- */
16
-function showDashboard(user) {
17
- hide(authSection);
18
- show(dashboardSection);
19
-
20
- if (user && userNameElement) {
21
- const firstName = user.nombre ? user.nombre.split(' ')[0] : 'Usuario';
22
- userNameElement.textContent = firstName;
23
- }
24
-
25
- loadActivity();
26
-}
27
-
28
-/**
29
- * Muestra la sección de autenticación
30
- */
31
-function showAuth() {
32
- show(authSection);
33
- hide(dashboardSection);
34
- clearStoredUser();
35
- clearAuthToken();
36
-}
37
-
38
-/**
39
- * Carga actividad reciente
40
- */
41
-async function loadActivity() {
42
- try {
43
- const activityContainer = document.getElementById('activityContainer');
44
-
45
- // Simulación: En producción, esto vendría del backend
46
- const activities = [
47
- {
48
- title: 'Respondiste una duda en Matemáticas',
49
- time: 'hace 2 horas'
50
- },
51
- {
52
- title: 'Te uniste al círculo de Programación',
53
- time: 'hace 1 día'
54
- },
55
- {
56
- title: 'Compartiste un tip académico',
57
- time: 'hace 2 días'
58
- }
59
- ];
60
-
61
- activityContainer.innerHTML = activities.map(activity => `
62
- <div class="activity-item">
63
- <p>${activity.title}</p>
64
- <small class="text-muted">${activity.time}</small>
65
- </div>
66
- `).join('');
67
- } catch (error) {
68
- console.error('Error cargando actividad:', error);
69
- }
70
-}
71
-
72
-/**
73
- * Maneja logout
74
- */
75
-async function handleLogout() {
76
- try {
77
- // Opcional: notificar al backend
78
- // await fetchAPI(API_CONFIG.ENDPOINTS.AUTH.LOGOUT, 'POST');
79
- } catch (error) {
80
- console.error('Error en logout:', error);
81
- } finally {
82
- showAuth();
83
- }
84
-}
85
-
86
-/**
87
- * Verifica si el usuario está autenticado y carga la UI apropiada
88
- */
89
-function initializeApp() {
90
- if (isTokenValid() && getStoredUser()) {
91
- showDashboard(getStoredUser());
92
- } else {
93
- clearAuthToken();
94
- clearStoredUser();
95
- showAuth();
96
- }
97
-}
98
-
99
-/**
100
- * Toggle sidebar en móvil
101
- */
102
-if (toggleBtn) {
103
- toggleBtn.addEventListener('click', () => {
104
- sidebar.classList.toggle('collapsed');
105
- });
106
-}
107
-
108
-/**
109
- * Logout button
110
- */
111
-if (logoutBtn) {
112
- logoutBtn.addEventListener('click', () => {
113
- if (confirm('¿Estás seguro de que quieres cerrar sesión?')) {
114
- handleLogout();
115
- }
116
- });
117
-}
118
-
119
-/**
120
- * Actualizar enlace activo en navegación
121
- */
122
-function updateActiveNavLink() {
123
- const currentPage = window.location.pathname.split('/').pop() || 'index.html';
124
-
125
- document.querySelectorAll('.nav-link').forEach(link => {
126
- const href = link.getAttribute('href');
127
- if (href === currentPage || (currentPage === '' && href === 'index.html')) {
128
- link.classList.add('active');
129
- } else {
130
- link.classList.remove('active');
131
- }
132
- });
133
-}
134
-
135
-/**
136
- * Inicializa la aplicación cuando el DOM esté listo
137
- */
138
-document.addEventListener('DOMContentLoaded', () => {
139
- initializeApp();
140
- updateActiveNavLink();
141
-});
142
-
143
-/**
144
- * Escucha cambios en el almacenamiento local (para sincronizar entre pestañas)
145
- */
146
-window.addEventListener('storage', (e) => {
147
- if (e.key === 'authToken' && !e.newValue) {
148
- showAuth();
149
- }
150
-});
151
-
152
-/**
153
- * Manejo global de errores
154
- */
155
-window.addEventListener('error', (event) => {
156
- console.error('Error global:', event.error);
157
- showError('Ocurrió un error inesperado. Por favor, recarga la página.');
158
-});
159
-
160
-/**
161
- * Manejo de rechazos de promesas no capturadas
162
- */
163
-window.addEventListener('unhandledrejection', (event) => {
164
- console.error('Promesa rechazada:', event.reason);
165
- showError('Error de conexión. Verifica tu conexión a internet.');
166
-});