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.

Every lesson in Nexu is composed of ordered blocks. Each block document lives in the lessons/{lessonId}/blocks/ subcollection and carries a type field that determines both its Firestore content schema and which React component renders it in LessonFlowPage. The four supported block types are video, theory, game, and exam. All blocks share a common wrapper shape defined by LessonBlock:
interface LessonBlock {
  blockId: string
  type: BlockType                  // 'video' | 'theory' | 'game' | 'exam'
  order: number
  title: string
  isRequired: boolean
  imageUrl?: string
  imageAlt?: string
  content: VideoBlockContent | TheoryBlockContent | GameBlockContent | ExamBlockContent
}

video block — VideoBlockContent

The video block embeds a YouTube video and optionally enforces a minimum watch percentage before the learner can proceed.

Firestore content schema

interface VideoBlockContent {
  youtubeUrl: string        // YouTube embed URL, e.g. "https://www.youtube.com/embed/ScMzIvxBSi4"
  minWatchPercent: number   // Minimum % to watch before completing (0 = no minimum enforced)
  durationSeconds: number   // Total video duration in seconds
}

Lesson 1 example (b01_video)

{
  "blockId": "b01_video",
  "type": "video",
  "order": 1,
  "title": "Introducción y contexto normativo",
  "isRequired": true,
  "content": {
    "youtubeUrl": "https://www.youtube.com/embed/ScMzIvxBSi4",
    "minWatchPercent": 0,
    "durationSeconds": 600
  }
}

UI component

VideoBlockView renders the YouTube embed. When the video ends (or when minWatchPercent is reached), it signals completion to LessonFlowPage via an onComplete callback.

Progress tracked

interface VideoBlockProgress {
  completed: boolean
  videoWatched: boolean
  watchedPct: number
}
In the current Lesson 1 seed, minWatchPercent is set to 0, so the video block completes as soon as the learner clicks through — no minimum watch time is enforced. Set this to a value like 80 to require 80% of the video to be watched before proceeding.

theory block — TheoryBlockContent

Theory blocks deliver normative content — a summary, key points, an optional technical note, and one or more inline questions the learner must answer correctly before advancing.

Firestore content schema

interface TheoryBlockContent {
  normativeRef: string               // Regulation reference, e.g. "Art. 11, Res. 2674 de 2013"
  summary: string                    // Block summary text
  keyPoints: string[]                // Key learning points (bullet list)
  technicalNote: string | null       // Optional in-depth technical note
  hasInterludedQuestion: boolean     // Whether inline questions are present
  questionId: string | null          // Single inline question ID (e.g. "q_p1i")
  questionIds: string[] | null       // Multiple inline question IDs (e.g. ["q_p3i","q_p4i","q_p5i"])
}
Use questionId for a single question and questionIds for multiple questions. The service helper getTheoryQuestionIds(content) normalises both cases into a single string[], so the rest of the app never needs to check which field is populated.

Lesson 1 example (b02_modulo_a — single question)

{
  "blockId": "b02_modulo_a",
  "type": "theory",
  "order": 2,
  "title": "Estado de salud del manipulador",
  "isRequired": true,
  "content": {
    "normativeRef": "Art. 11, Res. 2674 de 2013",
    "summary": "Toda persona en contacto con alimentos debe mantener estado de salud apropiado...",
    "keyPoints": [
      "Examen médico de ingreso y periódico es obligatorio",
      "Incluye coprocultivo, frotis de garganta y valoración dermatológica"
    ],
    "technicalNote": "La norma no limita la restricción a personas con síntomas activos...",
    "hasInterludedQuestion": true,
    "questionId": "q_p1i",
    "questionIds": null
  }
}

Lesson 1 example (b04_modulo_c — multiple questions)

{
  "blockId": "b04_modulo_c",
  "type": "theory",
  "order": 4,
  "title": "Lavado de manos: fundamentos y técnica OMS",
  "isRequired": true,
  "content": {
    "normativeRef": "Art. 13, Res. 2674 de 2013",
    "summary": "El lavado de manos elimina la microbiota transitoria...",
    "keyPoints": [
      "Técnica OMS: 11 pasos, 40 a 60 segundos con agua y jabón"
    ],
    "technicalNote": "Los guantes no reemplazan el lavado de manos...",
    "hasInterludedQuestion": true,
    "questionId": null,
    "questionIds": ["q_p3i", "q_p4i", "q_p5i"]
  }
}

UI components

TheoryBlockView renders the summary, key points, and technical note. After the learner clicks “Continuar”, LessonFlowPage transitions to the question step and renders InlineQuestionView for each question in sequence. The learner must answer correctly to advance to the next block.

Progress tracked

interface TheoryBlockProgress {
  completed: boolean
  // Single-question blocks:
  questionAnswered?: boolean
  answeredCorrect?: boolean
  // Multi-question blocks:
  questionsAnswered?: boolean[]    // One entry per question in questionIds[]
}

game block — GameBlockContent

The game block presents a scenario-based error-identification activity. It defines a list of hygiene violations the learner must spot, along with scoring rules and a time limit.

Firestore content schema

interface GameError {
  errorId: string
  description: string
  normativeRef: string
  feedbackText: string
}

interface GameBlockContent {
  sceneName: string
  scenario: string
  objective: string
  totalErrors: number
  errors: GameError[]
  scoring: {
    baseScore: number
    penaltyPerHint: number
    penaltyPerWrongClick: number
    passingScore: number
  }
  timeLimitSeconds: number
  unityGamePath?: string      // Optional path to Unity WebGL build
  unityBuildName?: string     // Optional Unity build name
}

Lesson 1 example (b07_game)

{
  "blockId": "b07_game",
  "type": "game",
  "order": 7,
  "title": "Minijuego: Inspección de cocina",
  "isRequired": true,
  "content": {
    "sceneName": "kitchen_hygiene_inspection",
    "scenario": "Cocina de un establecimiento de preparación de alimentos. Debes identificar 6 incumplimientos de higiene personal.",
    "objective": "Encuentra los 6 errores de higiene personal en la escena antes de que se agote el tiempo.",
    "totalErrors": 6,
    "errors": [
      {
        "errorId": "err_joyeria",
        "description": "Manipulador con anillo y pulsera durante la preparación de alimentos",
        "normativeRef": "Art. 13, num. 3, Res. 2674 de 2013",
        "feedbackText": "La joyería acumula microbiota en superficies irregulares y puede desprenderse en el alimento como cuerpo extraño."
      },
      {
        "errorId": "err_fumar",
        "description": "Manipulador fumando dentro del área de producción",
        "normativeRef": "Art. 13, num. 3, Res. 2674 de 2013",
        "feedbackText": "Fumar en áreas de producción está expresamente prohibido por la norma."
      }
    ],
    "scoring": {
      "baseScore": 100,
      "penaltyPerHint": 10,
      "penaltyPerWrongClick": 5,
      "passingScore": 60
    },
    "timeLimitSeconds": 180,
    "unityGamePath": "/games/cocina-infectada/",
    "unityBuildName": "BPM_Game_Build"
  }
}

UI component

GameBlockView currently renders a placeholder UI: it displays the list of errors defined in the errors array for the learner to identify by clicking. This allows the game block to function end-to-end while the Unity integration is being completed.

Progress tracked

interface GameBlockProgress {
  completed: boolean
  gameScore: number
  errorsFound: number
  hintsUsed: number
  timeSpentSec: number
}
Unity WebGL integration is in progress. The unityGamePath and unityBuildName fields in GameBlockContent are designed to wire up a Unity WebGL build (e.g. a 3D kitchen scene) when that build is ready. The current GameBlockView uses a placeholder UI — an interactive checklist of the errors defined in the errors array — so the full lesson flow can be completed and tested today.

exam block — ExamBlockContent

The exam block is the final gate of every lesson. Rather than rendering inline content, it displays a summary card with the exam parameters and a button that navigates the learner to /leccion/:id/evaluacion (LessonExamPage).

Firestore content schema

interface ExamBlockContent {
  totalQuestions: number           // Number of questions to draw from the pool (e.g. 15)
  passingPercent: number           // Minimum score to pass, e.g. 70 (means 70%)
  randomize: boolean               // Shuffle questions on each attempt
  showExplanationAfterAnswer: boolean
  unlimitedAttempts: boolean
}

Lesson 1 example (b08_exam)

{
  "blockId": "b08_exam",
  "type": "exam",
  "order": 8,
  "title": "Evaluación final — Lección 1",
  "isRequired": true,
  "content": {
    "totalQuestions": 15,
    "passingPercent": 70,
    "randomize": true,
    "showExplanationAfterAnswer": false,
    "unlimitedAttempts": true
  }
}

UI

The exam block renders a short summary in LessonFlowPage (question count and passing threshold). Clicking “Iniciar evaluación final” navigates to LessonExamPage at /leccion/:id/evaluacion, which runs the full randomized question flow and calls saveExamResult when the learner submits. The “Iniciar evaluación final” button is disabled until isExamUnlocked returns true — meaning all non-exam blocks must be completed first.

Progress tracked

interface ExamBlockProgress {
  completed: boolean
  lastScore: number     // Score from the most recent attempt (0–100)
  attempts: number      // Total number of exam submissions
  passed: boolean
}

Build docs developers (and LLMs) love