| 1 | (function () { |
| 2 | let rolActual = null; |
| 3 | |
| 4 | document.addEventListener('DOMContentLoaded', () => { |
| 5 | if (!Auth.requireAuth()) return; |
| 6 | |
| 7 | const user = Auth.getUser(); |
| 8 | rolActual = user?.rol || ''; |
| 9 | document.getElementById('ono-titulo').textContent = |
| 10 | rolActual === 'PROFESOR' ? 'Perfil del Maestro' : 'Bienvenido, estudiante'; |
| 11 | |
| 12 | if (rolActual === 'ESTUDIANTE') { |
| 13 | document.getElementById('ono-subtitulo').textContent = |
| 14 | 'Cuéntanos tu carrera y semestre para mostrar tus materias y empezar a navegar.'; |
| 15 | renderFormularioEstudiante(); |
| 16 | } else if (rolActual === 'PROFESOR') { |
| 17 | document.getElementById('ono-subtitulo').textContent = |
| 18 | 'Define tu asignación académica y tus datos de asesoría para aparecer en el directorio.'; |
| 19 | renderFormularioProfesor(); |
| 20 | } else { |
| 21 | window.location.href = Auth.resolvePath('pages/inicio.html'); |
| 22 | } |
| 23 | }); |
| 24 | |
| 25 | function cargarOpciones(url, textoVacio) { |
| 26 | return API.request(url).then(res => res || []); |
| 27 | } |
| 28 | |
| 29 | /* ------------------------- ESTUDIANTE ------------------------- */ |
| 30 | |
| 31 | async function renderFormularioEstudiante() { |
| 32 | const cont = document.getElementById('ono-contenido'); |
| 33 | const matricula = sessionStorage.getItem('pending-matricula') || ''; |
| 34 | cont.innerHTML = ` |
| 35 | <form id="ono-form-estudiante"> |
| 36 | <div class="form-group"> |
| 37 | <label for="ono-matricula">Matrícula</label> |
| 38 | <input type="text" id="ono-matricula" class="form-control" required |
| 39 | value="${escapeHtml(matricula)}" placeholder="A12345678"> |
| 40 | </div> |
| 41 | <div class="form-group"> |
| 42 | <label for="ono-carrera">Carrera</label> |
| 43 | <select id="ono-carrera" class="form-control" required> |
| 44 | <option value="">Selecciona tu carrera...</option> |
| 45 | </select> |
| 46 | </div> |
| 47 | <div class="form-group"> |
| 48 | <label>Semestre</label> |
| 49 | <div class="ono-grid" id="ono-semestres"></div> |
| 50 | </div> |
| 51 | <div class="ono-section"> |
| 52 | <h3>Materias de tu semestre</h3> |
| 53 | <div id="ono-materias">${spinnerHtml()}</div> |
| 54 | </div> |
| 55 | <div class="form-group" style="margin-top:1.25rem"> |
| 56 | <button type="submit" class="btn btn-primary" style="width:100%">Confirmar y empezar a navegar</button> |
| 57 | </div> |
| 58 | </form>`; |
| 59 | |
| 60 | const selCarrera = document.getElementById('ono-carrera'); |
| 61 | const selSemestre = document.getElementById('ono-semestres'); |
| 62 | |
| 63 | try { |
| 64 | const carreras = await cargarOpciones('/carreras', 'Sin carreras'); |
| 65 | selCarrera.innerHTML = |
| 66 | '<option value="">Selecciona tu carrera...</option>' + |
| 67 | carreras.map(c => `<option value="${c.id}">${escapeHtml(c.nombre)}</option>`).join(''); |
| 68 | |
| 69 | selSemestre.innerHTML = Array.from({ length: 12 }, (_, i) => i + 1).map(s => |
| 70 | `<label class="${s === 1 ? 'sem-activo' : ''}"> |
| 71 | <input type="radio" name="ono-semestre" value="${s}" ${s === 1 ? 'checked' : ''}> |
| 72 | ${s}° |
| 73 | </label>`).join(''); |
| 74 | |
| 75 | selSemestre.addEventListener('change', (e) => { |
| 76 | if (e.target.name === 'ono-semestre') { |
| 77 | selSemestre.querySelectorAll('label').forEach(l => l.classList.remove('sem-activo')); |
| 78 | e.target.closest('label').classList.add('sem-activo'); |
| 79 | cargarMateriasEstudiante(); |
| 80 | } |
| 81 | }); |
| 82 | selCarrera.addEventListener('change', cargarMateriasEstudiante); |
| 83 | |
| 84 | if (carreras.length) cargarMateriasEstudiante(); |
| 85 | |
| 86 | document.getElementById('ono-form-estudiante').addEventListener('submit', async (ev) => { |
| 87 | ev.preventDefault(); |
| 88 | Utils.clearAlert('alert-container'); |
| 89 | const btn = ev.target.querySelector('button[type="submit"]'); |
| 90 | |
| 91 | if (!selCarrera.value) { |
| 92 | Utils.showAlert('alert-container', 'Selecciona tu carrera.'); |
| 93 | return; |
| 94 | } |
| 95 | const semestre = Number(document.querySelector('input[name="ono-semestre"]:checked')?.value || 1); |
| 96 | |
| 97 | try { |
| 98 | btn.disabled = true; |
| 99 | btn.textContent = 'Guardando...'; |
| 100 | const materias = [...document.querySelectorAll('#ono-materias input[type="checkbox"]:checked')] |
| 101 | .map(cb => Number(cb.value)); |
| 102 | await API.request('/auth/onboarding/estudiante', { |
| 103 | method: 'POST', |
| 104 | body: JSON.stringify({ |
| 105 | matricula: document.getElementById('ono-matricula').value.trim(), |
| 106 | idCarrera: Number(selCarrera.value), |
| 107 | semestre, |
| 108 | materias |
| 109 | }) |
| 110 | }); |
| 111 | sessionStorage.removeItem('pending-matricula'); |
| 112 | window.location.href = Auth.resolvePath('pages/inicio.html'); |
| 113 | } catch (error) { |
| 114 | Utils.showAlert('alert-container', error.message || 'No se pudo completar el perfil.'); |
| 115 | btn.disabled = false; |
| 116 | btn.textContent = 'Confirmar y empezar a navegar'; |
| 117 | } |
| 118 | }); |
| 119 | } catch (error) { |
| 120 | cont.innerHTML = `<div class="alert alert-error">Error al cargar el catálogo: ${escapeHtml(error.message)}</div>`; |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | async function cargarMateriasEstudiante() { |
| 125 | const idCarrera = document.getElementById('ono-carrera')?.value; |
| 126 | const semestre = Number(document.querySelector('input[name="ono-semestre"]:checked')?.value || 1); |
| 127 | const contMaterias = document.getElementById('ono-materias'); |
| 128 | if (!idCarrera) { |
| 129 | contMaterias.innerHTML = '<div class="ono-espera">Selecciona tu carrera para ver las materias.</div>'; |
| 130 | return; |
| 131 | } |
| 132 | contMaterias.innerHTML = spinnerHtml(); |
| 133 | try { |
| 134 | const materias = await cargarOpciones(`/materias/plan/${idCarrera}/${semestre}`, 'Sin materias'); |
| 135 | contMaterias.innerHTML = materias.length |
| 136 | ? `<div class="ono-grid">${materias.map(m => ` |
| 137 | <label> |
| 138 | <input type="checkbox" name="ono-materia" value="${m.id}"> ${escapeHtml(m.nombre)} |
| 139 | </label>`).join('')} |
| 140 | </div> |
| 141 | <div class="form-hint">Marca las materias que cursas este semestre.</div>` |
| 142 | : '<div class="ono-espera">No hay materias registradas para ese semestre.</div>'; |
| 143 | } catch (error) { |
| 144 | contMaterias.innerHTML = `<div class="alert alert-error">${escapeHtml(error.message)}</div>`; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /* ------------------------- PROFESOR ------------------------- */ |
| 149 | |
| 150 | async function renderFormularioProfesor() { |
| 151 | const cont = document.getElementById('ono-contenido'); |
| 152 | const nomina = sessionStorage.getItem('pending-numeroNomina') || ''; |
| 153 | cont.innerHTML = ` |
| 154 | <form id="ono-form-profesor"> |
| 155 | <div class="form-group"> |
| 156 | <label for="ono-nomina">Nómina / Matrícula Docente</label> |
| 157 | <input type="text" id="ono-nomina" class="form-control" required |
| 158 | value="${escapeHtml(nomina)}" placeholder="L01234567"> |
| 159 | </div> |
| 160 | <div class="ono-section"> |
| 161 | <h3>Asignación Académica y Especialidad (Ruta MAPS)</h3> |
| 162 | <div class="form-group"> |
| 163 | <label for="ono-area">Área / Carrera principal</label> |
| 164 | <select id="ono-area" class="form-control"> |
| 165 | <option value="">Selecciona el departamento...</option> |
| 166 | </select> |
| 167 | </div> |
| 168 | <div class="form-group"> |
| 169 | <label>Certificados a cargo</label> |
| 170 | <div class="ono-grid" id="ono-certificados"><div class="ono-espera">${spinnerHtml()}</div></div> |
| 171 | </div> |
| 172 | <div class="form-group"> |
| 173 | <label>Materias que imparte</label> |
| 174 | <div class="ono-grid" id="ono-materias-prof"><div class="ono-espera">${spinnerHtml()}</div></div> |
| 175 | </div> |
| 176 | <div class="form-group"> |
| 177 | <label for="ono-semestres">Semestres asignados</label> |
| 178 | <input type="text" id="ono-semestres" class="form-control" placeholder="ej. 4°, 5°, 6°"> |
| 179 | <div class="form-hint">Ciclos académicos donde tiene grupos activos.</div> |
| 180 | </div> |
| 181 | </div> |
| 182 | <div class="ono-section"> |
| 183 | <h3>Datos de Asesoría y Contacto</h3> |
| 184 | <div class="form-group"> |
| 185 | <label for="ono-horario">Horario de asesoría semanal</label> |
| 186 | <input type="text" id="ono-horario" class="form-control" placeholder="ej. Lunes y Miércoles 4:00 PM - 6:00 PM"> |
| 187 | </div> |
| 188 | <div class="form-group"> |
| 189 | <label for="ono-enlace">Enlace de sala virtual</label> |
| 190 | <input type="url" id="ono-enlace" class="form-control" placeholder="https://teams.microsoft.com/..."> |
| 191 | </div> |
| 192 | <div class="form-group"> |
| 193 | <label for="ono-semblanza">Semblanza / Especialidad técnica</label> |
| 194 | <textarea id="ono-semblanza" class="form-control" rows="3" |
| 195 | placeholder="ej. Especialista en arquitectura backend Java y modelado relacional en MySQL."></textarea> |
| 196 | </div> |
| 197 | </div> |
| 198 | <div class="form-group" style="margin-top:1.25rem"> |
| 199 | <button type="submit" class="btn btn-primary" style="width:100%">Guardar y empezar a navegar</button> |
| 200 | </div> |
| 201 | </form>`; |
| 202 | |
| 203 | const selArea = document.getElementById('ono-area'); |
| 204 | const contCert = document.getElementById('ono-certificados'); |
| 205 | const contMat = document.getElementById('ono-materias-prof'); |
| 206 | |
| 207 | const [carreras, certificados, materias] = await Promise.all([ |
| 208 | cargarOpciones('/carreras', 'Sin carreras'), |
| 209 | cargarOpciones('/certificados', 'Sin certificados'), |
| 210 | cargarOpciones('/materias', 'Sin materias') |
| 211 | ]); |
| 212 | |
| 213 | selArea.innerHTML = |
| 214 | '<option value="">Selecciona el departamento...</option>' + |
| 215 | carreras.map(c => `<option value="${escapeHtml(c.nombre)}">${escapeHtml(c.nombre)}</option>`).join(''); |
| 216 | |
| 217 | contCert.innerHTML = certificados.length |
| 218 | ? certificados.map(c => `<label> |
| 219 | <input type="checkbox" name="ono-certificado" value="${c.id}"> ${escapeHtml(c.nombre)} |
| 220 | </label>`).join('') |
| 221 | : `<div class="ono-espera" style="grid-column:1/-1">Aún no hay certificados registrados en el catálogo.</div>`; |
| 222 | |
| 223 | contMat.innerHTML = materias.length |
| 224 | ? materias.map(m => `<label> |
| 225 | <input type="checkbox" name="ono-materia-prof" value="${m.id}"> ${escapeHtml(m.nombre)} |
| 226 | </label>`).join('') |
| 227 | : `<div class="ono-espera" style="grid-column:1/-1">Aún no hay materias en el catálogo.</div>`; |
| 228 | |
| 229 | document.getElementById('ono-form-profesor').addEventListener('submit', async (ev) => { |
| 230 | ev.preventDefault(); |
| 231 | Utils.clearAlert('alert-container'); |
| 232 | const btn = ev.target.querySelector('button[type="submit"]'); |
| 233 | const certIds = [...document.querySelectorAll('input[name="ono-certificado"]:checked')].map(i => Number(i.value)); |
| 234 | const matIds = [...document.querySelectorAll('input[name="ono-materia-prof"]:checked')].map(i => Number(i.value)); |
| 235 | |
| 236 | try { |
| 237 | btn.disabled = true; |
| 238 | btn.textContent = 'Guardando...'; |
| 239 | await API.request('/auth/onboarding/profesor', { |
| 240 | method: 'POST', |
| 241 | body: JSON.stringify({ |
| 242 | numeroNomina: document.getElementById('ono-nomina').value.trim(), |
| 243 | areaEspecialidad: selArea.value, |
| 244 | semestresAsignados: document.getElementById('ono-semestres').value.trim(), |
| 245 | horarioAsesorias: document.getElementById('ono-horario').value.trim(), |
| 246 | enlaceSalaVirtual: document.getElementById('ono-enlace').value.trim(), |
| 247 | semblanza: document.getElementById('ono-semblanza').value.trim(), |
| 248 | certificados: certIds, |
| 249 | materias: matIds |
| 250 | }) |
| 251 | }); |
| 252 | sessionStorage.removeItem('pending-numeroNomina'); |
| 253 | window.location.href = Auth.resolvePath('pages/inicio.html'); |
| 254 | } catch (error) { |
| 255 | Utils.showAlert('alert-container', error.message || 'No se pudo completar el perfil.'); |
| 256 | btn.disabled = false; |
| 257 | btn.textContent = 'Guardar y empezar a navegar'; |
| 258 | } |
| 259 | }); |
| 260 | } |
| 261 | |
| 262 | /* ------------------------- UTILIDADES ------------------------- */ |
| 263 | |
| 264 | function escapeHtml(texto) { |
| 265 | return String(texto || '').replace(/[&<>"']/g, c => ({ |
| 266 | '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' |
| 267 | }[c])); |
| 268 | } |
| 269 | |
| 270 | function spinnerHtml() { |
| 271 | return '<div class="ono-espera">Cargando...</div>'; |
| 272 | } |
| 273 | })(); |