Este documento describe la estructura de datos del archivo exams.json ubicado en src/lib/planner/exams.json y las interfaces TypeScript relacionadas.
src/lib/planner/exams.jsonsrc/lib/planner/index.ts - Contiene las interfaces TypeScript y funciones de utilidadsrc/content/docs/exams-data-structure.md - Esta documentaciónEl archivo exams.json contiene información sobre los exámenes del curso, incluyendo fechas, materias, temas, ubicaciones y enlaces relacionados. Este archivo es utilizado por el componente Planner para mostrar y filtrar exámenes, calcular estadísticas y organizar cronológicamente las evaluaciones.
El archivo exams.json es un objeto JSON con la siguiente estructura:
{ "version": 2, "exams": [ // Array de objetos Exam ]}| Propiedad | Tipo | Descripción |
|---|---|---|
version | number | Versión del formato de datos (actual: 2) |
exams | Exam[] | Array de objetos de examen |
export interface Exam { id: string; title: string; subject: string; date: string; time: string; location?: string; topics: string[]; notes?: string; formUrl?: string; links?: ExamLink[];}| Propiedad | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id | string | Sí | Identificador único del examen (puede ser UUID o string simple) |
title | string | Sí | Título descriptivo del examen |
subject | string | Sí | Materia o asignatura del examen |
date | string | Sí | Fecha del examen en formato YYYY-MM-DD |
time | string | Sí | Hora del examen en formato HH:MM |
location | string | No | Ubicación del examen (aula/laboratorio) |
topics | string[] | Sí | Lista de temas cubiertos en el examen |
notes | string | No | Notas adicionales o observaciones |
formUrl | string | No | URL del formulario para inscripciones/registros |
links | ExamLink[] | No | Array de enlaces relacionados al examen |
export interface ExamLink { label: string; url: string;}| Propiedad | Tipo | Obligatorio | Descripción |
|---|---|---|---|
label | string | Sí | Etiqueta descriptiva del enlace |
url | string | Sí | URL del enlace |
{ "version": 2, "exams": [ { "id": "80f35105-c3b7-43ee-8669-3f5cda551735", "title": "Bitácora e informe", "subject": "Proyecto", "date": "2026-09-18", "time": "23:59", "topics": [ "Trabajo en equipo" ], "notes": "Entrega final del proyecto semestral", "links": [ { "label": "Guía de proyecto", "url": "https://docs.google.com/document/d/..." } ] }, { "id": "1", "title": "Primer Parcial", "subject": "Examen", "date": "2026-09-28", "time": "13:00", "topics": [ "Cantidades físicas", "MRU", "MRUA", "Movimiento parabólico", "Leyes de Newton" ], "notes": "", "links": [] }, { "id": "826a86d2-b926-461c-ada5-7b4b253a8fe9", "title": "Primer Parcial Extraordinario", "subject": "Examen Extraordinario", "date": "2026-10-05", "time": "13:00", "location": "Aula 301", "topics": [ "Cantidades físicas", "MRU", "MRUA", "Movimiento parabólico", "Leyes de Newton" ], "notes": "Esta prueba la pueden realizar aquellas personas que hayan justificado su ausencia a la prueba ordinaria", "formUrl": "https://forms.tec.ac.cr/inscripcion-extraordinario", "links": [] } ]}YYYY-MM-DD
"2026-09-28"HH:MM
"13:00"subject)El sistema no tiene valores predefinidos para subject, pero los ejemplos más comunes son:
| Materia | Descripción |
|---|---|
Examen | Exámenes ordinarios del curso |
Examen Extraordinario | Exámenes de recuperación |
Examen de Reposición | Exámenes finales de reposición |
Proyecto | Entregas de proyectos o trabajos |
src/lib/planner/index.tsRetorna todos los exámenes ordenados cronológicamente por fecha.
export const getExams = (): Exam[] => { const exams = (plannerData as { exams: Exam[] }).exams; return [...exams].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() );};Retorna la fecha actual en formato ISO YYYY-MM-DD.
export const getTodayISO = (): string => { const d = new Date(); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`;};Calcula estadísticas de los exámenes, excluyendo exámenes extraordinarios.
export const getStats = (exams: Exam[]) => { const todayStr = getTodayISO(); const isNotExtraordinario = (e: Exam) => !e.subject.includes('Extraordinario');
return { total: exams.filter(isNotExtraordinario).length, upcoming: exams.filter(e => e.date > todayStr && isNotExtraordinario(e)).length, today: exams.filter(e => e.date === todayStr && isNotExtraordinario(e)).length };};Filtra exámenes según el criterio especificado.
export const filterExams = (exams: Exam[], filter: 'all' | 'upcoming' | 'today'): Exam[] => { const today = new Date().toLocaleDateString('en-CA');
return exams.filter(exam => { if (filter === 'all') return true; if (filter === 'today') return exam.date === today; if (filter === 'upcoming') return exam.date > today; return true; });};El componente Planner utiliza estas funciones para:
getExams() ordena por fechagetStats() muestra totales y próximos exámenesfilterExams() permite ver todos, de hoy, o próximosPara asegurar la integridad de los datos, el archivo debe cumplir con las siguientes reglas:
id únicoYYYY-MM-DD y ser fechas válidasHH:MM (00:00 a 23:59)topics debe ser un array (puede estar vacío)formUrl y links[].url deben ser URLs válidasPara agregar un nuevo examen al archivo:
src/lib/planner/exams.jsonexamsid sea únicosubject apropiadaEjemplo de examen con todas las propiedades:
{ "id": "4", "title": "Cuarto Parcial", "subject": "Examen", "date": "2026-12-10", "time": "08:30", "location": "Aula 201", "topics": [ "Ondas", "Física moderna" ], "notes": "Examen final de curso", "formUrl": "https://forms.tec.ac.cr/inscripcion", "links": [ { "label": "Guía de estudio", "url": "https://docs.google.com/document/d/..." }, { "label": "Horario de consulta", "url": "https://calendar.google.com/..." } ]}Para eliminar un examen:
src/lib/planner/exams.jsonidexamsImportante: Verifica que el examen eliminado no sea referenciado en otros componentes del proyecto.
import { getExams } from '../lib/planner';
const exams = getExams();// Retorna array de exámenes ordenados por fecha ascendenteimport { filterExams, getExams } from '../lib/planner';
const exams = getExams();
// Solo exámenes de hoyconst todayExams = filterExams(exams, 'today');
// Solo exámenes futurosconst upcomingExams = filterExams(exams, 'upcoming');
// Todos los exámenesconst allExams = filterExams(exams, 'all');import { getExams, getStats } from '../lib/planner';
const exams = getExams();const stats = getStats(exams);
console.log(`Total de exámenes: ${stats.total}`);console.log(`Próximos exámenes: ${stats.upcoming}`);console.log(`Exámenes de hoy: ${stats.today}`);import { getExams } from '../lib/planner';
const exams = getExams();
// Solo exámenes ordinariosconst ordinaryExams = exams.filter(exam => exam.subject === 'Examen');
// Solo exámenes extraordinariosconst extraordinaryExams = exams.filter(exam => exam.subject.includes('Extraordinario'));import { getExams } from '../lib/planner';
const exams = getExams();
const examsWithNewton = exams.filter(exam => exam.topics.some(topic => topic.toLowerCase().includes('newton') ));getExams()subject para facilitar filtradoformUrl y links[].urlPara validar la estructura del archivo, puedes usar el siguiente esquema JSON:
{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["version", "exams"], "properties": { "version": { "type": "number", "enum": [2] }, "exams": { "type": "array", "items": { "type": "object", "required": ["id", "title", "subject", "date", "time", "topics"], "properties": { "id": { "type": "string", "minLength": 1 }, "title": { "type": "string", "minLength": 1 }, "subject": { "type": "string", "minLength": 1 }, "date": { "type": "string", "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" }, "location": { "type": "string" }, "topics": { "type": "array", "items": { "type": "string" } }, "notes": { "type": "string" }, "formUrl": { "type": "string", "format": "uri" }, "links": { "type": "array", "items": { "type": "object", "required": ["label", "url"], "properties": { "label": { "type": "string", "minLength": 1 }, "url": { "type": "string", "format": "uri" } } } } } } } }}