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>&copy; 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 -});