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.

Nexu’s Firestore database is organized into six logical collections. Three of them — lessons, blocks, and questions — are write-once content loaded by seed scripts and read-only for the application. The other three — users, certificates, and certificatePublic — are written by the application as users progress through the course and earn their certification.
Content collections (lessons, lessons/{id}/blocks, lessons/{id}/questions) are populated by seed scripts run from a developer terminal (npm run seed:lesson1). The React application never writes to these collections. Progress and certificate collections are written exclusively by the running application.

Collection Reference

lessons/{lessonId}

Top-level lesson document. Defined by the LessonDoc interface in src/types/lesson.ts. The lessonId is used as the Firestore document ID (e.g., lesson_01_higiene_personal).
FieldTypeDescription
lessonIdstringDocument ID, e.g. lesson_01_higiene_personal
titlestringHuman-readable lesson title
ordernumberPosition in the learning path (1-based)
normativeBasestringNormative reference, e.g. "Resolución 2674 de 2013, Cap. III"
passingScorenumberMinimum passing score for the final exam (e.g. 70)
estimatedMinutesnumberEstimated time to complete the lesson
isActivebooleanWhether the lesson appears in the learning path
totalBlocksnumberTotal number of blocks in this lesson
Composite index (defined in firestore.indexes.json):
{
  "collectionGroup": "lessons",
  "queryScope": "COLLECTION",
  "fields": [
    { "fieldPath": "isActive", "order": "ASCENDING" },
    { "fieldPath": "order",    "order": "ASCENDING" }
  ]
}
This index is used by fetchActiveLessons() in lessonService.ts, which queries where('isActive', '==', true) + orderBy('order', 'asc').

lessons/{lessonId}/blocks/{blockId}

Subcollection of content blocks within a lesson. Defined by LessonBlock in src/types/lesson.ts. Blocks are fetched in order sequence; the app enforces linear progression — a block is only accessible once the previous block is marked completed.
FieldTypeDescription
blockIdstringDocument ID, e.g. b01_video, b02_modulo_a
typestringOne of: video, theory, game, exam
ordernumberDisplay order within the lesson (1-based)
titlestringBlock title shown in the lesson map
isRequiredbooleanWhether the block must be completed before the exam unlocks
imageUrlstring?Optional header image URL
imageAltstring?Alt text for the header image
contentobjectType-specific content object (see block types below)

Block content shapes by type

videoVideoBlockContent
FieldTypeDescription
youtubeUrlstringYouTube embed URL
minWatchPercentnumberMinimum watch percentage to mark block complete
durationSecondsnumberVideo duration in seconds
theoryTheoryBlockContent
FieldTypeDescription
normativeRefstringRegulation article referenced by this module
summarystringShort summary paragraph
keyPointsstring[]Bullet list of key takeaways
technicalNotestring | nullOptional technical callout
hasInterludedQuestionbooleanWhether an inline question interrupts reading
questionIdstring | nullSingle inline question ID (legacy)
questionIdsstring[] | nullMultiple inline question IDs
gameGameBlockContent
FieldTypeDescription
sceneNamestringIdentifier of the 3-D scene
scenariostringScenario description shown to the user
objectivestringGame objective text
totalErrorsnumberNumber of planted errors to find
errorsGameError[]Array of { errorId, description, normativeRef, feedbackText }
scoring.baseScorenumberScore before penalties
scoring.penaltyPerHintnumberPoints deducted per hint used
scoring.penaltyPerWrongClicknumberPoints deducted per wrong click
scoring.passingScorenumberMinimum score to pass the game block
timeLimitSecondsnumberTime limit for the game
unityGamePathstring?Optional path to the Unity WebGL build directory
unityBuildNamestring?Optional Unity build name identifier
examExamBlockContent
FieldTypeDescription
totalQuestionsnumberNumber of questions drawn from the pool
passingPercentnumberMinimum percentage correct to pass
randomizebooleanWhether question order is shuffled
showExplanationAfterAnswerbooleanShow per-question explanation immediately
unlimitedAttemptsbooleanWhether retakes are allowed

lessons/{lessonId}/questions/{questionId}

Subcollection of exam and inline questions. Defined by LessonQuestion in src/types/lesson.ts. Questions with questionType === 'evaluacion_final' are drawn for the final exam; those with questionType === 'intercalada' appear inline within theory blocks.
FieldTypeDescription
questionIdstringDocument ID, e.g. q_p1i, q_e01
blockRefstringblockId of the block this question belongs to
questionTypestringintercalada (inline) or evaluacion_final (final exam pool)
ordernumberSort order within its block
textstringQuestion stem
optionsarrayArray of { label, text, isCorrect, feedback? } answer choices
explanationstringExplanation shown after the user answers
normativeRefstringRegulation article this question tests
difficultystringbasica, intermedia, or avanzada
Each option in options has this shape:
FieldTypeDescription
labelstringOption letter, e.g. "A"
textstringOption text
isCorrectbooleanWhether this option is the correct answer
feedbackstring?Why this option is wrong (falls back to explanation if absent)

users/{userId}

User profile document created at registration by AuthContext.register(). The document ID is the Firebase Auth UID. Fields written at registration by AuthContext.tsx:
FieldTypeDescription
fullNamestringUser’s full name
emailstringEmail address
documentTypestringcc, ce, or passport
documentNumberstringGovernment ID number
createdAtTimestampServer timestamp at registration

users/{userId}/lessonProgress/{lessonId}

One document per lesson the user has started. Written and updated by progressService.ts. The document ID matches the lessonId.
FieldTypeDescription
lessonIdstringLesson identifier
statusstringlocked, in_progress, passed, or failed
startedAtTimestampServer timestamp when the user first opened the lesson
completedAtTimestamp | nullServer timestamp when the user passed; null until then
lastBlockCompletedstringblockId of the most recently completed block
totalTimeSpentSecnumberCumulative time in seconds across all blocks
blocksProgressobjectMap of blockId → BlockProgressEntry (see below)
evalAttemptsnumber[]Array of every exam score, in attempt order
bestEvalScorenumberHighest exam score achieved across all attempts

blocksProgress entry shapes

Each key in blocksProgress is a blockId. The value shape depends on the block type: Video block progress
FieldType
completedboolean
videoWatchedboolean
watchedPctnumber
Theory block progress
FieldType
completedboolean
questionAnsweredboolean?
answeredCorrectboolean?
questionsAnsweredboolean[]?
Game block progress
FieldType
completedboolean
gameScorenumber
errorsFoundnumber
hintsUsednumber
timeSpentSecnumber
Exam block progress
FieldType
completedboolean
lastScorenumber
attemptsnumber
passedboolean

certificates/{certificateId}

Private certificate record. Only the certificate owner (userId == request.auth.uid) can read it. Defined by CertificateRecord in src/types/certificate.ts. The certificateId is deterministic: cert_{userId[0..8]}_nexu_bpm_curso.
FieldTypeDescription
certificateIdstringDocument ID
userIdstringFirebase Auth UID of the certificate owner
lessonIdstring?Deprecated. Legacy per-lesson ID; new certificates use courseId instead
courseIdstringnexu_bpm_curso — identifies the full course
companyIdstring | nullReserved for enterprise accounts; currently null
userNamestringFull name as entered at registration
userDocumentstringFull government ID — private, never exposed publicly
lessonTitlestringDisplay title shown on the certificate
courseNamestring"Buenas Prácticas de Manufactura"
finalScorenumberAverage score across all completed lessons
completedLessonsCompletedLessonSnapshot[]?Snapshot of each passed lesson: { lessonId, lessonTitle, finalScore }
verifyCodestringPublic verification code, e.g. NX-AB12CD34EF
qrVerifyUrlstringFull URL to /verificar/{verifyCode}
issuedAtTimestampIssue date
expiresAtTimestampExpiry date (1 year after issuedAt)
isValidbooleantrue when valid; false when revoked (future feature)
Composite index (defined in firestore.indexes.json):
{
  "collectionGroup": "certificates",
  "queryScope": "COLLECTION",
  "fields": [
    { "fieldPath": "userId",   "order": "ASCENDING" },
    { "fieldPath": "issuedAt", "order": "DESCENDING" }
  ]
}
A certificate is issued only when all 8 lessons of the program (NEXU_COURSE_TOTAL_LESSONS = 8) are published (isActive: true) and marked passed in the user’s progress. Passing a single lesson does not issue a certificate — tryIssueCourseCertificateAfterLesson() in certificateService.ts checks this condition after every exam.

certificatePublic/{verifyCode}

Publicly readable record — no authentication required. The document ID is the verifyCode itself (e.g., NX-AB12CD34EF). This collection powers the QR code verification page at /verificar/:code. Defined by CertificatePublicRecord in src/types/certificate.ts.
FieldTypeDescription
certificateIdstringReference back to the private certificates document
userIdstringOwner UID
userNamestringFull name (public)
userDocumentMaskedstringMasked ID, e.g. CC. ***6789
lessonTitlestringCertificate display title
courseNamestring"Buenas Prácticas de Manufactura"
finalScorenumberAverage score across completed lessons
completedLessonsCompletedLessonSnapshot[]?Passed lesson snapshots
issuedAtTimestampIssue date
expiresAtTimestampExpiry date
isValidbooleanValidity flag
statusstringvalid, expired, or revoked
The masked document field is generated in certificateService.ts as:
function maskDocument(documentType: string, documentNumber: string): string {
  const tail = documentNumber.slice(-4)
  return `${documentType.toUpperCase()}. ***${tail}`
}

Write Pattern Summary

CollectionWritten byRead by
lessonsSeed scripts (terminal)App — all authenticated users
lessons/.../blocksSeed scripts (terminal)App — all authenticated users
lessons/.../questionsSeed scripts (terminal)App — all authenticated users
users/{uid}AuthContext.register()Owner only
users/{uid}/lessonProgress/{id}progressService.tsOwner only
certificates/{id}certificateService.tsOwner only
certificatePublic/{code}certificateService.tsEveryone (no auth required)

Build docs developers (and LLMs) love