Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Renzo717/Aula-Virtual-Universidad-Radiolocucion/llms.txt

Use this file to discover all available pages before exploring further.

Grade bulletins (boletines) are professionally formatted A4 PDF report cards generated on demand by NuestraVoz. Each bulletin summarises a student’s final exam results across every subject in a given career, including the regular/libre status and any recuperatory scores. Admins and preceptors can download a bulletin for any enrolled student in seconds — no manual formatting required.

What a Bulletin Contains

The generated PDF is structured in two sections:
1

Student and Career Header

Displays the student’s full name, DNI, email address, the career name, and the academic year (e.g. “1º Año”).
2

Subject Grade Table

One row per active subject in the career, sorted alphabetically by subject name. Columns include: subject name, whether it is a promocional subject, libre flag, regular flag, final exam score, and recuperatory score.
A grade average (promedio) is computed from all non-null scores — preferring nota_recuperatorio over nota when both exist — and printed below the table. A generation timestamp and branding footer round out the document.

BoletinMateriaFila Type

Each row in the PDF table corresponds to one BoletinMateriaFila record:
interface BoletinMateriaFila {
  materia_id: string;
  materia_nombre: string;         // Subject name (column 1)
  promocional: boolean;           // "Promocional" / "No promocional" (column 2)
  libre: boolean;                 // Checkmark rendered when true (column 3)
  regular: boolean;               // Checkmark rendered when true (column 4)
  nota: number | null;            // Final exam score — "—" when null (column 5)
  nota_recuperatorio: number | null; // Recuperatory score — "—" when null (column 6)
}
nota_recuperatorio takes precedence over nota when calculating the bulletin’s overall average. If a student passed the recuperatory, that score is what counts towards their GPA.

Generating a Bulletin PDF

GET /api/boletines/pdf?estudiante_id=<uuid>&carrera_id=<uuid>
The endpoint streams back a binary PDF with the following HTTP headers:
Content-Type: application/pdf
Content-Disposition: attachment; filename="Apellido_Nombre-2025.pdf"
The filename is automatically sanitised — accented characters are normalised, spaces are replaced with underscores, and the current year is appended.
estudiante_id
string
required
UUID of the student whose bulletin should be generated.
carrera_id
string
required
UUID of the career. Only subjects belonging to this career and for which the student is actively enrolled are included.

What the API Does Internally

1

Fetch context

Loads the career record (nombre, anio), the student profile (nombre, apellido, dni, email), and all active subjects for that career — in a single parallel Promise.all.
2

Fetch grade records

Queries notas_finales for the student across all subject IDs, filtering by anio_academico (derived from carreras.anio).
3

Build row data

Maps each subject to a BoletinMateriaFila, defaulting libre, regular, nota, and nota_recuperatorio to safe empty values when no grade record exists.
4

Render PDF

Calls generateBoletinPdf (powered by PDFKit) to produce an A4 document with a header block, the grade table, a GPA line, and a footer timestamp.
5

Stream response

Sends the Buffer as application/pdf with a Content-Disposition: attachment header so the browser immediately triggers a file download.

Access Control

The boletines routes are protected by requireRole('admin', 'preceptor'):

Admin

Can generate a bulletin for any student in any active career.

Preceptor

Can generate a bulletin for any student in any active career.
Students do not have direct access to the /api/boletines/pdf endpoint. Their grades are visible only through the information surfaces provided by their teacher or institution.

Supporting Endpoints

MethodPathDescription
GET/api/boletines/carrerasList all active careers (id, nombre, codigo, anio)
GET/api/boletines/carrera/:carreraId/alumnosList enrolled students in a career
GET/api/boletines/pdf?estudiante_id=&carrera_id=Stream the bulletin PDF

Frontend: BoletinesPage

The BoletinesPage React component (apps/web/src/components/boletines/BoletinesPage.tsx) provides a two-step workflow for admins and preceptors:
1

Select a career

A dropdown populated from GET /api/boletines/carreras lists every active career with its year. Selecting a career immediately loads its enrolled students.
2

Download a student's bulletin

The student list renders a table with Apellido, Nombre, DNI, email, and an Imprimir PDF button per row. Clicking the button calls GET /api/boletines/pdf with estudiante_id and carrera_id as query parameters, receives the binary response, creates a temporary object URL, and triggers an <a download> click — all without navigating away from the page.
// Simplified download handler from BoletinesPage.tsx
async function downloadPdf(estudianteId: string) {
  const params = new URLSearchParams({
    estudiante_id: estudianteId,
    carrera_id: carreraId,
  });
  const res = await fetch(`/api/boletines/pdf?${params}`, { credentials: 'include' });
  const blob = await res.blob();
  const disposition = res.headers.get('Content-Disposition');
  const match = disposition?.match(/filename="([^"]+)"/);
  const filename = match?.[1] ?? 'boletin.pdf';

  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}
The filename suggested to the browser’s save dialog is derived server-side from the student’s name and the current year (e.g. Garcia_Maria-2025.pdf), ensuring downloaded files are easy to identify and organise.

Build docs developers (and LLMs) love