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 issues a single, official certificate when a user passes the final evaluation for all 8 BPM lessons in the program. The certificate is not awarded per-lesson; it represents full program completion and is valid for one year from the date of issuance. Once earned, it is stored in two Firestore collections — one private (owner-only) and one publicly readable for third-party verification.

Certificate Issuance Condition

A certificate is only created once the user has a status: 'passed' on every lesson in the course. The constant identifiers that govern this check are:
// src/types/certificate.ts
export const NEXU_COURSE_ID = 'nexu_bpm_curso'
export const NEXU_COURSE_TOTAL_LESSONS = 8
The service function tryIssueCourseCertificateAfterLesson is called after every exam, but it returns null unless all 8 lessons are passed. This makes the trigger completely automatic and requires no manual intervention.

Firestore Storage Model

The certificate data is split into two Firestore collections with different access controls:
CollectionDocument IDWho can read
certificates/{certificateId}cert_{userId[0:8]}_nexu_bpm_cursoAuthenticated owner only
certificatePublic/{verifyCode}NX-XXXXXXXXXX (verify code)Anyone — no login required
The private certificates collection holds the full record including the unmasked document number. The certificatePublic collection holds only a masked version of the ID (CC. ***6789) and is the source of truth for the public verification page.

Certificate Status Lifecycle

A certificate can be in one of three states, computed by resolveStatus(expiresAt, isValid):
StatusCondition
validisValid: true and expiresAt is in the future
expiredisValid: true but expiresAt timestamp is in the past
revokedisValid: false (reserved for a future admin feature)
The validity window is exactly 1 year (365 × 24 × 60 × 60 × 1000 ms) from issuedAt. The status is re-evaluated on every read — it is not stored as a mutable field in the private record, but it is denormalized into certificatePublic at creation time.

Idempotency

tryIssueCourseCertificateAfterLesson is idempotent. If a certificate already exists for a given user, it returns the existing document without creating a duplicate. There is always at most one certificate per user (keyed on cert_{userId[0:8]}_nexu_bpm_curso). The deprecated issueCertificate export delegates to the same pipeline internally, but throws if not all 8 lessons are passed — prefer tryIssueCourseCertificateAfterLesson in new code.

Tracking Progress Toward the Certificate

Use fetchCourseCertificationProgress(userId) to retrieve the user’s current standing across all 8 planned lessons. This function returns a CourseCertificationProgress object regardless of how many lessons are currently active in Firestore.
const progress = await fetchCourseCertificationProgress(userId)

CourseCertificationProgress Fields

FieldTypeDescription
courseIdstringAlways nexu_bpm_curso
courseNamestring"Buenas Prácticas de Manufactura"
lessonsLessonCompetency[]Per-lesson status array (8 entries)
passedCountnumberNumber of lessons with status: 'passed'
totalCountnumberAlways 8
percentnumberMath.round((passedCount / 8) * 100)
allLessonsPassedbooleantrue when all 8 lessons are passed
Each entry in the lessons array is a LessonCompetency with a status of 'passed', 'in_progress', 'locked', or 'upcoming'. Lessons not yet published in Firestore show up as 'upcoming' with a placeholder title.

Explore This Section

Issuance Flow

Step-by-step walkthrough from exam pass to dual Firestore writes.

PDF & QR

Client-side PDF generation with html2canvas and jsPDF, and how the QR code is built.

Public Verification

How anyone can verify a certificate at /verificar/:code without logging in.

Adding a Lesson

What lesson developers need to do — and what they don’t need to touch in this module.

Build docs developers (and LLMs) love