medoficaciones de front end y base de datoss

Daniel Silva committed Aug 27, 2026 at 19:31 UTC aedd43b14237280719e33b0ba5fb79d361fce360
24 files changed +1754 -233
frontend/README.md
+3 -3
@@ -29,11 +29,11 @@ frontend/
29 |----------|------|-----|
30 | Inicio | `pages/inicio.html` | `js/pages/inicio.js` |
31 | Perfil | `pages/perfil.html` | `js/pages/perfil.js` |
32 -| Foro | `pages/foro.html` | `js/pages/foro.js` |
33 -| Tips | `pages/tips.html` | `js/pages/tips.js` |
34 -| Apuntes | `pages/recursos.html` | `js/pages/recursos.js` |
32 +| Comunidad y Recursos | `pages/comunidad.html` | `js/pages/comunidad.js` |
33 | Mensajes | `pages/mensajes.html` | `js/pages/mensajes.js` |
34 | Empresarial | `pages/empresarial.html` | `js/pages/empresarial.js` |
35 | Círculos | `pages/circulos.html` | `js/pages/circulos.js` |
36
37 +> "Comunidad y Recursos" integra en pestañas Foro de Dudas, Repositorio de Apuntes y Tips Académicos. Cada pestaña solo renderiza contenido cuando el backend (`/foro`, `/recursos`, `/tips`) devuelve datos.
38 +
39 El contenido de cada pantalla va en `#main-content`.
frontend/css/comunidad.css new
+323
@@ -0,0 +1,323 @@
1 +/* MAPS Connect - Comunidad y Recursos (Foro + Apuntes + Tips) */
2 +
3 +.academic-hub-container {
4 + --hub-accent: #005a2b;
5 + --hub-accent-dark: #004420;
6 + --hub-accent-soft: #e8f5e9;
7 + --hub-border: #dcdfe6;
8 + --hub-bg: #f4f6f8;
9 + --hub-text: #1a1a1a;
10 + --hub-muted: #666;
11 +
12 + max-width: 1050px;
13 + margin: 0 auto;
14 + width: 100%;
15 +}
16 +
17 +/* Bordes rectos institucionales (scopeado a la página) */
18 +.academic-hub-container .card,
19 +.academic-hub-container .input-search,
20 +.academic-hub-container .select-materia,
21 +.academic-hub-container .btn-solid,
22 +.academic-hub-container .btn-clean,
23 +.academic-hub-container .badge,
24 +.academic-hub-container .file-icon,
25 +.academic-hub-container .solved-box {
26 + border-radius: 0;
27 +}
28 +
29 +.academic-hub-container .card {
30 + background: #ffffff;
31 + border: 1px solid var(--hub-border);
32 + padding: 20px;
33 + margin-bottom: 16px;
34 +}
35 +
36 +/* --- 1. BARRA DE BÚSQUEDA Y FILTRO SUPERIOR --- */
37 +.hub-filter-bar {
38 + display: grid;
39 + grid-template-columns: 2fr 1.2fr auto;
40 + gap: 12px;
41 + margin-bottom: 20px;
42 +}
43 +
44 +@media (max-width: 768px) {
45 + .hub-filter-bar {
46 + grid-template-columns: 1fr;
47 + }
48 +}
49 +
50 +.input-search,
51 +.select-materia {
52 + padding: 10px 14px;
53 + border: 1px solid var(--hub-border);
54 + background: #ffffff;
55 + font-size: 0.9rem;
56 + outline: none;
57 + color: var(--hub-text);
58 +}
59 +
60 +.input-search:focus,
61 +.select-materia:focus {
62 + border-color: var(--hub-accent);
63 +}
64 +
65 +.btn-solid {
66 + background-color: var(--hub-accent);
67 + color: #ffffff;
68 + border: none;
69 + padding: 10px 18px;
70 + font-size: 0.9rem;
71 + font-weight: bold;
72 + cursor: pointer;
73 + white-space: nowrap;
74 +}
75 +
76 +.btn-solid:hover {
77 + background-color: var(--hub-accent-dark);
78 +}
79 +
80 +.btn-clean {
81 + background-color: transparent;
82 + color: var(--hub-accent);
83 + border: 1px solid var(--hub-accent);
84 + padding: 6px 14px;
85 + font-size: 0.85rem;
86 + font-weight: 600;
87 + cursor: pointer;
88 +}
89 +
90 +.btn-clean:hover {
91 + background-color: var(--hub-accent-soft);
92 +}
93 +
94 +/* --- 2. PESTAÑAS DE SUB-NAVEGACIÓN --- */
95 +.hub-tabs {
96 + display: flex;
97 + border-bottom: 2px solid var(--hub-border);
98 + margin-bottom: 20px;
99 + background: #ffffff;
100 +}
101 +
102 +.tab-button {
103 + flex: 1;
104 + padding: 14px;
105 + background: none;
106 + border: none;
107 + border-bottom: 3px solid transparent;
108 + font-size: 0.95rem;
109 + font-weight: bold;
110 + color: var(--hub-muted);
111 + cursor: pointer;
112 + text-align: center;
113 +}
114 +
115 +.tab-button:hover {
116 + color: var(--hub-accent);
117 +}
118 +
119 +.tab-button.active {
120 + color: var(--hub-accent);
121 + border-bottom-color: var(--hub-accent);
122 + background-color: #fafafa;
123 +}
124 +
125 +/* Paneles de contenido */
126 +.tab-panel {
127 + display: none;
128 +}
129 +
130 +.tab-panel.active {
131 + display: block;
132 +}
133 +
134 +/* Badges */
135 +.hub-badge {
136 + padding: 3px 8px;
137 + font-size: 0.75rem;
138 + font-weight: bold;
139 + display: inline-block;
140 +}
141 +
142 +.badge-materia {
143 + background-color: var(--hub-accent-soft);
144 + color: var(--hub-accent);
145 + border: 1px solid #c8e6c9;
146 +}
147 +
148 +.badge-solved {
149 + background-color: #dcfce7;
150 + color: #166534;
151 + border: 1px solid #bbf7d0;
152 +}
153 +
154 +.badge-verified {
155 + background-color: #e0f2fe;
156 + color: #0284c7;
157 + border: 1px solid #bae6fd;
158 +}
159 +
160 +/* --- ESTILOS: FORO DE DUDAS --- */
161 +.post-header {
162 + display: flex;
163 + justify-content: space-between;
164 + align-items: flex-start;
165 + margin-bottom: 8px;
166 + gap: 12px;
167 + flex-wrap: wrap;
168 +}
169 +
170 +.post-meta {
171 + font-size: 0.8rem;
172 + color: #888;
173 +}
174 +
175 +.post-title {
176 + font-size: 1.1rem;
177 + color: #111;
178 + margin-bottom: 6px;
179 +}
180 +
181 +.post-desc {
182 + font-size: 0.9rem;
183 + color: #444;
184 + line-height: 1.4;
185 + margin-bottom: 12px;
186 +}
187 +
188 +.post-desc code {
189 + background: #f3f4f6;
190 + padding: 1px 5px;
191 + font-size: 0.85em;
192 +}
193 +
194 +.solved-box {
195 + background-color: #f9fafb;
196 + border-left: 3px solid var(--hub-accent);
197 + padding: 10px 12px;
198 + margin: 10px 0;
199 + font-size: 0.85rem;
200 +}
201 +
202 +.post-footer {
203 + display: flex;
204 + justify-content: space-between;
205 + align-items: center;
206 + border-top: 1px solid #f0f0f0;
207 + padding-top: 10px;
208 + margin-top: 10px;
209 + font-size: 0.82rem;
210 + color: var(--hub-muted);
211 +}
212 +
213 +.post-actions {
214 + display: flex;
215 + gap: 12px;
216 + flex-wrap: wrap;
217 +}
218 +
219 +.btn-link {
220 + background: none;
221 + border: none;
222 + color: #555;
223 + font-weight: 600;
224 + font-size: 0.82rem;
225 + cursor: pointer;
226 +}
227 +
228 +.btn-link:hover {
229 + color: var(--hub-accent);
230 +}
231 +
232 +/* --- ESTILOS: APUNTES Y REPOSITORIO --- */
233 +.apunte-card {
234 + display: flex;
235 + justify-content: space-between;
236 + align-items: center;
237 + flex-wrap: wrap;
238 + gap: 16px;
239 +}
240 +
241 +.apunte-info {
242 + display: flex;
243 + align-items: flex-start;
244 + gap: 14px;
245 +}
246 +
247 +.file-icon {
248 + width: 44px;
249 + height: 44px;
250 + background-color: var(--hub-accent-soft);
251 + color: var(--hub-accent);
252 + display: flex;
253 + align-items: center;
254 + justify-content: center;
255 + font-weight: bold;
256 + font-size: 0.85rem;
257 + border: 1px solid #c8e6c9;
258 + flex-shrink: 0;
259 +}
260 +
261 +.apunte-details h4 {
262 + font-size: 1rem;
263 + color: #111;
264 + margin-bottom: 4px;
265 +}
266 +
267 +.apunte-details p {
268 + font-size: 0.82rem;
269 + color: var(--hub-muted);
270 +}
271 +
272 +/* --- ESTILOS: TIPS ACADÉMICOS --- */
273 +.tip-grid {
274 + display: grid;
275 + grid-template-columns: 1fr 1fr;
276 + gap: 16px;
277 +}
278 +
279 +@media (max-width: 768px) {
280 + .tip-grid {
281 + grid-template-columns: 1fr;
282 + }
283 +}
284 +
285 +.tip-card {
286 + border-top: 3px solid var(--hub-accent);
287 + display: flex;
288 + flex-direction: column;
289 + justify-content: space-between;
290 +}
291 +
292 +.tip-card .tip-head {
293 + display: flex;
294 + justify-content: space-between;
295 + align-items: center;
296 + gap: 8px;
297 + flex-wrap: wrap;
298 +}
299 +
300 +.tip-title {
301 + font-size: 1.05rem;
302 + color: #111;
303 + margin: 8px 0;
304 +}
305 +
306 +.tip-text {
307 + font-size: 0.88rem;
308 + color: #444;
309 + line-height: 1.4;
310 + margin-bottom: 12px;
311 +}
312 +
313 +.tip-text code {
314 + background: #f3f4f6;
315 + padding: 1px 5px;
316 + font-size: 0.85em;
317 +}
318 +
319 +.vote-badge {
320 + font-size: 0.82rem;
321 + font-weight: bold;
322 + color: var(--hub-accent);
323 +}
frontend/css/mensajes.css new
+271
@@ -0,0 +1,271 @@
1 +/* MAPS Connect - Mensajes */
2 +
3 +.messages-container {
4 + --hub-accent: #005a2b;
5 + --hub-accent-dark: #004420;
6 + --hub-accent-soft: #e8f5e9;
7 + --hub-border: #dcdfe6;
8 + --hub-muted: #666;
9 +
10 + max-width: 1050px;
11 + margin: 0 auto;
12 + width: 100%;
13 + height: calc(100vh - 110px);
14 + min-height: 420px;
15 + background: #ffffff;
16 + border: 1px solid var(--hub-border);
17 + display: grid;
18 + grid-template-columns: 320px 1fr;
19 +}
20 +
21 +@media (max-width: 768px) {
22 + .messages-container {
23 + grid-template-columns: 1fr;
24 + height: auto;
25 + }
26 +}
27 +
28 +/* Bordes rectos institucionales (scopeado a la página) */
29 +.messages-container .avatar,
30 +.messages-container .btn-solid,
31 +.messages-container .btn-clean,
32 +.messages-container .chat-search-box input,
33 +.messages-container .chat-input-bar input,
34 +.messages-container .message {
35 + border-radius: 0;
36 +}
37 +
38 +/* --- BANDEJA LATERAL (IZQUIERDA) --- */
39 +.chat-sidebar {
40 + border-right: 1px solid var(--hub-border);
41 + display: flex;
42 + flex-direction: column;
43 + background: #fafafa;
44 +}
45 +
46 +.chat-search-box {
47 + padding: 14px;
48 + border-bottom: 1px solid #e0e0e0;
49 +}
50 +
51 +.chat-search-box input {
52 + width: 100%;
53 + padding: 8px 12px;
54 + border: 1px solid var(--hub-border);
55 + outline: none;
56 + background: #ffffff;
57 + font-size: 0.88rem;
58 + color: #1a1a1a;
59 +}
60 +
61 +.chat-search-box input:focus {
62 + border-color: var(--hub-accent);
63 +}
64 +
65 +.conversation-list {
66 + flex: 1;
67 + overflow-y: auto;
68 +}
69 +
70 +.chat-item {
71 + display: flex;
72 + align-items: center;
73 + gap: 12px;
74 + padding: 14px;
75 + border-bottom: 1px solid #f0f0f0;
76 + cursor: pointer;
77 + background: #ffffff;
78 + transition: background 0.15s;
79 +}
80 +
81 +.chat-item:hover {
82 + background: #f4f6f8;
83 +}
84 +
85 +.chat-item.active {
86 + background: var(--hub-accent-soft);
87 + border-left: 4px solid var(--hub-accent);
88 +}
89 +
90 +.avatar {
91 + width: 40px;
92 + height: 40px;
93 + background-color: var(--hub-accent);
94 + color: #ffffff;
95 + display: flex;
96 + align-items: center;
97 + justify-content: center;
98 + font-weight: bold;
99 + font-size: 0.85rem;
100 + flex-shrink: 0;
101 +}
102 +
103 +.avatar.prof {
104 + background-color: #0284c7;
105 +}
106 +
107 +.chat-item-info {
108 + flex: 1;
109 + min-width: 0;
110 +}
111 +
112 +.chat-item-header {
113 + display: flex;
114 + justify-content: space-between;
115 + margin-bottom: 2px;
116 + gap: 8px;
117 +}
118 +
119 +.chat-item-header strong {
120 + font-size: 0.9rem;
121 + color: #222;
122 + white-space: nowrap;
123 + overflow: hidden;
124 + text-overflow: ellipsis;
125 +}
126 +
127 +.chat-item-header span {
128 + font-size: 0.75rem;
129 + color: #888;
130 + flex-shrink: 0;
131 +}
132 +
133 +.chat-item-preview {
134 + font-size: 0.8rem;
135 + color: var(--hub-muted);
136 + white-space: nowrap;
137 + overflow: hidden;
138 + text-overflow: ellipsis;
139 +}
140 +
141 +/* --- VENTANA PRINCIPAL DE CHAT (DERECHA) --- */
142 +.chat-window {
143 + display: flex;
144 + flex-direction: column;
145 + background: #ffffff;
146 +}
147 +
148 +.chat-header {
149 + padding: 14px 20px;
150 + border-bottom: 1px solid var(--hub-border);
151 + display: flex;
152 + justify-content: space-between;
153 + align-items: center;
154 + background: #ffffff;
155 + gap: 12px;
156 + flex-wrap: wrap;
157 +}
158 +
159 +.chat-header-user {
160 + display: flex;
161 + align-items: center;
162 + gap: 12px;
163 +}
164 +
165 +.chat-header-user h3 {
166 + font-size: 1rem;
167 + color: #111;
168 +}
169 +
170 +.chat-header-user span {
171 + font-size: 0.8rem;
172 + color: var(--hub-accent);
173 + font-weight: 600;
174 +}
175 +
176 +/* Cuerpo con los mensajes */
177 +.chat-body {
178 + flex: 1;
179 + padding: 20px;
180 + overflow-y: auto;
181 + display: flex;
182 + flex-direction: column;
183 + gap: 12px;
184 + background-color: #f9fafb;
185 +}
186 +
187 +.message {
188 + max-width: 70%;
189 + padding: 10px 14px;
190 + font-size: 0.9rem;
191 + line-height: 1.4;
192 +}
193 +
194 +.message.received {
195 + align-self: flex-start;
196 + background-color: #ffffff;
197 + border: 1px solid #e0e0e0;
198 + color: #222;
199 +}
200 +
201 +.message.sent {
202 + align-self: flex-end;
203 + background-color: var(--hub-accent);
204 + color: #ffffff;
205 +}
206 +
207 +.message-time {
208 + display: block;
209 + font-size: 0.7rem;
210 + margin-top: 4px;
211 + text-align: right;
212 + opacity: 0.8;
213 +}
214 +
215 +/* Estado vacío del chat */
216 +.chat-empty {
217 + flex: 1;
218 + display: flex;
219 + align-items: center;
220 + justify-content: center;
221 + color: #999;
222 + font-size: 0.9rem;
223 + background-color: #f9fafb;
224 + padding: 24px;
225 + text-align: center;
226 +}
227 +
228 +/* Barra de entrada inferior */
229 +.chat-input-bar {
230 + padding: 14px 20px;
231 + border-top: 1px solid var(--hub-border);
232 + display: flex;
233 + gap: 10px;
234 + background: #ffffff;
235 +}
236 +
237 +.chat-input-bar input {
238 + flex: 1;
239 + padding: 10px 14px;
240 + border: 1px solid var(--hub-border);
241 + outline: none;
242 + font-size: 0.9rem;
243 + color: #1a1a1a;
244 +}
245 +
246 +.chat-input-bar input:focus {
247 + border-color: var(--hub-accent);
248 +}
249 +
250 +.messages-container .btn-solid {
251 + background-color: var(--hub-accent);
252 + color: #ffffff;
253 + border: none;
254 + padding: 10px 20px;
255 + font-size: 0.9rem;
256 + font-weight: bold;
257 + cursor: pointer;
258 +}
259 +
260 +.messages-container .btn-solid:hover {
261 + background-color: var(--hub-accent-dark);
262 +}
263 +
264 +.messages-container .btn-clean {
265 + background-color: transparent;
266 + color: #555;
267 + border: 1px solid var(--hub-border);
268 + padding: 8px 14px;
269 + cursor: pointer;
270 + font-weight: 600;
271 +}
frontend/css/perfil.css new
+254
@@ -0,0 +1,254 @@
1 +/* MAPS Connect - Mi Perfil */
2 +
3 +.perfil-container {
4 + --hub-accent: #005a2b;
5 + --hub-accent-dark: #004420;
6 + --hub-accent-soft: #e8f5e9;
7 + --hub-border: #dcdfe6;
8 + --hub-muted: #666;
9 +
10 + max-width: 1050px;
11 + margin: 0 auto;
12 + width: 100%;
13 +}
14 +
15 +/* Bordes rectos institucionales (scopeado a la página) */
16 +.perfil-container .card,
17 +.perfil-container .profile-avatar,
18 +.perfil-container .btn-solid,
19 +.perfil-container .btn-clean,
20 +.perfil-container .badge,
21 +.perfil-container .certificate-item,
22 +.perfil-container .stat-box {
23 + border-radius: 0;
24 +}
25 +
26 +.perfil-container .card {
27 + background: #ffffff;
28 + border: 1px solid var(--hub-border);
29 + padding: 20px;
30 + margin-bottom: 20px;
31 +}
32 +
33 +/* --- 1. CABECERA DE PERFIL --- */
34 +.profile-header-card {
35 + display: flex;
36 + justify-content: space-between;
37 + align-items: center;
38 + flex-wrap: wrap;
39 + gap: 20px;
40 + border-left: 4px solid var(--hub-accent);
41 +}
42 +
43 +.profile-main-info {
44 + display: flex;
45 + align-items: center;
46 + gap: 18px;
47 +}
48 +
49 +.profile-avatar {
50 + width: 70px;
51 + height: 70px;
52 + background-color: var(--hub-accent);
53 + color: #ffffff;
54 + display: flex;
55 + align-items: center;
56 + justify-content: center;
57 + font-weight: bold;
58 + font-size: 1.6rem;
59 + flex-shrink: 0;
60 +}
61 +
62 +.profile-name {
63 + font-size: 1.4rem;
64 + color: #111111;
65 + margin-bottom: 4px;
66 +}
67 +
68 +.profile-sub {
69 + font-size: 0.9rem;
70 + color: #555555;
71 +}
72 +
73 +/* Botones */
74 +.perfil-container .btn-solid {
75 + background-color: var(--hub-accent);
76 + color: #ffffff;
77 + border: none;
78 + padding: 9px 18px;
79 + font-size: 0.9rem;
80 + font-weight: bold;
81 + cursor: pointer;
82 +}
83 +
84 +.perfil-container .btn-solid:hover {
85 + background-color: var(--hub-accent-dark);
86 +}
87 +
88 +.perfil-container .btn-clean {
89 + background-color: transparent;
90 + color: var(--hub-accent);
91 + border: 1px solid var(--hub-accent);
92 + padding: 8px 16px;
93 + font-size: 0.85rem;
94 + font-weight: 600;
95 + cursor: pointer;
96 +}
97 +
98 +.perfil-container .btn-clean:hover {
99 + background-color: var(--hub-accent-soft);
100 +}
101 +
102 +/* --- 2. LAYOUT A DOS COLUMNAS --- */
103 +.profile-grid {
104 + display: grid;
105 + grid-template-columns: 1.8fr 1.2fr;
106 + gap: 20px;
107 +}
108 +
109 +@media (max-width: 850px) {
110 + .profile-grid {
111 + grid-template-columns: 1fr;
112 + }
113 +}
114 +
115 +.section-heading {
116 + font-size: 1.1rem;
117 + color: #222222;
118 + margin-bottom: 14px;
119 + border-bottom: 1px solid #e0e0e0;
120 + padding-bottom: 6px;
121 +}
122 +
123 +.sub-heading {
124 + font-size: 0.95rem;
125 + color: #333;
126 + margin: 16px 0 10px 0;
127 +}
128 +
129 +/* Badges */
130 +.perfil-container .badge {
131 + padding: 3px 8px;
132 + font-size: 0.75rem;
133 + font-weight: bold;
134 + display: inline-block;
135 +}
136 +
137 +.badge-green {
138 + background-color: var(--hub-accent-soft);
139 + color: var(--hub-accent);
140 + border: 1px solid #c8e6c9;
141 +}
142 +
143 +.badge-gray {
144 + background-color: #f1f5f9;
145 + color: #64748b;
146 + border: 1px solid #e2e8f0;
147 +}
148 +
149 +/* --- COLUMNA IZQUIERDA: TRAYECTORIA MAPS --- */
150 +.purpose-box {
151 + background-color: #f9fafb;
152 + border-left: 3px solid var(--hub-accent);
153 + padding: 12px 16px;
154 + margin-bottom: 18px;
155 +}
156 +
157 +.purpose-box strong {
158 + font-size: 0.85rem;
159 + color: var(--hub-accent);
160 + display: block;
161 + text-transform: uppercase;
162 + margin-bottom: 4px;
163 +}
164 +
165 +.purpose-box p {
166 + font-size: 0.92rem;
167 + color: #333333;
168 + font-style: italic;
169 +}
170 +
171 +.certificate-item {
172 + border: 1px solid var(--hub-border);
173 + padding: 14px;
174 + margin-bottom: 12px;
175 + background: #ffffff;
176 +}
177 +
178 +.certificate-item.active {
179 + border-left: 4px solid var(--hub-accent);
180 +}
181 +
182 +.cert-header {
183 + display: flex;
184 + justify-content: space-between;
185 + align-items: center;
186 + margin-bottom: 6px;
187 + gap: 8px;
188 + flex-wrap: wrap;
189 +}
190 +
191 +.cert-title {
192 + font-size: 0.95rem;
193 + color: #111111;
194 + font-weight: bold;
195 +}
196 +
197 +.cert-desc {
198 + font-size: 0.85rem;
199 + color: #555555;
200 + line-height: 1.4;
201 +}
202 +
203 +/* --- COLUMNA DERECHA: APORTACIONES Y ESTADÍSTICAS --- */
204 +.stats-summary {
205 + display: grid;
206 + grid-template-columns: 1fr 1fr;
207 + gap: 10px;
208 + margin-bottom: 18px;
209 +}
210 +
211 +.stat-box {
212 + background: #fafafa;
213 + border: 1px solid #e0e0e0;
214 + padding: 12px;
215 + text-align: center;
216 +}
217 +
218 +.stat-box .number {
219 + font-size: 1.3rem;
220 + font-weight: bold;
221 + color: var(--hub-accent);
222 + display: block;
223 +}
224 +
225 +.stat-box .label {
226 + font-size: 0.75rem;
227 + color: var(--hub-muted);
228 + text-transform: uppercase;
229 +}
230 +
231 +.activity-list {
232 + list-style: none;
233 +}
234 +
235 +.activity-item {
236 + padding: 10px 0;
237 + border-bottom: 1px solid #f0f0f0;
238 + font-size: 0.85rem;
239 +}
240 +
241 +.activity-item:last-child {
242 + border-bottom: none;
243 +}
244 +
245 +.activity-item strong {
246 + color: #222222;
247 + display: block;
248 + margin-bottom: 2px;
249 +}
250 +
251 +.activity-item span {
252 + color: #888888;
253 + font-size: 0.75rem;
254 +}
frontend/js/layout.js
+1 -3
@@ -5,9 +5,7 @@
5 const NAV_ITEMS = [
6 { href: 'inicio.html', label: 'Inicio' },
7 { href: 'perfil.html', label: 'Mi Perfil' },
8 - { href: 'foro.html', label: 'Foro de Dudas' },
9 - { href: 'tips.html', label: 'Tips Académicos' },
10 - { href: 'recursos.html', label: 'Apuntes' },
8 + { href: 'comunidad.html', label: 'Comunidad y Recursos' },
9 { href: 'mensajes.html', label: 'Mensajes' },
10 { href: 'empresarial.html', label: 'Semestre Empresarial' },
11 { href: 'circulos.html', label: 'Círculos de Estudio' }
frontend/js/pages/comunidad.js new
+299
@@ -0,0 +1,299 @@
1 +/**
2 + * MAPS Connect - Comunidad y Recursos (Foro + Apuntes + Tips)
3 + * Las tarjetas solo se renderizan cuando el backend devuelve datos.
4 + */
5 +
6 +const Comunidad = {
7 + state: {
8 + foro: [],
9 + apuntes: [],
10 + tips: [],
11 + loaded: new Set(),
12 + activeTab: 'foro-dudas'
13 + },
14 +
15 + init() {
16 + Layout.init('comunidad.html');
17 + Layout.setPageTitle('Comunidad y Recursos');
18 +
19 + this.bindTabs();
20 + this.bindFilters();
21 +
22 + // Permite enlazar directo a una pestaña: comunidad.html#foro-dudas
23 + const hash = window.location.hash.replace('#', '');
24 + const startTab = ['foro-dudas', 'apuntes', 'tips'].includes(hash) ? hash : 'foro-dudas';
25 + const btn = document.querySelector(`.tab-button[data-tab="${startTab}"]`);
26 + this.switchTab(startTab, btn ? btn.dataset.action : '+ Preguntar Duda');
27 + },
28 +
29 + bindTabs() {
30 + const tabs = document.querySelectorAll('.tab-button');
31 + tabs.forEach(btn => {
32 + btn.addEventListener('click', () => {
33 + this.switchTab(btn.dataset.tab, btn.dataset.action);
34 + });
35 + });
36 + },
37 +
38 + bindFilters() {
39 + const search = document.getElementById('hub-search');
40 + const materia = document.getElementById('hub-materia');
41 + search?.addEventListener('input', () => this.renderActive());
42 + materia?.addEventListener('change', () => this.renderActive());
43 + },
44 +
45 + switchTab(tabId, actionText) {
46 + this.state.activeTab = tabId;
47 +
48 + document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
49 + document.querySelectorAll('.tab-button').forEach(b => b.classList.remove('active'));
50 +
51 + document.getElementById(tabId)?.classList.add('active');
52 + document.querySelector(`.tab-button[data-tab="${tabId}"]`)?.classList.add('active');
53 +
54 + const actionBtn = document.getElementById('context-action-btn');
55 + if (actionBtn) actionBtn.textContent = actionText;
56 +
57 + this.loadTab(tabId);
58 + },
59 +
60 + async loadTab(tabId) {
61 + const map = {
62 + 'foro-dudas': { key: 'foro', loader: () => this.loadForo() },
63 + 'apuntes': { key: 'apuntes', loader: () => this.loadApuntes() },
64 + 'tips': { key: 'tips', loader: () => this.loadTips() }
65 + };
66 + const conf = map[tabId];
67 + if (!conf) return;
68 + if (this.state.loaded.has(conf.key)) {
69 + this.renderActive();
70 + return;
71 + }
72 + await conf.loader();
73 + },
74 +
75 + async loadForo() {
76 + try {
77 + const res = await API.request('/foro');
78 + this.state.foro = this.toArray(res);
79 + this.state.loaded.add('foro');
80 + } catch (e) {
81 + this.state.foro = [];
82 + }
83 + this.renderActive();
84 + },
85 +
86 + async loadApuntes() {
87 + try {
88 + const res = await API.request('/recursos');
89 + this.state.apuntes = this.toArray(res);
90 + this.state.loaded.add('apuntes');
91 + } catch (e) {
92 + this.state.apuntes = [];
93 + }
94 + this.renderActive();
95 + },
96 +
97 + async loadTips() {
98 + try {
99 + const res = await API.request('/tips');
100 + this.state.tips = this.toArray(res);
101 + this.state.loaded.add('tips');
102 + } catch (e) {
103 + this.state.tips = [];
104 + }
105 + this.renderActive();
106 + },
107 +
108 + renderActive() {
109 + const q = (document.getElementById('hub-search')?.value || '').trim().toLowerCase();
110 + const m = document.getElementById('hub-materia')?.value || '';
111 +
112 + const match = item => {
113 + const text = `${this.field(item, 'titulo')} ${this.field(item, 'descripcion')} ${this.field(item, 'texto')}`.toLowerCase();
114 + const materia = this.materiaNombre(item).toLowerCase();
115 + return (!q || text.includes(q)) && (!m || materia === m);
116 + };
117 +
118 + switch (this.state.activeTab) {
119 + case 'foro-dudas':
120 + this.renderForo(this.state.foro.filter(match), 'foro-dudas');
121 + break;
122 + case 'apuntes':
123 + this.renderApuntes(this.state.apuntes.filter(match), 'apuntes');
124 + break;
125 + case 'tips':
126 + this.renderTips(this.state.tips.filter(match), 'tips');
127 + break;
128 + }
129 + },
130 +
131 + renderForo(items, panelId) {
132 + const panel = document.getElementById(panelId);
133 + if (!panel) return;
134 + panel.innerHTML = '';
135 + if (!items.length) return;
136 + panel.innerHTML = items.map(item => this.foroCard(item)).join('');
137 + },
138 +
139 + renderApuntes(items, panelId) {
140 + const panel = document.getElementById(panelId);
141 + if (!panel) return;
142 + panel.innerHTML = '';
143 + if (!items.length) return;
144 + panel.innerHTML = items.map(item => this.apunteCard(item)).join('');
145 + },
146 +
147 + renderTips(items, panelId) {
148 + const panel = document.getElementById(panelId);
149 + if (!panel) return;
150 + panel.innerHTML = '';
151 + if (!items.length) return;
152 + panel.innerHTML = items.map(item => this.tipCard(item)).join('');
153 + },
154 +
155 + foroCard(item) {
156 + const titulo = this.field(item, 'titulo');
157 + const desc = this.field(item, 'descripcion');
158 + const materia = this.materiaNombre(item);
159 + const autor = this.autorNombre(item);
160 + const tiempo = this.field(item, 'tiempo') || this.field(item, 'haceTiempo') || '';
161 + const votos = this.num(item, 'votos');
162 + const respuestas = this.num(item, 'respuestas') || this.num(item, 'numRespuestas');
163 + const resuelto = item.resuelto === true || item.resuelto === 'true';
164 + const solucion = this.field(item, 'solucion');
165 +
166 + const badges = [
167 + materia ? `<span class="hub-badge badge-materia">${this.esc(materia)}</span>` : '',
168 + resuelto ? `<span class="hub-badge badge-solved">✓ Solución Aceptada</span>` : ''
169 + ].join('');
170 +
171 + const solucionBox = (resuelto && solucion)
172 + ? `<div class="solved-box"><strong>Solución validada por el autor:</strong><p>${this.esc(solucion)}</p></div>`
173 + : '';
174 +
175 + const metaTiempo = tiempo ? `Por: ${this.esc(autor)} • ${this.esc(tiempo)}` : `Por: ${this.esc(autor)}`;
176 + const accionRespuesta = resuelto
177 + ? `<button class="btn-link">💬 ${respuestas} Respuestas</button>`
178 + : `<button class="btn-link" style="color:#005a2b;font-weight:bold;">✍ Aportar Respuesta</button>`;
179 +
180 + return `
181 + <article class="card">
182 + <header class="post-header">
183 + <div style="display:flex;gap:6px;flex-wrap:wrap;">${badges}</div>
184 + <span class="post-meta">${this.esc(metaTiempo)}</span>
185 + </header>
186 + <h3 class="post-title">${this.esc(titulo)}</h3>
187 + <p class="post-desc">${this.esc(desc)}</p>
188 + ${solucionBox}
189 + <footer class="post-footer">
190 + <div class="post-actions">
191 + <button class="btn-link">▲ ${votos} Votos</button>
192 + ${accionRespuesta}
193 + <button class="btn-link">🔖 Guardar</button>
194 + </div>
195 + <span>${resuelto ? 'Última aportación reciente' : 'Esperando solución'}</span>
196 + </footer>
197 + </article>`;
198 + },
199 +
200 + apunteCard(item) {
201 + const titulo = this.field(item, 'titulo');
202 + const materia = this.materiaNombre(item);
203 + const autor = this.autorNombre(item);
204 + const tipo = this.field(item, 'tipo') || 'PDF';
205 + const tamano = this.field(item, 'tamano') || this.field(item, 'tamanio') || '';
206 + const descargas = this.num(item, 'descargas');
207 + const rating = this.field(item, 'rating') || '';
208 + const url = this.field(item, 'url') || this.field(item, 'archivoUrl') || '#';
209 +
210 + return `
211 + <article class="card apunte-card">
212 + <div class="apunte-info">
213 + <div class="file-icon">${this.esc(tipo)}</div>
214 + <div class="apunte-details">
215 + <h4>${this.esc(titulo)}</h4>
216 + <p>Materia: <strong>${this.esc(materia)}</strong> • Subido por: <strong>${this.esc(autor)}</strong></p>
217 + <p style="margin-top:4px;color:#888;">${tamano ? `Tamaño: ${this.esc(tamano)} • ` : ''}${descargas} descargas${rating ? ` • ${this.esc(rating)} ★` : ''}</p>
218 + </div>
219 + </div>
220 + <button class="btn-solid" type="button" data-href="${this.esc(url)}">⬇ Descargar Documento</button>
221 + </article>`;
222 + },
223 +
224 + tipCard(item) {
225 + const titulo = this.field(item, 'titulo');
226 + const texto = this.field(item, 'texto') || this.field(item, 'descripcion');
227 + const materia = this.materiaNombre(item);
228 + const autor = this.autorNombre(item);
229 + const votos = this.num(item, 'votos');
230 + const verificado = item.verificado === true || item.verificado === 'true';
231 +
232 + const verified = verificado
233 + ? `<span class="hub-badge badge-verified">✓ Verificado por Docente</span>`
234 + : '';
235 +
236 + return `
237 + <article class="card tip-card">
238 + <div>
239 + <div class="tip-head">
240 + <span class="hub-badge badge-materia">${this.esc(materia)}</span>
241 + ${verified}
242 + </div>
243 + <h4 class="tip-title">${this.esc(titulo)}</h4>
244 + <p class="tip-text">${this.esc(texto)}</p>
245 + </div>
246 + <div class="post-footer">
247 + <span class="vote-badge">▲ ${votos} votos de utilidad</span>
248 + <span style="font-size:0.78rem;color:#888;">Por: ${this.esc(autor)}</span>
249 + </div>
250 + </article>`;
251 + },
252 +
253 + // ---- helpers ----
254 + field(obj, key) {
255 + if (!obj) return '';
256 + return obj[key] != null ? String(obj[key]) : '';
257 + },
258 +
259 + num(obj, key) {
260 + const v = obj && obj[key];
261 + const n = Number(v);
262 + return Number.isFinite(n) ? n : 0;
263 + },
264 +
265 + materiaNombre(item) {
266 + if (!item) return '';
267 + if (typeof item.materia === 'string') return item.materia;
268 + if (item.materia && typeof item.materia === 'object') return item.materia.nombre || item.materia.nom || '';
269 + if (item.materiaNombre) return item.materiaNombre;
270 + return 'General';
271 + },
272 +
273 + autorNombre(item) {
274 + if (!item) return 'Anónimo';
275 + if (typeof item.autor === 'string') return item.autor;
276 + if (item.autor && typeof item.autor === 'object') return item.autor.nombre || item.autor.nom || 'Anónimo';
277 + if (item.autorNombre) return item.autorNombre;
278 + return 'Anónimo';
279 + },
280 +
281 + toArray(res) {
282 + if (!res) return [];
283 + if (Array.isArray(res)) return res;
284 + if (Array.isArray(res.data)) return res.data;
285 + if (Array.isArray(res.contenido)) return res.contenido;
286 + return [];
287 + },
288 +
289 + esc(str) {
290 + return String(str == null ? '' : str)
291 + .replace(/&/g, '&amp;')
292 + .replace(/</g, '&lt;')
293 + .replace(/>/g, '&gt;')
294 + .replace(/"/g, '&quot;')
295 + .replace(/'/g, '&#39;');
296 + }
297 +};
298 +
299 +document.addEventListener('DOMContentLoaded', () => Comunidad.init());
frontend/js/pages/foro.js deleted
-4
@@ -1,4 +0,0 @@
1 -document.addEventListener('DOMContentLoaded', () => {
2 - Layout.init('foro.html');
3 - Layout.setPageTitle('Foro de Dudas');
4 -});
frontend/js/pages/inicio.js
+3 -3
@@ -43,7 +43,7 @@ function renderDashboard(content, data) {
43
44 document.getElementById('quick-question-form')?.addEventListener('submit', event => {
45 event.preventDefault();
46 - window.location.href = 'foro.html';
46 + window.location.href = 'comunidad.html#foro-dudas';
47 });
48 }
49
@@ -87,7 +87,7 @@ function renderPost(post) {
87 <div class="post-header"><div class="user-block"><div class="avatar ${post.verificadoDocente ? 'prof' : ''}">${escapeHtml(initials)}</div><div class="user-data"><strong>${escapeHtml(author.nombre || 'Usuario')}</strong><span>${escapeHtml(post.fechaRelativa || '')}</span></div></div><div class="badges-wrap">${tags}</div></div>
88 <h2 class="post-title">${escapeHtml(post.titulo || '')}</h2>
89 ${post.contenido ? `<p class="post-body">${escapeHtml(post.contenido)}</p>` : ''}
90 - <div class="post-footer"><div class="action-links"><a class="btn-action" href="foro.html">💬 ${Number(post.respuestas) || 0} respuestas</a></div><span class="post-status">${post.solucionAceptada ? 'Resuelto' : ''}</span></div>
90 + <div class="post-footer"><div class="action-links"><a class="btn-action" href="comunidad.html#foro-dudas">💬 ${Number(post.respuestas) || 0} respuestas</a></div><span class="post-status">${post.solucionAceptada ? 'Resuelto' : ''}</span></div>
91 </article>
92 `;
93 }
@@ -106,7 +106,7 @@ function renderAccessCards(data) {
106 }
107
108 function renderEmptyFeed() {
109 - return '<section class="card empty-state"><h2>Aún no hay actividad</h2><p>Las dudas, avisos y recursos de tu comunidad aparecerán aquí cuando estén disponibles.</p><a class="side-link" href="foro.html">Ir al foro →</a></section>';
109 + return '<section class="card empty-state"><h2>Aún no hay actividad</h2><p>Las dudas, avisos y recursos de tu comunidad aparecerán aquí cuando estén disponibles.</p><a class="side-link" href="comunidad.html#foro-dudas">Ir al foro →</a></section>';
110 }
111
112 function escapeHtml(value) {
frontend/js/pages/mensajes.js
+228 -4
@@ -1,4 +1,228 @@
1 -document.addEventListener('DOMContentLoaded', () => {
2 - Layout.init('mensajes.html');
3 - Layout.setPageTitle('Mensajes');
4 -});
1 +/**
2 + * MAPS Connect - Mensajes
3 + * Las conversaciones y los mensajes se renderizan solo cuando el backend
4 + * (/mensajes y /mensajes/{id}) devuelve datos.
5 + */
6 +
7 +const Mensajes = {
8 + state: {
9 + conversaciones: [],
10 + seleccionada: null
11 + },
12 +
13 + async init() {
14 + Layout.init('mensajes.html');
15 + Layout.setPageTitle('Mensajes');
16 +
17 + this.bindEventos();
18 + await this.loadConversaciones();
19 + },
20 +
21 + bindEventos() {
22 + const list = document.getElementById('conversacion-list');
23 + list?.addEventListener('click', e => {
24 + const item = e.target.closest('.chat-item');
25 + if (item && item.dataset.id) this.seleccionar(item.dataset.id);
26 + });
27 +
28 + const search = document.getElementById('chat-search');
29 + search?.addEventListener('input', () => this.renderConversaciones());
30 +
31 + document.getElementById('enviar-mensaje')?.addEventListener('click', () => this.enviar());
32 + document.getElementById('mensaje-input')?.addEventListener('keydown', e => {
33 + if (e.key === 'Enter') this.enviar();
34 + });
35 + },
36 +
37 + async loadConversaciones() {
38 + try {
39 + const res = await API.request('/mensajes');
40 + this.state.conversaciones = this.toArray(res);
41 + } catch (e) {
42 + this.state.conversaciones = [];
43 + }
44 + this.renderConversaciones();
45 + },
46 +
47 + renderConversaciones() {
48 + const host = document.getElementById('conversacion-list');
49 + if (!host) return;
50 +
51 + const q = (document.getElementById('chat-search')?.value || '').trim().toLowerCase();
52 + const items = this.state.conversaciones.filter(c => {
53 + const nombre = this.nombreConv(c).toLowerCase();
54 + const preview = this.field(c, 'preview').toLowerCase();
55 + return !q || nombre.includes(q) || preview.includes(q);
56 + });
57 +
58 + if (!items.length) { host.innerHTML = ''; return; }
59 +
60 + host.innerHTML = items.map(c => {
61 + const id = this.field(c, 'id');
62 + const active = this.state.seleccionada && String(this.state.seleccionada) === String(id) ? ' active' : '';
63 + const initials = this.initials(this.nombreConv(c));
64 + const profCls = this.esProf(c) ? ' prof' : '';
65 + const tiempo = this.field(c, 'tiempo') || this.field(c, 'fechaRelativa');
66 + const preview = this.field(c, 'preview');
67 + return `
68 + <div class="chat-item${active}" data-id="${this.esc(id)}">
69 + <div class="avatar${profCls}">${this.esc(initials)}</div>
70 + <div class="chat-item-info">
71 + <div class="chat-item-header">
72 + <strong>${this.esc(this.nombreConv(c))}</strong>
73 + ${tiempo ? `<span>${this.esc(tiempo)}</span>` : ''}
74 + </div>
75 + <p class="chat-item-preview">${this.esc(preview)}</p>
76 + </div>
77 + </div>`;
78 + }).join('');
79 + },
80 +
81 + async seleccionar(id) {
82 + this.state.seleccionada = id;
83 + this.renderConversaciones();
84 +
85 + // Cabecera provisional con el dato de la lista; el detalle viene del backend.
86 + const conv = this.state.conversaciones.find(c => String(this.field(c, 'id')) === String(id));
87 + if (conv) this.renderCabecera(conv, true);
88 +
89 + this.setEntradaHabilitada(false);
90 + this.renderMensajes([]);
91 +
92 + let data;
93 + try {
94 + data = await API.request(`/mensajes/${encodeURIComponent(id)}`);
95 + } catch (e) {
96 + // El endpoint se implementará en el backend. La UI mantiene estados vacíos.
97 + data = null;
98 + }
99 +
100 + if (!data) return;
101 +
102 + // Refresca cabecera con el detalle completo si viene.
103 + this.renderCabecera(data, false);
104 + const mensajes = Array.isArray(data.mensajes) ? data.mensajes : (Array.isArray(data) ? data : []);
105 + this.renderMensajes(mensajes);
106 + this.setEntradaHabilitada(true);
107 + },
108 +
109 + renderCabecera(data, fromList) {
110 + const host = document.getElementById('chat-header');
111 + if (!host) return;
112 + const nombre = this.nombreConv(data);
113 + const initials = this.initials(nombre);
114 + const profCls = this.esProf(data) ? ' prof' : '';
115 + const sub = this.field(data, 'subtitulo') || (fromList ? '' : '');
116 +
117 + host.innerHTML = `
118 + <div class="chat-header-user">
119 + <div class="avatar${profCls}">${this.esc(initials)}</div>
120 + <div>
121 + <h3>${this.esc(nombre)}</h3>
122 + ${sub ? `<span>${this.esc(sub)}</span>` : ''}
123 + </div>
124 + </div>
125 + <div>
126 + <button class="btn-clean" type="button">Ver Perfil Académico</button>
127 + </div>
128 + `;
129 + },
130 +
131 + renderMensajes(mensajes) {
132 + const host = document.getElementById('chat-body');
133 + const empty = document.getElementById('chat-empty');
134 + if (!host) return;
135 +
136 + if (!mensajes.length) {
137 + host.innerHTML = '';
138 + if (empty) host.appendChild(empty);
139 + return;
140 + }
141 +
142 + if (empty) empty.remove();
143 + host.innerHTML = mensajes.map(m => this.messageEl(m)).join('');
144 + host.scrollTop = host.scrollHeight;
145 + },
146 +
147 + messageEl(m) {
148 + const texto = this.field(m, 'texto') || this.field(m, 'contenido');
149 + const tiempo = this.field(m, 'tiempo') || this.field(m, 'fechaRelativa');
150 + const enviado = m.enviado === true || m.enviado === 'true' || m.propio === true;
151 + const cls = enviado ? 'sent' : 'received';
152 + return `
153 + <div class="message ${cls}">
154 + ${this.esc(texto)}
155 + ${tiempo ? `<span class="message-time">${this.esc(tiempo)}</span>` : ''}
156 + </div>`;
157 + },
158 +
159 + async enviar() {
160 + const input = document.getElementById('mensaje-input');
161 + if (!input) return;
162 + const texto = input.value.trim();
163 + if (!texto || !this.state.seleccionada) return;
164 +
165 + try {
166 + await API.request(`/mensajes/${encodeURIComponent(this.state.seleccionada)}`, {
167 + method: 'POST',
168 + body: JSON.stringify({ texto })
169 + });
170 + input.value = '';
171 + // Refrescar la conversación para obtener el mensaje persistido.
172 + await this.seleccionar(this.state.seleccionada);
173 + } catch (e) {
174 + // El endpoint aún no existe en el backend. No se limpia el input.
175 + }
176 + },
177 +
178 + setEntradaHabilitada(on) {
179 + const input = document.getElementById('mensaje-input');
180 + const btn = document.getElementById('enviar-mensaje');
181 + if (input) input.disabled = !on;
182 + if (btn) btn.disabled = !on;
183 + },
184 +
185 + // ---- helpers ----
186 + nombreConv(c) {
187 + if (!c) return '';
188 + return this.field(c, 'nombre') || this.field(c, 'nombreCompleto') || (c.autor && (c.autor.nombre || c.autor.nombreCompleto)) || '';
189 + },
190 +
191 + esProf(c) {
192 + if (!c) return false;
193 + if (c.esProf === true || c.esProfesor === true || c.rol === 'DOCENTE' || c.rol === 'PROFESOR') return true;
194 + return false;
195 + },
196 +
197 + field(obj, key) {
198 + if (!obj) return '';
199 + return obj[key] != null ? String(obj[key]) : '';
200 + },
201 +
202 + initials(name) {
203 + if (!name) return '?';
204 + const parts = String(name).trim().split(/\s+/);
205 + const first = parts[0] ? parts[0][0] : '';
206 + const second = parts[1] ? parts[1][0] : '';
207 + return (first + second).toUpperCase() || '?';
208 + },
209 +
210 + toArray(res) {
211 + if (!res) return [];
212 + if (Array.isArray(res)) return res;
213 + if (Array.isArray(res.data)) return res.data;
214 + if (Array.isArray(res.contenido)) return res.contenido;
215 + return [];
216 + },
217 +
218 + esc(str) {
219 + return String(str == null ? '' : str)
220 + .replace(/&/g, '&amp;')
221 + .replace(/</g, '&lt;')
222 + .replace(/>/g, '&gt;')
223 + .replace(/"/g, '&quot;')
224 + .replace(/'/g, '&#39;');
225 + }
226 +};
227 +
228 +document.addEventListener('DOMContentLoaded', () => Mensajes.init());
frontend/js/pages/perfil.js
+189 -4
@@ -1,4 +1,189 @@
1 -document.addEventListener('DOMContentLoaded', () => {
2 - Layout.init('perfil.html');
3 - Layout.setPageTitle('Mi Perfil');
4 -});
1 +/**
2 + * MAPS Connect - Mi Perfil
3 + * La cabecera usa la sesión (Auth) como dato real; el detalle académico
4 + * (matrícula, carrera, certificados, stats, actividad) se renderiza solo
5 + * cuando el backend (/estudiantes/perfil) devuelve datos.
6 + */
7 +
8 +const Perfil = {
9 + async init() {
10 + Layout.init('perfil.html');
11 + Layout.setPageTitle('Mi Perfil');
12 +
13 + // La cabecera puede pintarse con la sesión real del usuario.
14 + this.renderHeader(Auth.getUser());
15 +
16 + try {
17 + const data = await API.request('/estudiantes/perfil');
18 + this.renderProfile(data || {});
19 + } catch (e) {
20 + // El endpoint se implementará en el backend. La UI mantiene estados vacíos.
21 + }
22 + },
23 +
24 + renderHeader(user) {
25 + const host = document.getElementById('profile-header-info');
26 + if (!host) return;
27 +
28 + const nombre = (user && user.nombre) || '';
29 + const email = (user && user.email) || '';
30 + if (!nombre && !email) {
31 + host.innerHTML = '';
32 + return;
33 + }
34 +
35 + const initials = this.initials(nombre || email);
36 +
37 + host.innerHTML = `
38 + <div class="profile-avatar">${this.esc(initials)}</div>
39 + <div>
40 + <h2 class="profile-name">${this.esc(nombre)}</h2>
41 + ${email ? `<p class="profile-sub">${this.esc(email)}</p>` : ''}
42 + </div>
43 + `;
44 + },
45 +
46 + renderProfile(data) {
47 + // Cabecera con datos completos si el backend los trae.
48 + if (data.nombre || data.matricula || data.carrera) {
49 + this.renderHeaderData(data);
50 + }
51 +
52 + // Propósito
53 + const purposeBox = document.getElementById('profile-purpose');
54 + const purposeText = document.getElementById('profile-purpose-text');
55 + if (purposeBox && purposeText) {
56 + if (data.proposito) {
57 + purposeText.textContent = data.proposito;
58 + purposeBox.hidden = false;
59 + } else {
60 + purposeBox.hidden = true;
61 + }
62 + }
63 +
64 + // Certificados
65 + const certs = Array.isArray(data.certificados) ? data.certificados : [];
66 + this.renderCertificados(certs);
67 +
68 + // Stats
69 + this.renderStats(data.stats || {});
70 +
71 + // Actividad
72 + const acts = Array.isArray(data.actividad) ? data.actividad : [];
73 + this.renderActividad(acts);
74 + },
75 +
76 + renderHeaderData(data) {
77 + const host = document.getElementById('profile-header-info');
78 + if (!host) return;
79 +
80 + const nombre = data.nombre || (Auth.getUser() && Auth.getUser().nombre) || '';
81 + const initials = this.initials(nombre);
82 +
83 + const subParts = [];
84 + if (data.matricula) subParts.push(`Matrícula: <strong>${this.esc(data.matricula)}</strong>`);
85 + if (data.carrera) subParts.push(`<strong>${this.esc(data.carrera)}</strong>`);
86 + const sub1 = subParts.length ? `<p class="profile-sub">${subParts.join(' • ')}</p>` : '';
87 +
88 + const subParts2 = [];
89 + if (data.semestre) subParts2.push(`Semestre Vigente: <strong>${this.esc(data.semestre)}° Semestre</strong>`);
90 + if (data.campus) subParts2.push(this.esc(data.campus));
91 + const sub2 = subParts2.length ? `<p class="profile-sub" style="margin-top:2px;">${subParts2.join(' • ')}</p>` : '';
92 +
93 + host.innerHTML = `
94 + <div class="profile-avatar">${this.esc(initials)}</div>
95 + <div>
96 + <h2 class="profile-name">${this.esc(nombre)}</h2>
97 + ${sub1}
98 + ${sub2}
99 + </div>
100 + `;
101 + },
102 +
103 + renderCertificados(certs) {
104 + const host = document.getElementById('profile-certificados');
105 + if (!host) return;
106 + if (!certs.length) { host.innerHTML = ''; return; }
107 + host.innerHTML = certs.map(c => this.certCard(c)).join('');
108 + },
109 +
110 + certCard(c) {
111 + const titulo = c.titulo || '';
112 + const desc = c.descripcion || '';
113 + const estado = c.estado || '';
114 + const semestre = c.semestre ? `${this.esc(c.semestre)}° Semestre` : '';
115 +
116 + let badge = '';
117 + if (estado === 'acreditado') {
118 + badge = `<span class="badge badge-green">✓ Acreditado${semestre ? ' (' + semestre + ')' : ''}</span>`;
119 + } else if (estado === 'encurso' || estado === 'en-curso') {
120 + badge = `<span class="badge badge-green">En Curso${semestre ? ' (' + semestre + ')' : ''}</span>`;
121 + } else if (estado === 'futuro' || estado === 'bloque') {
122 + badge = `<span class="badge badge-gray">Bloque Futuro${semestre ? ' (' + semestre + ')' : ''}</span>`;
123 + } else if (estado) {
124 + badge = `<span class="badge badge-gray">${this.esc(estado)}</span>`;
125 + }
126 +
127 + const activeCls = (estado === 'encurso' || estado === 'en-curso') ? ' active' : '';
128 +
129 + return `
130 + <div class="certificate-item${activeCls}">
131 + <div class="cert-header">
132 + <span class="cert-title">${this.esc(titulo)}</span>
133 + ${badge}
134 + </div>
135 + ${desc ? `<p class="cert-desc">${this.esc(desc)}</p>` : ''}
136 + </div>`;
137 + },
138 +
139 + renderStats(stats) {
140 + const host = document.getElementById('profile-stats');
141 + if (!host) return;
142 + const items = [
143 + { n: stats.dudasResueltas, l: 'Dudas Resueltas' },
144 + { n: stats.apuntesCompartidos, l: 'Apuntes Compartidos' },
145 + { n: stats.votos, l: 'Votos de Utilidad' },
146 + { n: stats.reputacion, l: 'Reputación Académica' }
147 + ];
148 + // Solo se pinta si al menos hay un valor con dato.
149 + const hasData = items.some(it => it.n !== undefined && it.n !== null && it.n !== '');
150 + if (!hasData) { host.innerHTML = ''; return; }
151 + host.innerHTML = items.map(it => `
152 + <div class="stat-box">
153 + <span class="number">${this.esc(it.n !== undefined && it.n !== null && it.n !== '' ? it.n : '—')}</span>
154 + <span class="label">${this.esc(it.l)}</span>
155 + </div>
156 + `).join('');
157 + },
158 +
159 + renderActividad(acts) {
160 + const host = document.getElementById('profile-actividad');
161 + if (!host) return;
162 + if (!acts.length) { host.innerHTML = ''; return; }
163 + host.innerHTML = acts.map(a => `
164 + <li class="activity-item">
165 + <strong>${this.esc(a.titulo || '')}</strong>
166 + <span>${this.esc(a.detalle || a.descripcion || '')}</span>
167 + </li>
168 + `).join('');
169 + },
170 +
171 + initials(name) {
172 + if (!name) return '?';
173 + const parts = String(name).trim().split(/\s+/);
174 + const first = parts[0] ? parts[0][0] : '';
175 + const second = parts[1] ? parts[1][0] : '';
176 + return (first + second).toUpperCase() || '?';
177 + },
178 +
179 + esc(str) {
180 + return String(str == null ? '' : str)
181 + .replace(/&/g, '&amp;')
182 + .replace(/</g, '&lt;')
183 + .replace(/>/g, '&gt;')
184 + .replace(/"/g, '&quot;')
185 + .replace(/'/g, '&#39;');
186 + }
187 +};
188 +
189 +document.addEventListener('DOMContentLoaded', () => Perfil.init());
frontend/js/pages/recursos.js deleted
-4
@@ -1,4 +0,0 @@
1 -document.addEventListener('DOMContentLoaded', () => {
2 - Layout.init('recursos.html');
3 - Layout.setPageTitle('Apuntes');
4 -});
frontend/js/pages/tips.js deleted
-4
@@ -1,4 +0,0 @@
1 -document.addEventListener('DOMContentLoaded', () => {
2 - Layout.init('tips.html');
3 - Layout.setPageTitle('Tips Académicos');
4 -});
frontend/pages/comunidad.html new
+75
@@ -0,0 +1,75 @@
1 +<!DOCTYPE html>
2 +<html lang="es">
3 +<head>
4 + <meta charset="UTF-8">
5 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 + <title>Comunidad y Recursos | MAPS Connect</title>
7 + <link rel="stylesheet" href="../css/base.css">
8 + <link rel="stylesheet" href="../css/layout.css">
9 + <link rel="stylesheet" href="../css/components.css">
10 + <link rel="stylesheet" href="../css/comunidad.css">
11 +</head>
12 +<body>
13 + <div class="app-layout">
14 + <aside class="sidebar" id="sidebar"></aside>
15 + <div class="main-wrapper">
16 + <header class="topbar" id="topbar"></header>
17 + <main class="main-content" id="main-content">
18 +
19 + <section class="academic-hub-container">
20 +
21 + <!-- 1. BARRA SUPERIOR DE FILTROS Y ACCIÓN CONTEXTUAL -->
22 + <div class="hub-filter-bar">
23 + <input type="text" class="input-search" id="hub-search" placeholder="Buscar por tema, duda, apunte o concepto clave...">
24 +
25 + <select class="select-materia" id="hub-materia">
26 + <option value="">Todas las materias del semestre</option>
27 + </select>
28 +
29 + <!-- Este botón cambia según la pestaña activa mediante JS -->
30 + <button class="btn-solid" id="context-action-btn" type="button">+ Preguntar Duda</button>
31 + </div>
32 +
33 + <!-- 2. PESTAÑAS DE NAVEGACIÓN INTERNA -->
34 + <nav class="hub-tabs" role="tablist">
35 + <button class="tab-button active" type="button" role="tab"
36 + data-tab="foro-dudas" data-action="+ Preguntar Duda">
37 + Foro de Dudas
38 + </button>
39 + <button class="tab-button" type="button" role="tab"
40 + data-tab="apuntes" data-action="+ Subir Apunte (PDF)">
41 + Repositorio de Apuntes
42 + </button>
43 + <button class="tab-button" type="button" role="tab"
44 + data-tab="tips" data-action="+ Compartir Tip">
45 + Tips Académicos
46 + </button>
47 + </nav>
48 +
49 + <!-- ================= PESTAÑA 1: FORO DE DUDAS ================= -->
50 + <section id="foro-dudas" class="tab-panel active" role="tabpanel">
51 + <!-- Las dudas se renderizan aquí solo cuando el backend responde -->
52 + </section>
53 +
54 + <!-- ================= PESTAÑA 2: APUNTES Y REPOSITORIO ================= -->
55 + <section id="apuntes" class="tab-panel" role="tabpanel">
56 + <!-- Los apuntes se renderizan aquí solo cuando el backend responde -->
57 + </section>
58 +
59 + <!-- ================= PESTAÑA 3: TIPS ACADÉMICOS ================= -->
60 + <section id="tips" class="tab-panel" role="tabpanel">
61 + <!-- Los tips se renderizan aquí solo cuando el backend responde -->
62 + </section>
63 +
64 + </section>
65 +
66 + </main>
67 + </div>
68 + </div>
69 + <script src="../js/api.js"></script>
70 + <script src="../js/auth.js"></script>
71 + <script src="../js/utils.js"></script>
72 + <script src="../js/layout.js"></script>
73 + <script src="../js/pages/comunidad.js"></script>
74 +</body>
75 +</html>
frontend/pages/foro.html deleted
-29
@@ -1,29 +0,0 @@
1 -<!DOCTYPE html>
2 -<html lang="es">
3 -<head>
4 - <meta charset="UTF-8">
5 - <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 - <title>Foro | MAPS Connect</title>
7 - <link rel="stylesheet" href="../css/base.css">
8 - <link rel="stylesheet" href="../css/layout.css">
9 - <link rel="stylesheet" href="../css/components.css">
10 -</head>
11 -<body>
12 - <div class="app-layout">
13 - <aside class="sidebar" id="sidebar"></aside>
14 - <div class="main-wrapper">
15 - <header class="topbar" id="topbar"></header>
16 - <main class="main-content" id="main-content">
17 -
18 - DUDAS RESUELTAS, COMENTARIOS, INTERACCIONES "LIKES" AL COMENTARIO
19 -
20 - </main>
21 - </div>
22 - </div>
23 - <script src="../js/api.js"></script>
24 - <script src="../js/auth.js"></script>
25 - <script src="../js/utils.js"></script>
26 - <script src="../js/layout.js"></script>
27 - <script src="../js/pages/foro.js"></script>
28 -</body>
29 -</html>
frontend/pages/inicio.html
+1 -4
@@ -16,10 +16,7 @@
16 <div class="main-wrapper">
17 <header class="topbar" id="topbar"></header>
18 <main class="main-content" id="main-content">
19 -
20 - hola
21 -
22 -
19 +
20 </main>
21 </div>
22 </div>
frontend/pages/mensajes.html
+32 -60
@@ -7,6 +7,7 @@
7 <link rel="stylesheet" href="../css/base.css">
8 <link rel="stylesheet" href="../css/layout.css">
9 <link rel="stylesheet" href="../css/components.css">
10 + <link rel="stylesheet" href="../css/mensajes.css">
11 </head>
12 <body>
13 <div class="app-layout">
@@ -15,66 +16,37 @@
16 <header class="topbar" id="topbar"></header>
17 <main class="main-content" id="main-content">
18
18 - <section class="mensaje">
19 -
20 - <div class="mensajes-encabezado">
21 - <h2>Mensajes</h2>
22 -
23 - <button class="notificaciones-btn">
24 - Notificaciones
25 - </button>
26 - </div>
27 -
28 - <div class="mensajes-contenedor">
29 -
30 - <aside class="conversaciones">
31 -
32 - <h3>Conversaciones</h3>
33 -
34 - <div class="conversaciom">
35 - <p class="conversacion-nombre">Profesor</p>
36 - <p class="conversacion-preview">
37 - Ultimo mensaje
38 - </p>
39 - </div>
40 -
41 - <div class="conversacion">
42 - <p class="conversacion-nombre">Compañero</p>
43 - <p class="conversacion-previw">
44 - Nos vemos manñana
45 - </p>
46 - </div>
47 -
48 - </aside>
49 -
50 - <section class="chat">
51 -
52 - <div class="chat-emcabezado">
53 - <h3>Selecciona una conversacion</h3>
54 - </div>
55 -
56 - <div class="chat-mensajes" id="chat-mensajes">
57 -
58 - <p>Aqui van a aparecer los mensajes</p>
59 -
60 - </div>
61 -
62 - <div class="chat-escribir">
63 -
64 - <input
65 - type="text"
66 - id="mensaje-input"
67 - placeholder="Escribe un mensaje">
68 -
69 - <button id="enviar-mensaje">
70 - Enviar
71 - </button>
72 -
73 - </div>
74 -
75 - </section>
76 -
77 - </div>
19 + <section class="messages-container">
20 +
21 + <!-- 1. BANDEJA LATERAL IZQUIERDA -->
22 + <aside class="chat-sidebar">
23 + <div class="chat-search-box">
24 + <input type="text" id="chat-search" placeholder="Buscar compañero o profesor...">
25 + </div>
26 + <div class="conversation-list" id="conversacion-list">
27 + <!-- Las conversaciones se renderizan aquí solo cuando el backend responde -->
28 + </div>
29 + </aside>
30 +
31 + <!-- 2. VENTANA DE CHAT DERECHA -->
32 + <section class="chat-window">
33 +
34 + <header class="chat-header" id="chat-header">
35 + <!-- Cabecera del chat: se renderiza al seleccionar una conversación -->
36 + </header>
37 +
38 + <div class="chat-body" id="chat-body">
39 + <div class="chat-empty" id="chat-empty">Selecciona una conversación para ver los mensajes.</div>
40 + <!-- Los mensajes se renderizan aquí solo cuando el backend responde -->
41 + </div>
42 +
43 + <footer class="chat-input-bar">
44 + <button class="btn-clean" type="button" id="btn-adjuntar" title="Adjuntar archivo o apunte">📎</button>
45 + <input type="text" id="mensaje-input" placeholder="Escribe un mensaje..." disabled>
46 + <button class="btn-solid" type="button" id="enviar-mensaje" disabled>Enviar</button>
47 + </footer>
48 +
49 + </section>
50
51 </section>
52
frontend/pages/perfil.html
+36 -31
@@ -7,6 +7,7 @@
7 <link rel="stylesheet" href="../css/base.css">
8 <link rel="stylesheet" href="../css/layout.css">
9 <link rel="stylesheet" href="../css/components.css">
10 + <link rel="stylesheet" href="../css/perfil.css">
11 </head>
12 <body>
13 <div class="app-layout">
@@ -15,45 +16,49 @@
16 <header class="topbar" id="topbar"></header>
17 <main class="main-content" id="main-content">
18
18 - <section class="perfil">
19 + <section class="perfil-container" id="perfil-root">
20
21 + <!-- 1. CABECERA DE PERFIL -->
22 + <div class="card profile-header-card">
23 + <div class="profile-main-info" id="profile-header-info">
24 + <!-- Avatar, nombre y datos se renderizan desde el backend (o sesión) -->
25 + </div>
26 + <div>
27 + <button class="btn-clean" type="button" id="btn-editar-proposito">Editar Propósito</button>
28 + </div>
29 + </div>
30
21 - <div class="perfil-encabezado">
22 -
23 - <img
24 - id="perfil-foto"
25 - class="perfil-foto"
26 - src="../assets/default-avatar.png"
27 - alt="Foto de perfil"
28 - >
29 -
30 - <h2 id="perfil-nombre">
31 - Nombre del estudiante
32 - </h2>
31 + <!-- 2. LAYOUT A DOS COLUMNAS -->
32 + <div class="profile-grid">
33
34 - </div>
34 + <!-- COLUMNA IZQUIERDA: TRAYECTORIA ACADÉMICA MAPS -->
35 + <section class="card">
36 + <h3 class="section-heading">Identidad y Ruta MAPS</h3>
37
36 - <div class="perfil-info">
38 + <div class="purpose-box" id="profile-purpose" hidden>
39 + <strong>Propósito de Vida</strong>
40 + <p id="profile-purpose-text"></p>
41 + </div>
42
38 - <div class="perfil-dato">
39 - <span>Correo</span>
40 - <p id="perfil-correo">correo@tecmilenio.mx</p>
41 - </div>
43 + <h4 class="sub-heading">Certificados del Plan de Estudios</h4>
44 + <div id="profile-certificados">
45 + <!-- Los certificados se renderizan aquí solo cuando el backend responde -->
46 + </div>
47 + </section>
48
43 - <div class="perfil-dato">
44 - <span>Matrícula</span>
45 - <p id="perfil-matricula">00000000</p>
46 - </div>
49 + <!-- COLUMNA DERECHA: REPUTACIÓN Y ACTIVIDAD COMUNITARIA -->
50 + <aside class="card">
51 + <h3 class="section-heading">Aportaciones en la Comunidad</h3>
52
48 - <div class="perfil-dato">
49 - <span>Semestre</span>
50 - <p id="perfil-semestre">1</p>
51 - </div>
53 + <div class="stats-summary" id="profile-stats">
54 + <!-- Las estadísticas se renderizan aquí solo cuando el backend responde -->
55 + </div>
56
53 - <div class="perfil-dato">
54 - <span>Carrera</span>
55 - <p id="perfil-carrera">Carrera</p>
56 - </div>
57 + <h4 class="sub-heading">Actividad Reciente</h4>
58 + <ul class="activity-list" id="profile-actividad">
59 + <!-- La actividad se renderiza aquí solo cuando el backend responde -->
60 + </ul>
61 + </aside>
62
63 </div>
64
frontend/pages/recursos.html deleted
-29
@@ -1,29 +0,0 @@
1 -<!DOCTYPE html>
2 -<html lang="es">
3 -<head>
4 - <meta charset="UTF-8">
5 - <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 - <title>Apuntes | MAPS Connect</title>
7 - <link rel="stylesheet" href="../css/base.css">
8 - <link rel="stylesheet" href="../css/layout.css">
9 - <link rel="stylesheet" href="../css/components.css">
10 -</head>
11 -<body>
12 - <div class="app-layout">
13 - <aside class="sidebar" id="sidebar"></aside>
14 - <div class="main-wrapper">
15 - <header class="topbar" id="topbar"></header>
16 - <main class="main-content" id="main-content">
17 -
18 - BORRAR SE VA A PONER EN TIPS
19 -
20 - </main>
21 - </div>
22 - </div>
23 - <script src="../js/api.js"></script>
24 - <script src="../js/auth.js"></script>
25 - <script src="../js/utils.js"></script>
26 - <script src="../js/layout.js"></script>
27 - <script src="../js/pages/recursos.js"></script>
28 -</body>
29 -</html>
frontend/pages/tips.html deleted
-31
@@ -1,31 +0,0 @@
1 -<!DOCTYPE html>
2 -<html lang="es">
3 -<head>
4 - <meta charset="UTF-8">
5 - <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 - <title>Tips | MAPS Connect</title>
7 - <link rel="stylesheet" href="../css/base.css">
8 - <link rel="stylesheet" href="../css/layout.css">
9 - <link rel="stylesheet" href="../css/components.css">
10 -</head>
11 -<body>
12 - <div class="app-layout">
13 - <aside class="sidebar" id="sidebar"></aside>
14 - <div class="main-wrapper">
15 - <header class="topbar" id="topbar"></header>
16 - <main class="main-content" id="main-content">
17 -
18 - REDDIT (CIRCULOS DE ESTUDIO), (APUNES) ESOS VAN AQUI
19 -
20 -
21 -
22 - </main>
23 - </div>
24 - </div>
25 - <script src="../js/api.js"></script>
26 - <script src="../js/auth.js"></script>
27 - <script src="../js/utils.js"></script>
28 - <script src="../js/layout.js"></script>
29 - <script src="../js/pages/tips.js"></script>
30 -</body>
31 -</html>
src/main/java/com/tecmilenio/mapsconect/dto/UsuarioDTO.java
-1
@@ -14,7 +14,6 @@ public class UsuarioDTO {
14 private Integer id;
15 private String email;
16 private String nombre;
17 - private String apellido;
17 private String rol;
18 private Boolean activo;
19
src/main/java/com/tecmilenio/mapsconect/entity/RolConverter.java new
+23
@@ -0,0 +1,23 @@
1 +package com.tecmilenio.mapsconect.entity;
2 +
3 +import jakarta.persistence.AttributeConverter;
4 +import jakarta.persistence.Converter;
5 +
6 +/**
7 + * Convierte el enum Rol (mayúsculas en Java) a minúsculas en la BD,
8 + * para coincidir con el enum de MySQL: ('estudiante','profesor','administrador').
9 + */
10 +@Converter
11 +public class RolConverter implements AttributeConverter<Usuario.Rol, String> {
12 +
13 + @Override
14 + public String convertToDatabaseColumn(Usuario.Rol rol) {
15 + return rol == null ? null : rol.name().toLowerCase();
16 + }
17 +
18 + @Override
19 + public Usuario.Rol convertToEntityAttribute(String dbValue) {
20 + if (dbValue == null) return null;
21 + return Usuario.Rol.valueOf(dbValue.toUpperCase());
22 + }
23 +}
src/main/java/com/tecmilenio/mapsconect/entity/Usuario.java
+12 -13
@@ -21,34 +21,33 @@ public class Usuario {
21 @Column(name = "id_usuario")
22 private Integer id;
23
24 - @Column(name = "correo", nullable = false, unique = true, length = 100)
24 + @Column(name = "correo", nullable = false, unique = true, length = 150)
25 private String email;
26
27 - @Column(nullable = false, length = 100)
28 - private String nombre;
29 -
30 - @Column(nullable = false, length = 100)
31 - private String apellido;
27 + @Column(name = "nombre_completo", nullable = false, length = 150)
28 + private String nombreCompleto;
29
30 @Column(name = "contrasena_hash", nullable = false)
31 private String contrasena;
32
36 - @Enumerated(EnumType.STRING)
33 + @Convert(converter = RolConverter.class)
34 + @Column(name = "rol", nullable = false)
35 private Rol rol;
36
37 @Builder.Default
40 - @Column(nullable = false)
41 - private Boolean activo = true;
38 + @Column(name = "puntos_reputacion", nullable = false)
39 + private Integer puntosReputacion = 0;
40
41 @Builder.Default
42 @Column(nullable = false)
45 - private LocalDateTime fechaCreacion = LocalDateTime.now();
43 + private Boolean activo = true;
44
47 - @Column
48 - private LocalDateTime fechaActualizacion;
45 + @Builder.Default
46 + @Column(name = "fecha_registro", nullable = false)
47 + private LocalDateTime fechaRegistro = LocalDateTime.now();
48
49 public enum Rol {
51 - ESTUDIANTE, DOCENTE, EGRESADO, ADMIN
50 + ESTUDIANTE, PROFESOR, ADMINISTRADOR
51 }
52
53 }
src/main/java/com/tecmilenio/mapsconect/service/UsuarioService.java
+2 -4
@@ -31,8 +31,7 @@ public class UsuarioService {
31
32 Usuario usuario = Usuario.builder()
33 .email(registroDTO.getEmail())
34 - .nombre(registroDTO.getNombre())
35 - .apellido(registroDTO.getApellido())
34 + .nombreCompleto(registroDTO.getNombre() + " " + registroDTO.getApellido())
35 .contrasena(passwordEncoder.encode(registroDTO.getContrasena()))
36 .rol(Usuario.Rol.ESTUDIANTE)
37 .activo(true)
@@ -77,8 +76,7 @@ public class UsuarioService {
76 return UsuarioDTO.builder()
77 .id(usuario.getId())
78 .email(usuario.getEmail())
80 - .nombre(usuario.getNombre())
81 - .apellido(usuario.getApellido())
79 + .nombre(usuario.getNombreCompleto())
80 .rol(usuario.getRol().toString())
81 .activo(usuario.getActivo())
82 .build();
src/main/resources/application-dev.yml
+2 -2
@@ -1,8 +1,8 @@
1 spring:
2 datasource:
3 - url: jdbc:mysql://localhost:3306/maps_conect_dev
3 + url: jdbc:mysql://localhost:3306/maps_conect
4 username: root
5 - password: Haloymega59!
5 + password: Berlin007.
6
7 jpa:
8 hibernate: