Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Stivenz3/Nexu/llms.txt

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

Adding a new lesson to Nexu is primarily a content task: you create three JSON files, run a seed script to push them to Firestore, and the existing frontend code picks them up automatically. No changes to React components or TypeScript types are needed for a standard lesson that uses the same block structure as Lesson 1.
The seed script overwrites all existing content for the target lesson in the shared Firebase project nexu-156ce. This affects every team member and the production app simultaneously. Always coordinate in the team chat before running a seed to avoid stepping on someone else’s test data.
1

Create the lesson JSON files

Create a new folder firestore-seed/lesson_XX/ (replace XX with the two-digit lesson number, e.g. 02) and add three files inside it.

1. lesson.json — lesson metadata

Based on the Lesson 1 structure:
{
  "lessonId": "lesson_02_control_temperaturas",
  "title": "Control de Temperaturas en la Manipulación de Alimentos",
  "order": 2,
  "normativeBase": "Resolución 2674 de 2013, Capítulo IV",
  "passingScore": 70,
  "estimatedMinutes": 60,
  "isActive": true,
  "totalBlocks": 8
}
Set isActive: false while the content is still being drafted so the lesson does not appear in /ruta for learners.

2. blocks.json — ordered block array

An array of block objects. Copy the structure from firestore-seed/lesson_01/blocks.json and update the IDs and content. The typical lesson uses 8 blocks: 1 video, 5 theory, 1 game, 1 exam.
[
  {
    "blockId": "b01_video",
    "type": "video",
    "order": 1,
    "title": "Introducción al control de temperaturas",
    "isRequired": true,
    "content": {
      "youtubeUrl": "https://www.youtube.com/embed/REPLACE_WITH_REAL_ID",
      "minWatchPercent": 0,
      "durationSeconds": 600
    }
  },
  {
    "blockId": "b02_modulo_a",
    "type": "theory",
    "order": 2,
    "title": "Zona de peligro de temperatura",
    "isRequired": true,
    "content": {
      "normativeRef": "Art. 22, Res. 2674 de 2013",
      "summary": "Los alimentos potencialmente peligrosos deben mantenerse fuera de la zona de peligro (5°C–60°C).",
      "keyPoints": [
        "Zona de peligro: 5°C a 60°C",
        "Tiempo máximo acumulado en zona de peligro: 4 horas"
      ],
      "technicalNote": null,
      "hasInterludedQuestion": true,
      "questionId": "q_p1i",
      "questionIds": null
    }
  }
]

3. questions.json — inline and exam questions

An array of all questions for the lesson. Inline questions reference their parent block via blockRef; exam questions reference the exam block (b08_exam).
[
  {
    "questionId": "q_p1i",
    "blockRef": "b02_modulo_a",
    "questionType": "intercalada",
    "order": 1,
    "text": "¿Cuál es la temperatura mínima de cocción para destruir la mayoría de los patógenos alimentarios?",
    "options": [
      { "label": "A", "text": "55°C", "isCorrect": false },
      { "label": "B", "text": "70°C", "isCorrect": true },
      { "label": "C", "text": "40°C", "isCorrect": false },
      { "label": "D", "text": "80°C", "isCorrect": false }
    ],
    "explanation": "La temperatura mínima de cocción segura para la mayoría de los patógenos es 70°C.",
    "normativeRef": "Art. 22, Res. 2674 de 2013",
    "difficulty": "basica"
  },
  {
    "questionId": "q_e01",
    "blockRef": "b08_exam",
    "questionType": "evaluacion_final",
    "order": 1,
    "text": "¿Qué rango de temperatura se denomina zona de peligro para los alimentos?",
    "options": [
      { "label": "A", "text": "0°C a 40°C", "isCorrect": false },
      { "label": "B", "text": "5°C a 60°C", "isCorrect": true },
      { "label": "C", "text": "10°C a 50°C", "isCorrect": false },
      { "label": "D", "text": "15°C a 70°C", "isCorrect": false }
    ],
    "explanation": "La zona de peligro de temperatura es el rango entre 5°C y 60°C donde los microorganismos se multiplican más rápidamente.",
    "normativeRef": "Res. 2674 de 2013",
    "difficulty": "basica"
  }
]
All question and block text must come from the official PDF or technical document for the lesson — never invent normative content. The blockRef value on each question must exactly match the blockId of its parent block.

ID naming conventions

TypeConventionExample
Lessonlesson_NN_sluglesson_02_control_temperaturas
BlocksbNN_slugb01_video, b02_modulo_a
Inline questionsq_pNiq_p1i, q_p2i
Exam questionsq_eNNq_e01, q_e15
2

Run the seed script

Coordinate with the team before running. The seed deletes and recreates the lesson in the shared Firestore project nexu-156ce.
# Authenticate with Firebase CLI (first time per machine)
npx firebase login
npx firebase use nexu-156ce

# Set up Application Default Credentials for the seed script
gcloud auth application-default login

# Run the seed (replace XX with the lesson number, e.g. 02)
npm run seed:lesson2
Expected output:
Eliminando datos previos de lesson_02_control_temperaturas...
Insertando lección, bloques y preguntas...
Listo (lesson_02): 1 lección, 8 bloques, 23 preguntas.
lessonId: lesson_02_control_temperaturas
If isActive is false in lesson.json, the script also prints:
Aviso: isActive es false — no aparecerá en /ruta hasta activarla.
The seed script reads credentials via applicationDefault() (from gcloud auth application-default login). If you see Could not load the default credentials, re-run gcloud auth application-default login first. For a full explanation of the three authentication layers, see the DESARROLLO_LECCIONES.md §7 in the repo.What the seed does NOT touch:
  • User accounts and lessonProgress documents
  • Certificates already issued
  • Firestore security rules
  • Vercel hosting / frontend build
3

Frontend verification (no code changes needed for standard lessons)

The existing frontend components are already wired to handle any lesson that follows the Lesson 1 structure.
ComponentBehaviour
LearningPathPageAutomatically shows lessons with isActive: true, ordered by order. Lesson N+1 is locked until lesson N has status: 'passed'.
LessonFlowPageRenders each block using the registered block-type components (VideoBlockView, TheoryBlockView, GameBlockView). No changes needed for standard block types.
LessonExamPageCalls fetchExamQuestions, scores the exam, saves results via saveExamResult, and calls tryIssueCourseCertificateAfterLesson() on pass. No changes needed.
The only time you need to touch src/types/lesson.ts is if the new lesson introduces new fields in a block’s content object that don’t exist in the current type definitions.
4

Deploy Firestore rules if needed

If you changed firestore.rules or firestore.indexes.json as part of this lesson (e.g. to add a new index), deploy them before testing:
npm run firebase:deploy:rules
Standard lessons using the existing block types do not require rule changes.
5

Test the full flow

Log in as a new user (or delete the lessonProgress document for an existing user) and run through the entire lesson:
  1. Navigate to /ruta — the lesson should appear with the correct title and order
  2. Click “Empezar” — the first block (b01_video) should load
  3. Complete the video block — the next block should unlock
  4. Work through all theory blocks, answering each inline question correctly
  5. Complete the game block
  6. Verify the exam block’s “Iniciar evaluación final” button is enabled
  7. Complete the exam with a score ≥ 70%
  8. Verify the certificate appears at /certificado
  9. Return to /ruta — the lesson should show as completed and the next lesson (if any) should unlock

Checklist

Use this list as a PR checklist when delivering a new lesson:
  • lesson.json, blocks.json, questions.json created in firestore-seed/lesson_XX/ and validated against the official PDF
  • lesson.isActive: true and order value is correct relative to existing lessons
  • Seed run on nexu-156ce (coordinated with team — see Step 2)
  • Full flow tested end-to-end with a new user: video → theory → game → exam → certificate
  • Firestore rules deployed if firestore.rules or indexes were changed
  • pnpm-lock.yaml updated (pnpm install --lockfile-only) if any new npm dependencies were added to package.json

Build docs developers (and LLMs) love