main
js 253 lines 11.2 KB
Raw
1 /**
2 * Panel Docente - Gestion y Acompanamiento Academico.
3 * Contrato de GET /docente/stats:
4 * { perfil: { nombre, nomina, area, horario, enlace, rol },
5 * kpis: { dudasPorValidar, circulosACargo, alumnosAsesorados, descargasApuntes },
6 * dudas: [{ id, titulo, contenido, materia, autor }],
7 * asesorias: [{ id, titulo, materia, fecha, hora, inscritos, cupoMax, enlace }] }
8 */
9 document.addEventListener('DOMContentLoaded', async () => {
10 Layout.init('docente.html');
11 Layout.setPageTitle('Gestion Docente');
12
13 const content = document.getElementById('main-content');
14 if (!content) return;
15
16 content.innerHTML = '<div class="docente-container"><p class="loading-message">Cargando panel docente...</p></div>';
17
18 let data = {};
19 try {
20 data = await API.request('/docente/stats');
21 } catch (e) {
22 content.innerHTML = `<div class="docente-container"><div class="card docente-empty">No se pudo cargar el panel: ${Utils.esc(e.message || 'sin permisos de profesor')}</div></div>`;
23 return;
24 }
25
26 renderDocente(content, data || {});
27 });
28
29 function renderDocente(content, data) {
30 const perfil = data.perfil || {};
31 const kpis = data.kpis || {};
32 const dudas = Array.isArray(data.dudas) ? data.dudas : [];
33 const asesorias = Array.isArray(data.asesorias) ? data.asesorias : [];
34
35 content.innerHTML = `
36 <div class="docente-container">
37 <header class="card docente-header">
38 <div class="header-info">
39 <h2>Espacio de Acompanamiento Academico</h2>
40 <p>
41 Profesor: <strong>${Utils.esc(perfil.nombre || '')}</strong>
42 &bull; Nomina: <strong>${Utils.esc(perfil.nomina || '')}</strong>
43 &bull; ${Utils.esc(perfil.area || '')}
44 </p>
45 </div>
46 <div>
47 <span class="docente-badge">Rol: ${Utils.esc(perfil.rol || 'Asesor Docente')}</span>
48 </div>
49 </header>
50
51 <div class="docente-kpi-grid">
52 <div class="docente-kpi">
53 <div class="val">${Number(kpis.dudasPorValidar) || 0}</div>
54 <div class="lbl">Dudas por Validar</div>
55 </div>
56 <div class="docente-kpi">
57 <div class="val">${Number(kpis.circulosACargo) || 0}</div>
58 <div class="lbl">Circulos a mi Cargo</div>
59 </div>
60 <div class="docente-kpi">
61 <div class="val">${Number(kpis.alumnosAsesorados) || 0}</div>
62 <div class="lbl">Alumnos Asesorados</div>
63 </div>
64 <div class="docente-kpi">
65 <div class="val">${Number(kpis.descargasApuntes) || 0}</div>
66 <div class="lbl">Descargas de mis Apuntes</div>
67 </div>
68 </div>
69
70 <section class="card docente-section">
71 <div class="docente-section-header">
72 <h3>Dudas de tus Grupos que Esperan Visto Bueno</h3>
73 </div>
74 ${dudas.length ? `
75 <table class="docente-table">
76 <thead>
77 <tr>
78 <th>Materia</th>
79 <th>Pregunta del Alumno</th>
80 <th>Autor</th>
81 <th>Accion Docente</th>
82 </tr>
83 </thead>
84 <tbody>
85 ${dudas.map(d => `
86 <tr>
87 <td><strong>${Utils.esc(d.materia || 'General')}</strong></td>
88 <td>${Utils.esc(d.titulo || d.contenido || '')}</td>
89 <td>${Utils.esc(d.autor || '')}</td>
90 <td><button class="btn-solid docente-btn-validar" data-id="${d.id}" style="padding: 5px 10px; font-size: 0.8rem;">Validar y Cerrar</button></td>
91 </tr>`).join('')}
92 </tbody>
93 </table>` : '<p class="docente-empty">No hay dudas pendientes por validar.</p>'}
94 </section>
95
96 <section class="card docente-section">
97 <div class="docente-section-header">
98 <h3>Mis Salases de Asesoria Programadas</h3>
99 <button class="btn-outline" id="btn-agendar">+ Agendar Nueva Asesoria</button>
100 </div>
101 <p class="docente-meta">
102 Horario configurado: <strong>${Utils.esc(perfil.horario || 'Sin horario configurado')}</strong>
103 ${perfil.enlace ? ` &bull; Sala virtual: <a class="docente-link" href="${Utils.esc(perfil.enlace)}" target="_blank">${Utils.esc(perfil.enlace)}</a>` : ''}
104 </p>
105 ${asesorias.length ? `
106 <table class="docente-table">
107 <thead>
108 <tr>
109 <th>Fecha y Hora</th>
110 <th>Materia / Tema</th>
111 <th>Inscritos</th>
112 <th>Enlace</th>
113 </tr>
114 </thead>
115 <tbody>
116 ${asesorias.map(a => `
117 <tr>
118 <td>${Utils.esc(a.fecha || '')} ${Utils.esc(a.hora || '')}</td>
119 <td>${Utils.esc(a.materia || '')} &mdash; ${Utils.esc(a.titulo || '')}</td>
120 <td>${a.inscritos} / ${a.cupoMax}</td>
121 <td>${a.enlace ? `<a class="docente-link" href="${Utils.esc(a.enlace)}" target="_blank">Abrir sala</a>` : '<span style="color:#999">—</span>'}</td>
122 </tr>`).join('')}
123 </tbody>
124 </table>` : '<p class="docente-empty">No hay sesiones de asesoria programadas.</p>'}
125 </section>
126 </div>
127 `;
128
129 document.querySelectorAll('.docente-btn-validar').forEach(btn => {
130 btn.addEventListener('click', async () => {
131 const id = btn.getAttribute('data-id');
132 if (!id) return;
133 btn.disabled = true;
134 btn.textContent = 'Cerrando...';
135 try {
136 await API.request(`/docente/dudas/${id}/validar`, { method: 'POST' });
137 Utils.toast('Duda validada y cerrada.', 'success');
138 refresh();
139 } catch (e) {
140 Utils.toast(e.message || 'No se pudo validar la duda.', 'error');
141 btn.disabled = false;
142 btn.textContent = 'Validar y Cerrar';
143 }
144 });
145 });
146
147 document.getElementById('btn-agendar')?.addEventListener('click', () => {
148 showAgendarForm(content);
149 });
150 }
151
152 function refresh() {
153 const content = document.getElementById('main-content');
154 if (!content) return;
155 content.innerHTML = '<div class="docente-container"><p class="loading-message">Cargando panel docente...</p></div>';
156 API.request('/docente/stats')
157 .then(data => renderDocente(content, data || {}))
158 .catch(e => {
159 content.innerHTML = `<div class="docente-container"><div class="card docente-empty">No se pudo cargar el panel: ${Utils.esc(e.message || 'error')}</div></div>`;
160 });
161 }
162
163 function showAgendarForm(content) {
164 const overlay = document.createElement('div');
165 overlay.className = 'modal-overlay';
166 overlay.innerHTML = `
167 <div class="modal docente-asistencia-modal">
168 <h3>Agendar Nueva Asesoria</h3>
169 <form id="agendar-form">
170 <label>Titulo / Tema</label>
171 <input type="text" id="ag-titulo" required placeholder="Ej. Repaso de Patrones de Diseño">
172
173 <label>Materia</label>
174 <input type="text" id="ag-materia" required placeholder="Ej. Arquitectura de Software">
175
176 <label>Descripcion</label>
177 <textarea id="ag-descripcion" rows="3" placeholder="Temas que se cubriran..."></textarea>
178
179 <div class="ag-row">
180 <div>
181 <label>Fecha</label>
182 <input type="date" id="ag-fecha" required>
183 </div>
184 <div>
185 <label>Hora</label>
186 <input type="time" id="ag-hora" required>
187 </div>
188 </div>
189
190 <div class="ag-row">
191 <div>
192 <label>Modalidad</label>
193 <select id="ag-modalidad">
194 <option value="Grupal">Grupal</option>
195 <option value="Individual">Individual</option>
196 <option value="Virtual">Virtual</option>
197 </select>
198 </div>
199 <div>
200 <label>Ubicacion</label>
201 <input type="text" id="ag-ubicacion" placeholder="Salon o enlace de sala">
202 </div>
203 </div>
204
205 <div class="ag-row">
206 <div>
207 <label>Duracion (min)</label>
208 <input type="number" id="ag-duracion" value="90" min="15">
209 </div>
210 <div>
211 <label>Cupo maximo</label>
212 <input type="number" id="ag-cupo" value="30" min="1">
213 </div>
214 </div>
215
216 <div class="modal-actions">
217 <button type="button" class="btn-outline" id="ag-cancelar">Cancelar</button>
218 <button type="submit" class="btn-solid">Programar Asesoria</button>
219 </div>
220 </form>
221 </div>
222 `;
223 document.body.appendChild(overlay);
224
225 overlay.querySelector('#ag-cancelar').addEventListener('click', () => overlay.remove());
226 overlay.querySelector('#agendar-form').addEventListener('submit', async (e) => {
227 e.preventDefault();
228 const payload = {
229 titulo: overlay.querySelector('#ag-titulo').value.trim(),
230 materia: overlay.querySelector('#ag-materia').value.trim(),
231 descripcion: overlay.querySelector('#ag-descripcion').value.trim(),
232 fecha: overlay.querySelector('#ag-fecha').value,
233 hora: overlay.querySelector('#ag-hora').value,
234 modalidad: overlay.querySelector('#ag-modalidad').value,
235 ubicacion: overlay.querySelector('#ag-ubicacion').value.trim() || null,
236 duracionMin: parseInt(overlay.querySelector('#ag-duracion').value, 10) || 90,
237 cupoMax: parseInt(overlay.querySelector('#ag-cupo').value, 10) || 30
238 };
239 const submitBtn = overlay.querySelector('button[type="submit"]');
240 submitBtn.disabled = true;
241 submitBtn.textContent = 'Programando...';
242 try {
243 await API.request('/docente/asesorias', { method: 'POST', body: JSON.stringify(payload) });
244 Utils.toast('Asesoria programada.', 'success');
245 overlay.remove();
246 refresh();
247 } catch (err) {
248 Utils.toast(err.message || 'No se pudo programar la asesoria.', 'error');
249 submitBtn.disabled = false;
250 submitBtn.textContent = 'Programar Asesoria';
251 }
252 });
253 }