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.

Lectura del código · Paquete del 06/11/2026 · Frontend React + API Spring + PostgreSQL

Requisitos funcionales relacionados

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

Ingreso → consulta de instancias propias → perfil confirmado → accesos de estudiante

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.

HTTP · esquema
POST /api/estudiantes/mis-instancias
Authorization: Bearer CREDENCIAL_DE_EJEMPLO

Sin cuerpo de solicitud.

instanciasApi.ts · líneas 352–363

TypeScript
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

TypeScript
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

TypeScript
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

Credencial → ID de la sesión → búsqueda por estudiante → listado → detalle de lectura

1. Consultar por la identidad de la sesión

EstudianteController.java · líneas 38–42

Java
@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

Java
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

TypeScript
const instanciaSeleccionada = confirmado
    ? filas.find((instancia) => instancia.idInstancia === idSeleccionado) ?? null
    : null

Cada 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

TypeScript
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

Filas propias → instancias activas → mes y día en Montevideo → detalle

1. Usar las mismas instancias propias confirmadas

Ruta · ejemplo
/mis-instancias/calendario?mes=2026-11

PaginaCalendarioPropio.tsx · líneas 39–40

TypeScript
const activas = confirmado ? filas.filter((instancia) => instancia.estActivo) : []
  const instanciaSeleccionada = activas.find((instancia) => instancia.idInstancia === idSeleccionado) ?? null

Mi 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

TypeScript
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ó

Publicación → lista propia → detalle autorizado → enlace a la instancia

1. El equipo comparte un recordatorio sobre una instancia

DialogoFormularioRecordatorio.tsx · líneas 340–343

TypeScript
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

HTTP · esquema
GET /api/estudiantes/mis-recordatorios
Authorization: Bearer CREDENCIAL_DE_EJEMPLO

MisRecordatoriosController.java · líneas 49–53

Java
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

Java
WHERE r.visibleParaEstudiante = true
          AND r.estActivo = true
          AND i.estActivo = true
          AND e.idUsuario = :idEstudiante
          AND ie.idUsuario = :idEstudiante
          AND e.estActivo = true

El 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

Java
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

Ruta y HTTP · ID ficticio
/mis-recordatorios?detalle=58
GET /api/estudiantes/mis-recordatorios/58

MisRecordatoriosService.java · líneas 53–55

Java
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

TSX
to="/mis-instancias"
                  state={{ seleccionarInstancia: recordatorio.idInstancia }}
                  className={estilosDetalle.enlaceMeet}
                >
                  Ver la instancia en Mis instancias

El 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

Volver al foco → consulta vigente · cambiar de cuenta → descartar la respuesta anterior

1. Ver los cambios de publicación al volver a la pestaña

PaginaMisRecordatorios.tsx · líneas 145–152

TypeScript
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

TypeScript
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

Resultados esperados en el ejemplo ficticio
SituaciónEstudiante AEstudiante B
La instancia 54 está asociada a A y activaLa encuentra en Mis instancias y en el mes correspondiente.Su lista propia contiene únicamente sus instancias.
El recordatorio 58 de la instancia 54 sigue privadoSu detalle propio devuelve 404.Su detalle propio devuelve 404.
El equipo publica el recordatorio 58Aparece en la lista y puede abrir su detalle.La lista propia lo excluye y el detalle devuelve 404.
El equipo retira la publicaciónEl 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.