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.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.
Certificate Issuance Condition
A certificate is only created once the user has astatus: 'passed' on every lesson in the course. The constant identifiers that govern this check are:
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:| Collection | Document ID | Who can read |
|---|---|---|
certificates/{certificateId} | cert_{userId[0:8]}_nexu_bpm_curso | Authenticated owner only |
certificatePublic/{verifyCode} | NX-XXXXXXXXXX (verify code) | Anyone — no login required |
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 byresolveStatus(expiresAt, isValid):
| Status | Condition |
|---|---|
valid | isValid: true and expiresAt is in the future |
expired | isValid: true but expiresAt timestamp is in the past |
revoked | isValid: false (reserved for a future admin feature) |
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
UsefetchCourseCertificationProgress(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.
CourseCertificationProgress Fields
| Field | Type | Description |
|---|---|---|
courseId | string | Always nexu_bpm_curso |
courseName | string | "Buenas Prácticas de Manufactura" |
lessons | LessonCompetency[] | Per-lesson status array (8 entries) |
passedCount | number | Number of lessons with status: 'passed' |
totalCount | number | Always 8 |
percent | number | Math.round((passedCount / 8) * 100) |
allLessonsPassed | boolean | true when all 8 lessons are passed |
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.