SIENEP · del navegador a la base
Consulta propia del estudiante
Una estudiante entra a SIENEP para ver cuándo tiene un encuentro de seguimiento. Abre Mis instancias, ubica el encuentro en Mi calendario y revisa un recordatorio que le compartió el equipo. Seguimos ese recorrido por React, Spring y PostgreSQL para ver qué recibe su cuenta.
Requisitos funcionales relacionados
RF12Visualización de InstanciasRF26Visualización de Recordatorios
Mis instancias, Mi calendario y Mis recordatorios son vistas de consulta propia. Este recorrido amplía la visualización de instancias y recordatorios para la cuenta del estudiante.
Fuente oficial Requerimientos de Programación de UTEC, 2026 · pp. 4–5
Los IDs, credenciales y cuentas de los ejemplos son marcadores. El HTML funciona sin internet y permite copiar los fragmentos o imprimir todos los recorridos.
Cómo se habilita la vista propia
1. Ingresar con la cuenta del estudiante
La estudiante A inicia sesión con su cuenta. Puede hacerlo con el ingreso local o con Auth0 si su cuenta está habilitada. En ambos casos, cuando el backend acepta la credencial, Spring identifica la cuenta de SIENEP mediante el ID del usuario autenticado.
POST /api/estudiantes/mis-instancias
Authorization: Bearer CREDENCIAL_DE_EJEMPLO
Sin cuerpo de solicitud.instanciasApi.ts · líneas 352–363
export async function listarMisInstancias(
opciones?: OpcionesApi,
): Promise<InstanciaLecturaResponse[]> {
const respuesta = await clienteHttp.post<unknown>(
'/api/estudiantes/mis-instancias',
undefined,
{
senal: opciones?.senal,
},
)
return validarListadoInstancias(respuesta)
}En esta API, el POST es una consulta. Devuelve las instancias de la cuenta autenticada. El cliente HTTP adjunta la credencial y el servidor toma de allí la identidad que usará para buscar.
2. Confirmar el perfil con la respuesta del servidor
consultaInstanciasPropias.tsx · líneas 107–113
const solicitud = listarMisInstancias({ senal: abortador.signal })
.then((datos) => {
if (!vigente()) return
setFilas(datos)
setError(null)
setEstado('confirmado')
})Un 200 con una lista válida confirma el perfil para el Inicio y la navegación. 200 [] también lo confirma y muestra un estado vacío. Si la consulta recibe 403, el acceso queda sin confirmar. Un 401 pasa por el manejo de sesión. Un fallo de red, 5xx o una respuesta inválida se muestra como error de consulta.
Inicio, Mis instancias y Mi calendario comparten el resultado en React. El proveedor lo asocia con el ID de la cuenta y el ciclo de sesión, que cambia al salir o entrar con otra identidad.
3. Mostrar los accesos propios en Inicio y en el menú
navegacion.ts · líneas 116–119
return elementos.filter((elemento) => {
if (!elemento.disponible) return false
if (elemento.requierePerfilEstudiante) return perfilEstudianteConfirmado
if (elemento.permiso) return tiene(elemento.permiso)Con el perfil confirmado, el Inicio ofrece Mis instancias, Mi calendario y Mis recordatorios. Los enlaces de gestión siguen dependiendo de sus permisos. Una cuenta con más de un perfil puede tener accesos propios y, además, los accesos de gestión para los que está autorizada.
El menú decide qué accesos presentar. La API aplica las reglas de autorización aunque se escriba una URL directamente.
Consultar las instancias de la cuenta
1. Consultar por la identidad de la sesión
EstudianteController.java · líneas 38–42
@PostMapping("/mis-instancias")
public ResponseEntity<List<InstanciaResponseDto>> misInstancias(Authentication auth) {
Integer idEstudiante = (Integer) auth.getPrincipal();
return ResponseEntity.ok(instanciaService.listarPropiasDto(idEstudiante));
}InstanciaService.java · líneas 429–435
public List<InstanciaResponseDto> listarPropiasDto(Integer idEstudiante) {
if (!estudianteRepository.existsById(idEstudiante)) {
throw new AccessDeniedException("Solo los estudiantes pueden acceder a este recurso");
}
return instanciaRepository.findByEstudianteConRelaciones(idEstudiante)
.stream().map(InstanciaResponseDto::from).toList();
}El controlador extrae el ID del principal de Spring. El servicio comprueba que exista el perfil de estudiante y el repositorio busca por e.idUsuario = :idEstudiante. La búsqueda sale de la cuenta que autenticó el servidor. La consulta del repositorio incluye las instancias propias activas e inactivas.
2. Abrir la tarjeta a partir del listado autorizado
PaginaMisInstancias.tsx · líneas 36–38
const instanciaSeleccionada = confirmado
? filas.find((instancia) => instancia.idInstancia === idSeleccionado) ?? null
: nullCada tarjeta proviene de la lista recibida. Al elegir una, React busca ese ID en las filas confirmadas y monta PanelDetallePropio. Ese panel muestra título, código, fecha y hora, estado, lugar si existe y enlace de reunión si cumple las condiciones.
PanelDetallePropio.tsx · líneas 48–50
const enlaceCalendar = clasificarEnlaceCalendar(instancia.urlMeet)
const meet = instancia.urlMeet?.trim() ?? ''
const tieneMeet = enlaceCalendar === null && meet !== '' && esUrlHttpsValida(meet)El enlace se habilita para un valor HTTPS válido que el clasificador no identifica como enlace de Calendar. La ficha propia se presenta en modo de lectura.
3. Distinguir lo que llega en el JSON de lo que se muestra
Esta consulta reutiliza InstanciaResponseDto. El JSON puede incluir descripción e identificadores de funcionario, estudiante y categoría. El panel propio muestra un conjunto más chico de campos. La reducción ocurre en la presentación, así que esos otros datos siguen formando parte de la respuesta de la API.
La consulta de recordatorios tiene su propio DTO reducido desde el servidor. En el recorrido siguiente de recordatorios se ve dónde queda ese límite.
Ubicar las instancias en el mes
1. Usar las mismas instancias propias confirmadas
/mis-instancias/calendario?mes=2026-11PaginaCalendarioPropio.tsx · líneas 39–40
const activas = confirmado ? filas.filter((instancia) => instancia.estActivo) : []
const instanciaSeleccionada = activas.find((instancia) => instancia.idInstancia === idSeleccionado) ?? nullMi calendario usa el mismo proveedor que el listado y conserva únicamente las instancias activas. Es una cuadrícula mensual dentro de SIENEP construida con esas filas.
2. Cambiar de mes y elegir una fecha
PaginaCalendarioPropio.tsx · líneas 42–50
const cambiarMes = (nuevoMes: { anio: number; mes: number }) => {
setIdSeleccionado(null)
const mes = String(nuevoMes.mes).padStart(2, '0')
setSearchParams((prev) => {
const siguientes = new URLSearchParams(prev)
siguientes.set('mes', `${nuevoMes.anio}-${mes}`)
return siguientes
})
}El mes elegido queda en la URL como ?mes=AAAA-MM. Cuando falta un mes válido, la página usa el mes actual de Montevideo. El componente mensual agrupa las instancias por día en esa zona horaria. Al elegir una instancia abre el mismo panel de lectura.
Si una instancia de la estudiante A se inactiva, puede seguir apareciendo como inactiva en su listado. Cuando se vuelve a consultar, queda fuera del calendario porque ese componente filtra las activas.
Leer lo que el equipo compartió
1. El equipo comparte un recordatorio sobre una instancia
DialogoFormularioRecordatorio.tsx · líneas 340–343
const payloadAlta = visible && !sinEstudiante
? { ...resultadoAlta.payload, visibleParaEstudiante: true }
: resultadoAlta.payload
const creado = await crearRecordatorio(payloadAlta, { senal: controller.signal })El profesional marca Visible para el estudiante en el alta o la edición. El backend requiere un recordatorio activo con regla completa y una instancia activa vinculada al estudiante activo correspondiente. El alta queda privada por defecto. Si se cambia la instancia, el nuevo contexto requiere confirmar otra vez la visibilidad.
2. Pedir la lista propia con el token de la cuenta
GET /api/estudiantes/mis-recordatorios
Authorization: Bearer CREDENCIAL_DE_EJEMPLOMisRecordatoriosController.java · líneas 49–53
public ResponseEntity<List<MiRecordatorioDto>> listar(HttpServletRequest request, Authentication auth) {
rechazarParametros(request);
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.body(misRecordatoriosService.listar(principal(auth)));
}La vista usa una ruta de lectura propia. La API exige un perfil de estudiante activo y toma la identidad de la sesión. El listado rechaza parámetros de consulta. El permiso recordatorios.leer corresponde a la gestión profesional, que usa sus propias rutas.
RecordatorioRepository.java · líneas 66–71
WHERE r.visibleParaEstudiante = true
AND r.estActivo = true
AND i.estActivo = true
AND e.idUsuario = :idEstudiante
AND ie.idUsuario = :idEstudiante
AND e.estActivo = trueEl repositorio exige publicación, estado activo y coincidencia entre el estudiante autenticado, el recordatorio y la instancia. El servicio también excluye reglas incompletas o inválidas.
3. Mostrar solo los campos del contrato propio
MisRecordatoriosService.java · líneas 97–102
private MiRecordatorioDto respuesta(Recordatorio r, Set<Integer> cumplidas) {
RecordatorioService.EstadoVigente estado = recordatorioService.estadoVigente(r, cumplidas);
return new MiRecordatorioDto(r.getIdRecordatorio(), r.getCodigoRecordatorio(), r.getTitulo(),
r.getInstancia().getIdInstancia(), local(r.getFecHora()), r.getRecurrencia(), r.getIntervaloRecurrencia(),
estado.estado(), local(estado.proximaOcurrencia()), estado.numeroProximaOcurrencia());
}El servidor calcula el estado y la próxima fecha con los cumplimientos de la revisión vigente. El DTO entrega código, título, fecha inicial, repetición, estado, próxima ocurrencia y el ID de la instancia para el enlace. La descripción interna, los datos de personas, la categoría y los datos de Google Calendar quedan fuera del contrato.
El frontend valida el DTO y permite ver todos, pendientes o cumplidos. El panel ofrece la lectura del detalle y el enlace hacia la instancia propia.
4. Volver a consultar al abrir el detalle
/mis-recordatorios?detalle=58
GET /api/estudiantes/mis-recordatorios/58MisRecordatoriosService.java · líneas 53–55
Recordatorio r = recordatorioRepository.findPublicadoParaEstudiante(idRecordatorio, idEstudiante)
.filter(this::reglaCompleta)
.orElseThrow(RecordatorioPropioNoDisponibleException::new);Abrir una tarjeta hace otro GET. El servicio vuelve a comprobar que ese ID siga publicado para la misma cuenta. El detalle ajeno, inexistente, despublicado o dado de baja recibe el mismo 404 genérico. React muestra que el recordatorio ya no está disponible y retira esa fila si estaba en pantalla.
PanelDetalleMiRecordatorio.tsx · líneas 107–111
to="/mis-instancias"
state={{ seleccionarInstancia: recordatorio.idInstancia }}
className={estilosDetalle.enlaceMeet}
>
Ver la instancia en Mis instanciasEl enlace lleva a Mis instancias con una sugerencia de selección. El panel solo se abre si el ID figura en las filas propias confirmadas.
Revalidar los datos al volver o cambiar de cuenta
1. Ver los cambios de publicación al volver a la pestaña
PaginaMisRecordatorios.tsx · líneas 145–152
const alVolver = () => {
if (document.visibilityState !== 'visible') return
consultar()
// Con el detalle abierto también se revalida el objeto mostrado.
if (parametrosRef.current.has('detalle')) setRevisionDetalle((n) => n + 1)
}
window.addEventListener('focus', alVolver)
document.addEventListener('visibilitychange', alVolver)Al recuperar el foco o volver a una pestaña visible, la página vuelve a consultar el listado y revalida el detalle abierto. Si el profesional retiró la publicación o dio de baja el recordatorio, la siguiente respuesta refleja ese cambio. La API de recordatorios propios marca sus respuestas con Cache-Control: no-store.
2. Separar lo que está en vuelo al cambiar de cuenta
consultaInstanciasPropias.tsx · líneas 96–101
const vigente = () =>
montado.current &&
!abortador.signal.aborted &&
generacion.current === generacionSolicitud &&
obtenerCicloSesion() === ciclo &&
usuarioVigente(userId)Si la estudiante A sale y entra la estudiante B, el proveedor se monta con otro ID y ciclo de sesión. Al desmontarse cancela la solicitud anterior. La comprobación de vigencia descarta también una respuesta tardía de A. El listado y el detalle de recordatorios aplican controles equivalentes de cuenta, ciclo y solicitud.
La autorización por estudiante se decide en el servidor. El control de solicitudes tardías protege el estado que muestra el navegador al cambiar de sesión.
3. Seguir el mismo ejemplo con dos cuentas
| Situación | Estudiante A | Estudiante B |
|---|---|---|
| La instancia 54 está asociada a A y activa | La encuentra en Mis instancias y en el mes correspondiente. | Su lista propia contiene únicamente sus instancias. |
| El recordatorio 58 de la instancia 54 sigue privado | Su detalle propio devuelve 404. | Su detalle propio devuelve 404. |
| El equipo publica el recordatorio 58 | Aparece en la lista y puede abrir su detalle. | La lista propia lo excluye y el detalle devuelve 404. |
| El equipo retira la publicación | El registro desaparece al revalidar. La instancia conserva su estado. | El detalle mantiene la respuesta 404. |
El informe QA04 del paquete conserva una prueba de API con dos cuentas, incluida la respuesta idéntica para un ID ajeno y uno inexistente. El cuadro ilustra ese aislamiento con un caso de estudio y añade el recorrido de pantalla.
Para contarlo en la defensa
“Cuando un estudiante entra, la API usa la identidad de su sesión para buscar sus instancias. React muestra esas filas en un listado y coloca las activas en el calendario mensual. Para recordatorios, el servidor exige además que el equipo haya publicado el registro y que la instancia pertenezca a ese estudiante. Entrega un DTO de lectura y vuelve a comprobar el acceso al abrir cada detalle.”
Archivos revisados
Los fragmentos y sus números de línea corresponden a las copias del paquete del 06/11/2026. El informe QA04 citado pertenece a ese paquete.
frontend/src/modulos/instancias/api/instanciasApi.tsfrontend/src/modulos/instanciasPropias/consultaInstanciasPropias.tsxfrontend/src/aplicacion/disposiciones/navegacion.tsbackend/src/main/java/edu/utec/sienep/controller/EstudianteController.javabackend/src/main/java/edu/utec/sienep/service/InstanciaService.javafrontend/src/modulos/instanciasPropias/paginas/PaginaMisInstancias.tsxfrontend/src/modulos/instanciasPropias/componentes/PanelDetallePropio.tsxfrontend/src/modulos/instanciasPropias/paginas/PaginaCalendarioPropio.tsxfrontend/src/modulos/recordatorios/componentes/DialogoFormularioRecordatorio.tsxbackend/src/main/java/edu/utec/sienep/controller/MisRecordatoriosController.javabackend/src/main/java/edu/utec/sienep/repository/RecordatorioRepository.javabackend/src/main/java/edu/utec/sienep/service/MisRecordatoriosService.javafrontend/src/modulos/misRecordatorios/componentes/PanelDetalleMiRecordatorio.tsxfrontend/src/modulos/misRecordatorios/paginas/PaginaMisRecordatorios.tsxfrontend/src/aplicacion/rutas.tsxfrontend/src/modulos/inicio/paginas/PaginaInicio.tsxfrontend/src/modulos/instanciasPropias/RutaInstanciasPropias.tsxfrontend/src/modulos/instancias/componentes/CalendarioMensual.tsxfrontend/src/modulos/misRecordatorios/api/misRecordatoriosApi.tsbackend/src/main/java/edu/utec/sienep/dto/InstanciaResponseDto.javabackend/src/main/java/edu/utec/sienep/repository/InstanciaRepository.javabackend/src/main/java/edu/utec/sienep/dto/MiRecordatorioDto.javabackend/src/main/java/edu/utec/sienep/config/MisRecordatoriosNoStoreFilter.javabackend/src/main/java/edu/utec/sienep/service/RecordatorioService.javaINFORME-QA04.md