Skip to content

Sistema de Badges

NappAI Fluency gestiona dos sistemas de badges independientes que conviven en la misma plataforma:

SistemaEntidadMódulo principalPortal
Badges de CreadorCreatorProfileAdminModule (admin-badges.*)/creator/*
Badges de EstudianteUserStudentBadgesModule/dashboard, /learn/*

Ambos sistemas comparten la misma filosofía de definición + grant:

  • Una definición (*BadgeDefinition) describe el badge: nombre, icono, criterios, tier.
  • Un grant (*BadgeGrant) registra que un usuario o creador concreto ha obtenido el badge, con la fecha, el origen (system o userId del admin) y la posibilidad de revocación.

Los badges son visibles en el perfil público del estudiante (/profile/:username) a través del componente BadgesSection y son exportables como Open Badges 3.0 para su verificación externa (Credly, Badgr, LinkedIn).


Los badges de creador se almacenan en CreatorBadgeDefinition. El sistema contempla 8 badges organizados en 4 tiers. Las definiciones se inicializan mediante el seed principal (apps/api/prisma/seed.ts) y son editables por SUPER_ADMIN.

Estructura del criteria:

// El campo criteria es JSON — una sola propiedad indica el tipo de criterio
{ minPaths: 1 } // número de rutas publicadas
{ minStudents: 100 } // matrículas totales
{ minCompletionRate: 70 } // tasa de completado media (%)
{ minRating: 4.8 } // valoración media de rutas
{ type: 'manual' } // solo SUPER_ADMIN puede otorgarlo
TierSlugNombreCriterio
1 — Baseprimera-rutaPrimera RutaminPaths: 1
1 — Baseprimeros-estudiantesPrimeros EstudiantesminStudents: 10
2 — Silvercreador-activoCreador ActivominPaths: 3
2 — Silvereducador-popularEducador PopularminStudents: 100
3 — Goldmaestro-iaMaestro de la IAminStudents: 500
3 — Goldexperto-retencionExperto en RetenciónminCompletionRate: 70
4 — Diamondformador-eliteFormador de ÉliteminStudents: 2000
4 — Diamondtop-creador-nappaiTop Creador NappAIminRating: 4.8

AdminBadgesService.computeCreatorMetrics() calcula en tiempo real:

MétricaQuery Prisma
totalStudentsenrollment.count donde path.creatorId = id
avgRatingpathReview.aggregate._avg.rating
completionRateprogress.count (COMPLETED) / totalStudents * 100
totalPathspath.count donde creatorId = id

Los badges de estudiante se almacenan en StudentBadgeDefinition con un campo category de tipo enum StudentBadgeCategory. El sistema incluye 31 badges pre-definidos, organizados en 8 categorías (CAT-01 a CAT-08).

enum StudentBadgeCategory {
LEVEL // CAT-01: Nivel de Dominio IA
ROUTES // CAT-02: Rutas Completadas
FLUENCY // CAT-03: Score de Fluidez
STREAK // CAT-04: Constancia en Campo
QUIZ // CAT-05: Maestría en Quiz
SPECIALIZATION // CAT-06: Especialización en IA
SPEED // CAT-07: Velocidad de Aprendizaje
HONOR // CAT-08: Distinción de Honor
}

StudentBadgeCriteria es una union type que contempla 8 tipos:

type StudentBadgeCriteria =
| { type: 'xp'; minXP: number; extraCriteria?: { minLessons?: number; minRoutes?: number; minFluidityScore?: number }; autoRevoke: false }
| { type: 'routes_completed'; minRoutes: number; autoRevoke: false }
| { type: 'fluency_score'; minScore: number; autoRevoke: false }
| { type: 'streak'; minDays: number; autoRevoke: boolean } // ← puede ser true
| { type: 'quiz_performance'; minCount: number; minScore: number; autoRevoke: false }
| { type: 'specialization'; routeTag: string; autoRevoke: false }
| { type: 'speed'; target: 'lessons'|'route'; minCount: number; maxTimeRatio: number; minQuizScore: number|null; autoRevoke: false }
| { type: 'manual'; autoRevoke: false }

CAT-01: Nivel de Dominio IA — 5 badges, tipo xp

SlugNombreTierCriterio
cat01.badge.001Aprendiz IA1minXP: 0 (ingreso)
cat01.badge.002Practicante IA2minXP: 100 + minLessons: 5
cat01.badge.003Experto IA3minXP: 500 + minRoutes: 1
cat01.badge.004Maestro IA4minXP: 1500 + minRoutes: 3
cat01.badge.005Gran Maestro IA5minXP: 4000 + minRoutes: 5 + minFluidityScore: 80

CAT-02: Rutas Completadas — 4 badges, tipo routes_completed

SlugNombreTierCriterio
cat02.badge.001Primer Destino1minRoutes: 1
cat02.badge.002Explorador2minRoutes: 3
cat02.badge.003Navegante3minRoutes: 5
cat02.badge.004Pionero4minRoutes: 10

CAT-03: Score de Fluidez — 4 badges, tipo fluency_score

SlugNombreTierCriterio
cat03.badge.001Mente Despierta1minScore: 25
cat03.badge.002Analista Cognitivo2minScore: 50
cat03.badge.003Arquitecto Cognitivo3minScore: 75
cat03.badge.004Mente Suprema4minScore: 95

CAT-04: Constancia en Campo — 4 badges, tipo streak (autoRevoke: true excepto el de 365 días)

SlugNombreTierCriterio
cat04.badge.001Constante1minDays: 7 · se revoca si se rompe la racha
cat04.badge.002Disciplinado2minDays: 30 · se revoca si se rompe la racha
cat04.badge.003Implacable3minDays: 100 · se revoca si se rompe la racha
cat04.badge.004Indestructible4minDays: 365 · irrevocable (autoRevoke: false)

CAT-05: Maestría en Quiz — 4 badges, tipo quiz_performance

SlugNombreTierCriterio
cat05.badge.001Agudo110 quizzes superados (cualquier nota)
cat05.badge.002Preciso250 quizzes con nota ≥ 80 %
cat05.badge.003Certero3100 quizzes con nota ≥ 90 %
cat05.badge.004Infalible450 quizzes perfectos al 100 %

CAT-06: Especialización en IA — 4 badges, tipo specialization

SlugNombrerouteTag
cat06.badge.001Maestro del Promptprompting
cat06.badge.002Arquitecto de Agentesagents
cat06.badge.003Estratega de Datosdata-ai
cat06.badge.004Piloto Autónomoautomation

El criterio se evalúa comprobando si enrollment.path.targetRoles contiene el routeTag configurado y el enrollment tiene completedAt no nulo.

CAT-07: Velocidad de Aprendizaje — 3 badges, tipo speed

SlugNombreTierCriterio
cat07.badge.001Mente Rápida15 lecciones en < 50 % del tiempo estimado
cat07.badge.002Relámpago21 ruta en < 40 % del tiempo estimado
cat07.badge.003Fusión Neural31 ruta en < 40 % + nota media ≥ 90 %

El tiempo se compara contra lesson.durationMin * 60. Una lección es “rápida” si progress.timeSpentSec < durationEstimadaSec * 0.6.

CAT-08: Distinción de Honor — 3 badges, tipo manual

SlugNombreTierDescripción
cat08.badge.001Pionero Fundador1Primera generación de la academia
cat08.badge.002Colaborador Élite2Contribución excepcional al equipo NappAI
cat08.badge.003Embajador NappAI3Representante oficial de la academia

Los badges de tipo manual solo pueden otorgarse desde el panel admin (SUPER_ADMIN). El sistema bloquea su auto-otorgamiento en el evaluador.


flowchart TD
A([Trigger]) --> B{Tipo de trigger}
B -->|Cron 02:30 diario| C[StudentBadgeEvaluatorJob]
B -->|Acceso a GET /creator/me/badges| D[CreatorsService.getMyBadgeProgress]
B -->|POST admin/.../run| E[AdminBadgesService.runEvaluation]
C --> F[calculateStats para cada usuario]
D --> G[computeCreatorMetrics para creador]
E --> H[computeCreatorMetrics para cada creador]
F --> I{studentQualifies?}
G --> J{currentValue >= targetValue?}
H --> K{currentValue >= targetValue?}
I -->|Sí, sin grant activo| L[StudentBadgeGrant.create / grantedBy=system]
I -->|No, con grant streak autoRevoke| M[StudentBadgeGrant.update revokedAt=now]
J -->|Sí, sin grant activo| N[CreatorBadgeGrant.create / grantedBy=system]
K -->|Sí, sin grant activo| O[CreatorBadgeGrant.upsert / grantedBy=system]
P([SUPER_ADMIN]) -->|POST /admin/student-badge-grants| Q[manualGrant]
P -->|POST /admin/badge-definitions/grants| R[manualGrant creador]
Q --> S{criteria.type === manual?}
R --> S
S -->|No| T[400 BadRequest]
S -->|Sí| U[Grant creado con grantedBy=adminUserId]

Badges de Estudiante — evaluación batch nightly

Section titled “Badges de Estudiante — evaluación batch nightly”

El StudentBadgeEvaluatorJob corre todos los días a las 02:30 (cron 30 2 * * *). Se integra con ScheduledJobsModule para que el SUPER_ADMIN pueda habilitarlo/deshabilitarlo desde /admin/platform/jobs (slug: evaluate-creator-badges).

El flujo interno de runEvaluationAsync():

  1. Carga todas las definiciones activas con type !== 'manual'.
  2. Itera todos los User no suspendidos.
  3. Para cada usuario llama a calculateStats(userId) que agrega:
    • XP total desde StudentXPEvent
    • fluidityScore del campo User.fluidityScore
    • Rutas completadas desde Enrollment con completedAt != null
    • Quizzes y sus notas desde QuizAttempt
    • Progreso de lecciones desde Progress (estado COMPLETED)
    • Streak calculado desde las fechas de Progress.completedAt
  4. Evalúa cada definición con studentQualifies(criteria, stats).
  5. Si califica y no tiene grant activo → crea StudentBadgeGrant (grantedBy: 'system').
  6. Si los badges de tipo streak tienen autoRevoke: true y el estudiante ya no califica → actualiza revokedAt = now().
  7. Guarda el resultado del run en memoria (últimas 20 ejecuciones accesibles vía API).

El evaluador también puede dispararse manualmente desde el panel admin vía POST /api/admin/student-badge-evaluator/run (responde 202 Accepted, fire-and-forget).

Badges de Creador — evaluación on-demand

Section titled “Badges de Creador — evaluación on-demand”

Para creadores, la evaluación ocurre en tiempo real al acceder a GET /api/creator/me/badges. CreatorsService.getMyBadgeProgress():

  1. Calcula métricas reales del creador (no usa campos denormalizados).
  2. Compara cada definición activa contra las métricas.
  3. Si califica y no tiene grant → crea el grant en ese mismo request (grantedBy: 'system').
  4. Devuelve el progreso detallado (currentValue / targetValue / porcentaje).

El AdminBadgesService.runEvaluation() también ofrece una evaluación batch para creadores, disparable desde /admin/badge-evaluator/run. Itera todos los CreatorProfile con isPublic: true.


model CreatorBadgeDefinition {
id String @id @default(uuid())
slug String @unique
name String
description String
iconEmoji String
iconKey String // key en el badge-registry del frontend
tier Int // 1=Base, 2=Silver, 3=Gold, 4=Diamond
criteria Json // { minStudents: 500 } | { type: 'manual' } | …
isActive Boolean @default(true)
grants CreatorBadgeGrant[]
}
model CreatorBadgeGrant {
id String @id @default(uuid())
creatorId String
creator CreatorProfile @relation(...)
badgeId String
badge CreatorBadgeDefinition @relation(...)
grantedAt DateTime @default(now())
revokedAt DateTime?
grantedBy String? // 'system' | userId del admin
@@unique([creatorId, badgeId])
@@index([creatorId])
}
model StudentBadgeDefinition {
id String @id @default(cuid())
slug String @unique
name String
description String @default("")
category StudentBadgeCategory
iconKey String
tier Int
criteria Json
isActive Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
grants StudentBadgeGrant[]
}
model StudentBadgeGrant {
id String @id @default(cuid())
studentId String
student User @relation("StudentBadgeGrants", ...)
badgeId String
badge StudentBadgeDefinition @relation(...)
grantedBy String? // 'system' | userId del admin
grantedAt DateTime @default(now())
revokedAt DateTime?
supersededAt DateTime?
metadata Json?
assertionId String? @unique @default(cuid())
badgeUrl String? // URL de la imagen del badge (Open Badges)
@@index([studentId, badgeId])
}

El campo assertionId permite generar la URL pública de verificación OB3: /badges/student/:assertionId


MétodoRutaGuardDescripción
GET/api/users/me/badgesJwtAuthGuardTodas las definiciones activas + grants del usuario. Responde { definitions[], grants[] }
GET/api/users/me/badges/summaryJwtAuthGuardResumen ligero: XP, nivel actual, insignias obtenidas, próximo hito con % de progreso
GET/api/badges/student/:assertionIdSin authAserción OB3 pública para verificación externa
MétodoRutaGuardDescripción
GET/api/creator/me/badgesJwtAuthGuard + RolesGuard(CONTENT_ADMIN, SUPER_ADMIN)Progreso detallado de todos los badges de creador. Auto-otorga badges elegibles en el mismo request

Endpoints Admin — Student Badges (SUPER_ADMIN)

Section titled “Endpoints Admin — Student Badges (SUPER_ADMIN)”
MétodoRutaDescripción
GET/api/admin/student-badge-definitionsLista todas las definiciones
GET/api/admin/student-badge-definitions/:idDetalle de una definición
PATCH/api/admin/student-badge-definitions/:idActualiza description, criteria, isActive
GET/api/admin/student-badge-definitions/:id/statsKPIs: grants totales, activos, revocados, últimas 10 concesiones
GET/api/admin/student-badge-grantsTodos los grants paginados. Query params: filter (active/revoked/manual/auto), page, limit
POST/api/admin/student-badge-grantsOtorga manualmente un badge de tipo manual a un estudiante
PATCH/api/admin/student-badge-grants/:id/revokeRevoca un grant
PATCH/api/admin/student-badge-grants/:id/restoreRestaura un grant revocado
GET/api/admin/student-badge-evaluator/last-runÚltima ejecución del evaluador
GET/api/admin/student-badge-evaluator/runsÚltimas N ejecuciones
POST/api/admin/student-badge-evaluator/runDispara evaluación manual (202 Accepted)

Endpoints Admin — Creator Badges (SUPER_ADMIN)

Section titled “Endpoints Admin — Creator Badges (SUPER_ADMIN)”
MétodoRutaDescripción
GET/api/admin/badge-definitionsLista todas las definiciones de badges de creador
POST/api/admin/badge-definitionsCrea una nueva definición
PATCH/api/admin/badge-definitions/:idActualiza una definición
DELETE/api/admin/badge-definitions/:idSoft-delete (isActive=false). Con ?force=true hard-delete en cascada
GET/api/admin/badge-definitions/:id/simulateSimula quién calificaría para el badge: devuelve { qualifies[], close[] }
GET/api/admin/badge-definitions/grantsTodos los grants paginados
POST/api/admin/badge-definitions/grantsOtorga manualmente un badge manual a un creador
PATCH/api/admin/badge-definitions/grants/:id/revokeRevoca un grant
PATCH/api/admin/badge-definitions/grants/:id/restoreRestaura un grant revocado
GET/api/admin/badge-evaluator/last-runÚltima evaluación batch de creadores
GET/api/admin/badge-evaluator/runsÚltimas N evaluaciones
POST/api/admin/badge-evaluator/runDispara evaluación batch (202 Accepted)
PATCH/api/admin/badge-evaluator/configActiva/desactiva el evaluador automático
GET/api/admin/badge-evaluator/statsEstadísticas globales del sistema de badges

ComponenteRutaUso
BadgeIconapps/web/src/components/badges/BadgeIcon.tsxBadge de creador — SVG con shape de escudo según tier, gradiente y símbolo del badge-registry
StudentBadgeIconapps/web/src/components/badges/StudentBadgeIcon.tsxBadge de estudiante — variante con gradientes específicos por categoría
BadgesSectionapps/web/src/components/portfolio/BadgesSection.tsxGrid de badges en el perfil público del estudiante. Muestra pin OB3 y enlace de verificación

Los iconos se resuelven a través de badge-registry.tsx mediante un iconKey que mapea a un SVG path. El sistema de tiers define 4 gradientes visuales:

  • Tier 1 — Base: bronce
  • Tier 2 — Silver: plateado
  • Tier 3 — Gold: dorado
  • Tier 4 — Diamond: púrpura/diamante
  • Tier 5 (solo estudiantes, categoría LEVEL): reservado para Gran Maestro IA
PáginaRuta en appAcceso
StudentMyBadgesPage/my-badgesUsuario autenticado. Hero con XP y nivel, filtros por categoría, polling cada 60 s
CreatorBadgesPage/creator/badgesCONTENT_ADMIN, SUPER_ADMIN. Banner con nivel actual, progreso hacia el siguiente tier, grid tri-columna
AdminBadgeSystemPage/admin/badge-systemSUPER_ADMIN. Panel completo: CRUD de definiciones, grants, simulador, evaluador
AdminStudentBadgesPage/admin/student-badgesSUPER_ADMIN. Gestión de definitions y grants de estudiantes
// Hooks exportados desde apps/web/src/store/api/studentBadgesApi.ts
useGetMyBadgesQuery() // { definitions[], grants[] }
useGetMyBadgesSummaryQuery() // BadgeSummary
useListStudentBadgeDefinitionsQuery() // admin: lista con conteos
useGetStudentBadgeGrantsQuery(filter) // admin: grants paginados
useManualGrantStudentBadgeMutation() // admin: grant manual
useRevokeStudentBadgeGrantMutation() // admin: revocar
useTriggerStudentBadgeEvaluationMutation() // admin: disparar evaluador

Los StudentBadgeGrant con assertionId exponen una URL pública de aserción compatible con el estándar Open Badges 3.0:

GET /api/badges/student/:assertionId

La respuesta sigue el formato VerifiableCredential + OpenBadgeCredential e incluye credentialSubject.achievement con los criterios del badge. Este endpoint no requiere autenticación y es el que se usa para la verificación en LinkedIn y otras plataformas compatibles.

El botón “Compartir en LinkedIn” en StudentMyBadgesPage genera una URL de LinkedIn Certifications pre-rellenada con el nombre del badge, año/mes de emisión y la URL de verificación pública.