Sistema de Badges
Visión general
Section titled “Visión general”NappAI Fluency gestiona dos sistemas de badges independientes que conviven en la misma plataforma:
| Sistema | Entidad | Módulo principal | Portal |
|---|---|---|---|
| Badges de Creador | CreatorProfile | AdminModule (admin-badges.*) | /creator/* |
| Badges de Estudiante | User | StudentBadgesModule | /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 (systemouserIddel 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).
Badges de Creador
Section titled “Badges de Creador”Definición y criterios
Section titled “Definición y criterios”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 otorgarloCatálogo de badges
Section titled “Catálogo de badges”| Tier | Slug | Nombre | Criterio |
|---|---|---|---|
| 1 — Base | primera-ruta | Primera Ruta | minPaths: 1 |
| 1 — Base | primeros-estudiantes | Primeros Estudiantes | minStudents: 10 |
| 2 — Silver | creador-activo | Creador Activo | minPaths: 3 |
| 2 — Silver | educador-popular | Educador Popular | minStudents: 100 |
| 3 — Gold | maestro-ia | Maestro de la IA | minStudents: 500 |
| 3 — Gold | experto-retencion | Experto en Retención | minCompletionRate: 70 |
| 4 — Diamond | formador-elite | Formador de Élite | minStudents: 2000 |
| 4 — Diamond | top-creador-nappai | Top Creador NappAI | minRating: 4.8 |
Métricas usadas en evaluación
Section titled “Métricas usadas en evaluación”AdminBadgesService.computeCreatorMetrics() calcula en tiempo real:
| Métrica | Query Prisma |
|---|---|
totalStudents | enrollment.count donde path.creatorId = id |
avgRating | pathReview.aggregate._avg.rating |
completionRate | progress.count (COMPLETED) / totalStudents * 100 |
totalPaths | path.count donde creatorId = id |
Badges de Estudiante
Section titled “Badges de Estudiante”Definición y categorías
Section titled “Definición y categorías”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}Tipos de criteria
Section titled “Tipos de criteria”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álogo completo (31 badges)
Section titled “Catálogo completo (31 badges)”CAT-01: Nivel de Dominio IA — 5 badges, tipo xp
| Slug | Nombre | Tier | Criterio |
|---|---|---|---|
cat01.badge.001 | Aprendiz IA | 1 | minXP: 0 (ingreso) |
cat01.badge.002 | Practicante IA | 2 | minXP: 100 + minLessons: 5 |
cat01.badge.003 | Experto IA | 3 | minXP: 500 + minRoutes: 1 |
cat01.badge.004 | Maestro IA | 4 | minXP: 1500 + minRoutes: 3 |
cat01.badge.005 | Gran Maestro IA | 5 | minXP: 4000 + minRoutes: 5 + minFluidityScore: 80 |
CAT-02: Rutas Completadas — 4 badges, tipo routes_completed
| Slug | Nombre | Tier | Criterio |
|---|---|---|---|
cat02.badge.001 | Primer Destino | 1 | minRoutes: 1 |
cat02.badge.002 | Explorador | 2 | minRoutes: 3 |
cat02.badge.003 | Navegante | 3 | minRoutes: 5 |
cat02.badge.004 | Pionero | 4 | minRoutes: 10 |
CAT-03: Score de Fluidez — 4 badges, tipo fluency_score
| Slug | Nombre | Tier | Criterio |
|---|---|---|---|
cat03.badge.001 | Mente Despierta | 1 | minScore: 25 |
cat03.badge.002 | Analista Cognitivo | 2 | minScore: 50 |
cat03.badge.003 | Arquitecto Cognitivo | 3 | minScore: 75 |
cat03.badge.004 | Mente Suprema | 4 | minScore: 95 |
CAT-04: Constancia en Campo — 4 badges, tipo streak (autoRevoke: true excepto el de 365 días)
| Slug | Nombre | Tier | Criterio |
|---|---|---|---|
cat04.badge.001 | Constante | 1 | minDays: 7 · se revoca si se rompe la racha |
cat04.badge.002 | Disciplinado | 2 | minDays: 30 · se revoca si se rompe la racha |
cat04.badge.003 | Implacable | 3 | minDays: 100 · se revoca si se rompe la racha |
cat04.badge.004 | Indestructible | 4 | minDays: 365 · irrevocable (autoRevoke: false) |
CAT-05: Maestría en Quiz — 4 badges, tipo quiz_performance
| Slug | Nombre | Tier | Criterio |
|---|---|---|---|
cat05.badge.001 | Agudo | 1 | 10 quizzes superados (cualquier nota) |
cat05.badge.002 | Preciso | 2 | 50 quizzes con nota ≥ 80 % |
cat05.badge.003 | Certero | 3 | 100 quizzes con nota ≥ 90 % |
cat05.badge.004 | Infalible | 4 | 50 quizzes perfectos al 100 % |
CAT-06: Especialización en IA — 4 badges, tipo specialization
| Slug | Nombre | routeTag |
|---|---|---|
cat06.badge.001 | Maestro del Prompt | prompting |
cat06.badge.002 | Arquitecto de Agentes | agents |
cat06.badge.003 | Estratega de Datos | data-ai |
cat06.badge.004 | Piloto Autónomo | automation |
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
| Slug | Nombre | Tier | Criterio |
|---|---|---|---|
cat07.badge.001 | Mente Rápida | 1 | 5 lecciones en < 50 % del tiempo estimado |
cat07.badge.002 | Relámpago | 2 | 1 ruta en < 40 % del tiempo estimado |
cat07.badge.003 | Fusión Neural | 3 | 1 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
| Slug | Nombre | Tier | Descripción |
|---|---|---|---|
cat08.badge.001 | Pionero Fundador | 1 | Primera generación de la academia |
cat08.badge.002 | Colaborador Élite | 2 | Contribución excepcional al equipo NappAI |
cat08.badge.003 | Embajador NappAI | 3 | Representante 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.
Diagrama de flujo
Section titled “Diagrama de flujo”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]Lógica de asignación
Section titled “Lógica de asignación”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():
- Carga todas las definiciones activas con
type !== 'manual'. - Itera todos los
Userno suspendidos. - Para cada usuario llama a
calculateStats(userId)que agrega:- XP total desde
StudentXPEvent fluidityScoredel campoUser.fluidityScore- Rutas completadas desde
EnrollmentconcompletedAt != null - Quizzes y sus notas desde
QuizAttempt - Progreso de lecciones desde
Progress(estadoCOMPLETED) - Streak calculado desde las fechas de
Progress.completedAt
- XP total desde
- Evalúa cada definición con
studentQualifies(criteria, stats). - Si califica y no tiene grant activo → crea
StudentBadgeGrant(grantedBy: 'system'). - Si los badges de tipo
streaktienenautoRevoke: truey el estudiante ya no califica → actualizarevokedAt = now(). - 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():
- Calcula métricas reales del creador (no usa campos denormalizados).
- Compara cada definición activa contra las métricas.
- Si califica y no tiene grant → crea el grant en ese mismo request (
grantedBy: 'system'). - 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.
Modelo de datos
Section titled “Modelo de datos”CreatorBadgeDefinition
Section titled “CreatorBadgeDefinition”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[]}CreatorBadgeGrant
Section titled “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])}StudentBadgeDefinition
Section titled “StudentBadgeDefinition”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[]}StudentBadgeGrant
Section titled “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
API — Endpoints
Section titled “API — Endpoints”Endpoints del Estudiante
Section titled “Endpoints del Estudiante”| Método | Ruta | Guard | Descripción |
|---|---|---|---|
GET | /api/users/me/badges | JwtAuthGuard | Todas las definiciones activas + grants del usuario. Responde { definitions[], grants[] } |
GET | /api/users/me/badges/summary | JwtAuthGuard | Resumen ligero: XP, nivel actual, insignias obtenidas, próximo hito con % de progreso |
GET | /api/badges/student/:assertionId | Sin auth | Aserción OB3 pública para verificación externa |
Endpoints del Creador
Section titled “Endpoints del Creador”| Método | Ruta | Guard | Descripción |
|---|---|---|---|
GET | /api/creator/me/badges | JwtAuthGuard + 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étodo | Ruta | Descripción |
|---|---|---|
GET | /api/admin/student-badge-definitions | Lista todas las definiciones |
GET | /api/admin/student-badge-definitions/:id | Detalle de una definición |
PATCH | /api/admin/student-badge-definitions/:id | Actualiza description, criteria, isActive |
GET | /api/admin/student-badge-definitions/:id/stats | KPIs: grants totales, activos, revocados, últimas 10 concesiones |
GET | /api/admin/student-badge-grants | Todos los grants paginados. Query params: filter (active/revoked/manual/auto), page, limit |
POST | /api/admin/student-badge-grants | Otorga manualmente un badge de tipo manual a un estudiante |
PATCH | /api/admin/student-badge-grants/:id/revoke | Revoca un grant |
PATCH | /api/admin/student-badge-grants/:id/restore | Restaura 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/run | Dispara evaluación manual (202 Accepted) |
Endpoints Admin — Creator Badges (SUPER_ADMIN)
Section titled “Endpoints Admin — Creator Badges (SUPER_ADMIN)”| Método | Ruta | Descripción |
|---|---|---|
GET | /api/admin/badge-definitions | Lista todas las definiciones de badges de creador |
POST | /api/admin/badge-definitions | Crea una nueva definición |
PATCH | /api/admin/badge-definitions/:id | Actualiza una definición |
DELETE | /api/admin/badge-definitions/:id | Soft-delete (isActive=false). Con ?force=true hard-delete en cascada |
GET | /api/admin/badge-definitions/:id/simulate | Simula quién calificaría para el badge: devuelve { qualifies[], close[] } |
GET | /api/admin/badge-definitions/grants | Todos los grants paginados |
POST | /api/admin/badge-definitions/grants | Otorga manualmente un badge manual a un creador |
PATCH | /api/admin/badge-definitions/grants/:id/revoke | Revoca un grant |
PATCH | /api/admin/badge-definitions/grants/:id/restore | Restaura 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/run | Dispara evaluación batch (202 Accepted) |
PATCH | /api/admin/badge-evaluator/config | Activa/desactiva el evaluador automático |
GET | /api/admin/badge-evaluator/stats | Estadísticas globales del sistema de badges |
Frontend
Section titled “Frontend”Componentes de renderizado de badges
Section titled “Componentes de renderizado de badges”| Componente | Ruta | Uso |
|---|---|---|
BadgeIcon | apps/web/src/components/badges/BadgeIcon.tsx | Badge de creador — SVG con shape de escudo según tier, gradiente y símbolo del badge-registry |
StudentBadgeIcon | apps/web/src/components/badges/StudentBadgeIcon.tsx | Badge de estudiante — variante con gradientes específicos por categoría |
BadgesSection | apps/web/src/components/portfolio/BadgesSection.tsx | Grid 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áginas de badges
Section titled “Páginas de badges”| Página | Ruta en app | Acceso |
|---|---|---|
StudentMyBadgesPage | /my-badges | Usuario autenticado. Hero con XP y nivel, filtros por categoría, polling cada 60 s |
CreatorBadgesPage | /creator/badges | CONTENT_ADMIN, SUPER_ADMIN. Banner con nivel actual, progreso hacia el siguiente tier, grid tri-columna |
AdminBadgeSystemPage | /admin/badge-system | SUPER_ADMIN. Panel completo: CRUD de definiciones, grants, simulador, evaluador |
AdminStudentBadgesPage | /admin/student-badges | SUPER_ADMIN. Gestión de definitions y grants de estudiantes |
RTK Query — studentBadgesApi
Section titled “RTK Query — studentBadgesApi”// Hooks exportados desde apps/web/src/store/api/studentBadgesApi.tsuseGetMyBadgesQuery() // { definitions[], grants[] }useGetMyBadgesSummaryQuery() // BadgeSummaryuseListStudentBadgeDefinitionsQuery() // admin: lista con conteosuseGetStudentBadgeGrantsQuery(filter) // admin: grants paginadosuseManualGrantStudentBadgeMutation() // admin: grant manualuseRevokeStudentBadgeGrantMutation() // admin: revocaruseTriggerStudentBadgeEvaluationMutation() // admin: disparar evaluadorOpen Badges 3.0
Section titled “Open Badges 3.0”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/:assertionIdLa 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.