main
js 224 lines 12.2 KB
Raw
1 /**
2 * Panel de inicio.
3 * Contrato esperado de GET /inicio:
4 * { perfil, materias, publicaciones, circuloActual, asesoria, empresaDestacada }
5 */
6 document.addEventListener('DOMContentLoaded', async () => {
7 Layout.init('inicio.html');
8 Layout.setPageTitle('Inicio');
9
10 const content = document.getElementById('main-content');
11 if (!content) return;
12
13 content.innerHTML = '<div class="inicio-container"><p class="loading-message">Cargando tu información académica…</p></div>';
14
15 let dashboard = {};
16 try {
17 dashboard = await API.request('/inicio');
18 } catch {
19 // El endpoint se implementará en el backend. La UI mantiene estados vacíos.
20 }
21
22 renderDashboard(content, dashboard || {});
23
24 mostrarBienvenida();
25 });
26
27 function mostrarBienvenida() {
28 // Limpia cualquier flag viejo que pudiera haber quedado de versiones anteriores.
29 try { localStorage.removeItem('bienvenida-pendiente'); } catch (e) {}
30
31 let pendiente = false;
32 try {
33 pendiente =
34 localStorage.getItem('bienvenida-pendiente') === '1' ||
35 sessionStorage.getItem('bienvenida-pendiente') === '1';
36 } catch (e) {}
37 if (!pendiente) return;
38
39 try {
40 sessionStorage.removeItem('bienvenida-pendiente');
41 localStorage.removeItem('bienvenida-pendiente');
42 } catch (e) {}
43
44 const nombre = (Auth.getUser() && Auth.getUser().nombre) || '';
45
46 const overlay = document.createElement('div');
47 overlay.className = 'welcome-overlay';
48 overlay.id = 'welcomeOverlay';
49 overlay.setAttribute('aria-hidden', 'true');
50 overlay.innerHTML = `
51 <div class="welcome-popup">
52 <div class="popup-tag">
53 <span class="pulse-mini"></span>
54 <span>MAPS CONNECT // ACCESS GRANTED</span>
55 </div>
56 <h1 class="popup-title">BIENVENIDO</h1>
57 <div class="popup-username" id="popupUserName">${escapeHtml(nombre || 'Usuario').toUpperCase()}</div>
58 <p class="popup-status">SESIÓN INICIADA • CARGANDO ENTORNO HUD...</p>
59 </div>
60 `;
61 document.body.appendChild(overlay);
62
63 setTimeout(() => {
64 overlay.classList.add('fade-out');
65 // Elimina el overlay del DOM al terminar la transición para que nunca se quede pegado.
66 setTimeout(() => {
67 if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
68 }, 550);
69 }, 1500);
70 }
71
72 function renderDashboard(content, data) {
73 const perfil = data.perfil || null;
74 const esDocente = perfil?.rol === 'PROFESOR';
75 const materias = Array.isArray(data.materias) ? data.materias : [];
76 const publicaciones = Array.isArray(data.publicaciones) ? data.publicaciones : [];
77 const accesos = renderAccessCards(data);
78
79 content.innerHTML = `
80 <div class="inicio-container">
81 ${perfil ? (esDocente ? renderDocenteBanner(perfil) : renderBanner(perfil)) : ''}
82 ${data.carreraFiltrada && !esDocente ? `<p class="filter-note">Mostrando solo contenido de tu carrera: <strong>${escapeHtml(data.carreraFiltrada)}</strong></p>` : ''}
83 <div class="main-layout">
84 <section class="feed-column" aria-label="Actividad reciente">
85 ${!esDocente && materias.length ? renderQuickQuestion(materias) : ''}
86 ${publicaciones.length ? publicaciones.map(renderPost).join('') : renderEmptyFeed()}
87 </section>
88 ${accesos ? `<aside class="sidebar-section" aria-label="Accesos directos">${accesos}</aside>` : ''}
89 </div>
90 </div>
91 `;
92
93 document.getElementById('quick-question-form')?.addEventListener('submit', event => {
94 event.preventDefault();
95 window.location.href = 'comunidad.html#foro-dudas';
96 });
97
98 content.addEventListener('click', event => {
99 const followBtn = event.target.closest('.follow-btn');
100 if (!followBtn) return;
101 event.preventDefault();
102 const id = followBtn.getAttribute('data-id');
103 if (!id) return;
104 Utils.alternarSeguir(followBtn, id);
105 });
106 }
107
108 function renderDocenteBanner(perfil) {
109 return `
110 <section class="card maps-banner prof-card" aria-labelledby="prof-title">
111 <div class="banner-content">
112 <div>
113 <h2 id="prof-title">Bienvenido, Docente</h2>
114 <p>Panel de docencia MAPS. Aquí aparecerán los avisos y materiales que compartes con tu comunidad.</p>
115 </div>
116 <span class="badge badge-verified">Perfil Docente</span>
117 </div>
118 </section>
119 `;
120 }
121
122 function renderBanner(perfil) {
123 const semestre = Number(perfil.semestre) || 0;
124 const totalSemestres = Number(perfil.totalSemestres) || 0;
125 const avance = totalSemestres ? Math.min(100, Math.round((semestre / totalSemestres) * 100)) : 0;
126
127 return `
128 <section class="card maps-banner" aria-labelledby="maps-title">
129 <div class="banner-content">
130 <div>
131 <h2 id="maps-title">${escapeHtml(perfil.carrera || 'Mi trayectoria MAPS')}</h2>
132 <p>${perfil.certificado ? `Certificado Activo: <strong>${escapeHtml(perfil.certificado)}</strong>` : 'Sin certificado activo'}${semestre ? ` • Semestre Vigente: <strong>${semestre}°</strong>` : ''}</p>
133 </div>
134 <span class="badge badge-materia">Plan MAPS Oficial</span>
135 </div>
136 ${totalSemestres ? `<div class="progress-container"><div class="progress-labels"><span>Avance de Carrera</span><span>Semestre ${semestre} de ${totalSemestres} (${avance}%)</span></div><div class="progress-bar" role="progressbar" aria-label="Avance de carrera" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${avance}"><div class="progress-fill" style="width: ${avance}%"></div></div></div>` : ''}
137 </section>
138 `;
139 }
140
141 function renderQuickQuestion(materias) {
142 const options = materias.map(materia => `<option value="${escapeHtml(materia.id ?? '')}">${escapeHtml(materia.nombre || materia)}</option>`).join('');
143 return `
144 <form class="card create-post" id="quick-question-form">
145 <label class="visually-hidden" for="quick-question">Nueva duda académica</label>
146 <input id="quick-question" name="question" type="text" maxlength="180" placeholder="¿Tienes una duda académica? Pregunta a tu comunidad..." required>
147 <div class="create-post-actions"><select aria-label="Materia de la duda"><option value="">Seleccionar materia...</option>${options}</select><button class="btn-solid" type="submit">Publicar duda</button></div>
148 </form>
149 `;
150 }
151
152 function renderPost(post) {
153 const author = (post.autor && typeof post.autor === 'object') ? post.autor : {};
154 const esDocente = author.tipo === 'profesor' || author.tipo === 'docente' || Boolean(post.verificadoDocente);
155 const esMaterial = post.tipoContenido === 'material';
156 const nombre = author.nombre || 'Usuario';
157 const meta = esMaterial
158 ? ['Docente', post.materia ? `de ${post.materia}` : ''].filter(Boolean).join(' ')
159 : (author.semestre ? `${author.semestre}° Semestre` : 'Estudiante');
160 const tiempo = post.fechaRelativa ? ` • ${post.fechaRelativa}` : '';
161 const meId = Auth.getUser() && Auth.getUser().id;
162 const clickeable = author.id && author.id !== meId;
163 const hrefAutor = `usuario.html?id=${encodeURIComponent(author.id)}`;
164 const avatarWrap = clickeable
165 ? `<a class="user-link" href="${hrefAutor}" aria-label="Ver perfil de ${escapeHtml(nombre)}">${Utils.avatarHtml(nombre, author.foto, `avatar ${esDocente ? 'prof' : ''}`, nombre)}</a>`
166 : Utils.avatarHtml(nombre, author.foto, `avatar ${esDocente ? 'prof' : ''}`, nombre);
167 const nombreWrap = clickeable
168 ? `<a class="user-link" href="${hrefAutor}"><strong>${escapeHtml(nombre)}</strong></a>`
169 : `<strong>${escapeHtml(nombre)}</strong>`;
170 const seguirBoton = (author.id && author.id !== meId)
171 ? `<button class="follow-btn${author.seguido ? ' siguiendo' : ''}" type="button" data-id="${author.id}" data-siguiendo="${author.seguido ? 'true' : 'false'}">${author.seguido ? 'Siguiendo' : 'Seguir'}</button>`
172 : '';
173
174 const tags = [
175 post.materia && `<span class="badge badge-materia">${escapeHtml(post.materia)}</span>`,
176 post.participado && '<span class="badge badge-interacted">Has participado</span>',
177 post.solucionAceptada && '<span class="badge badge-solved">Solución Aceptada</span>',
178 esDocente && '<span class="badge badge-verified">Aviso Docente</span>',
179 ].filter(Boolean).join('');
180
181 const embed = esMaterial && post.archivo
182 ? `<div class="embed-box prof-box"><strong style="color:#0284c7;">Archivo disponible:</strong><p>${escapeHtml(post.archivo.nombre)} (${escapeHtml(post.archivo.peso)}) • ${escapeHtml(post.archivo.descargas)} descargas de tu carrera</p></div>`
183 : (post.respuestaUsuario ? `<div class="embed-box"><strong>Tu respuesta:</strong><p>${escapeHtml(post.respuestaUsuario)}</p></div>` : '');
184
185 const votos = Number.isFinite(Number(post.votos)) ? Number(post.votos) : 0;
186 const botonVotos = post.participado
187 ? `<button class="btn-action voted" type="button">Votado (${votos})</button>`
188 : `<button class="btn-action" type="button">${votos} Votos</button>`;
189 const tercerBoton = esMaterial
190 ? '<button class="btn-action" type="button">Descargar</button>'
191 : '<button class="btn-action" type="button">Guardar</button>';
192 const status = esMaterial ? 'Material Oficial' : (post.solucionAceptada ? 'Resuelto' : '');
193
194 return `
195 <article class="card post-card">
196 <div class="post-header"><div class="user-block">${avatarWrap}<div class="user-data"><div class="user-data-line">${nombreWrap}${seguirBoton}</div><span>${escapeHtml(meta + tiempo)}</span></div></div><div class="badges-wrap">${tags}</div></div>
197 <h3 class="post-title">${escapeHtml(post.titulo || '')}</h3>
198 ${post.contenido ? `<p class="post-body">${escapeHtml(post.contenido)}</p>` : ''}
199 ${embed}
200 <div class="post-footer"><div class="action-links">${botonVotos}<button class="btn-action" type="button">${Number(post.respuestas) || 0} ${esMaterial ? 'Comentarios' : 'Respuestas'}</button>${tercerBoton}</div>${status ? `<span class="post-status">${escapeHtml(status)}</span>` : ''}</div>
201 </article>
202 `;
203 }
204
205 function renderAccessCards(data) {
206 const cards = [];
207 const circulo = data.circuloActual;
208 const asesoria = data.asesoria;
209 const empresa = data.empresaDestacada;
210
211 if (circulo) cards.push(`<section class="card side-card"><span class="badge badge-materia">${escapeHtml(circulo.fecha || 'Próximamente')}</span><h4>${escapeHtml(circulo.nombre || 'Círculo de estudio')}</h4>${circulo.descripcion ? `<p>${escapeHtml(circulo.descripcion)}</p>` : ''}<a class="btn-solid" style="width:100%;" href="circulos.html">${circulo.plataforma ? `Entrar a Sala (${escapeHtml(circulo.plataforma)})` : 'Ver círculo'}</a></section>`);
212 if (asesoria) cards.push(`<section class="card side-card prof-card"><span class="badge badge-verified">Asesoría Disponible</span><h4>${escapeHtml(asesoria.docente || 'Docente')}</h4>${asesoria.horario ? `<p>Horario de atención para dudas del certificado: <strong>${escapeHtml(asesoria.horario)}</strong></p>` : ''}<a class="btn-clean" href="mensajes.html">Enviar Mensaje Privado</a></section>`);
213 if (empresa) cards.push(`<section class="card side-card"><h4>Radar Semestre Empresarial</h4><p><strong>${escapeHtml(empresa.nombre || '')}${empresa.calificacion ? ` • ${escapeHtml(empresa.calificacion)}` : ''}</strong>${empresa.resena ? `<br>${escapeHtml(empresa.resena)}` : ''}</p><a class="side-link" href="empresarial.html">Ver directorio de empresas →</a></section>`);
214
215 return cards.join('');
216 }
217
218 function renderEmptyFeed() {
219 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>';
220 }
221
222 function escapeHtml(value) {
223 return String(value ?? '').replace(/[&<>'"]/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' })[character]);
224 }