Portafolio del Estudiante
¿Qué es?
Section titled “¿Qué es?”El portafolio del estudiante es una página pública y compartible que agrega automáticamente los logros de aprendizaje de un usuario: certificados verificables, badges Open Badge, rutas completadas, habilidades demostradas y una línea de tiempo de actividad.
Cada estudiante tiene su propio portafolio en la URL fluency.nappai.com/u/:username. El portafolio es privado por defecto — el estudiante debe activarlo explícitamente y elegir un username desde los ajustes de su cuenta (/settings, pestaña Portafolio).
Archivos clave:
apps/api/src/modules/portfolio/— módulo NestJS (controller, service, dto)apps/web/src/pages/public/StudentPortfolioPage.tsx— página públicaapps/web/src/pages/settings/sections/PortfolioSection.tsx— configuración del estudianteapps/web/src/components/portfolio/— 14 componentes de UIapps/web/src/store/api/portfolioApi.ts— slice RTK Queryapps/web/src/hooks/usePortfolio.ts— hook auxiliar
Secciones del portafolio
Section titled “Secciones del portafolio”El portafolio se compone de las siguientes secciones. Cada sección puede activarse o desactivarse individualmente desde los ajustes.
| Sección | Componente | Controlable |
|---|---|---|
| Hero con nivel XP, score y estadísticas | PortfolioHeroBanner | No |
| Habilidades verificadas | SkillsSection | Sí (showCertificates) |
| Certificados SHA-256 | CertificatesSection | Sí (showCertificates) |
| Badges Open Badge 3.0 | BadgesSection | Sí (showBadges) |
| Rutas completadas o en curso | PathsSection | Sí (showPaths) |
| Línea de tiempo de actividad | TimelineSection | Sí (showTimeline) |
| Medidor XP y nivel | XPMeter | Sí (showXP) |
| Links de perfil (LinkedIn, web, GitHub) | ProfileLinksCard | No |
| Card para reclutadores | RecruiterInfoCard | No |
| Estándares (Open Badge) | StandardsCard | No |
URL pública y visibilidad
Section titled “URL pública y visibilidad”GET /u/:username ← Ruta React Router — página públicaEl username debe tener entre 3 y 40 caracteres (letras, números, guiones). Los cambios de username están sujetos a un cooldown de 30 días. Nombres reservados (admin, api, verify, dashboard, etc.) no pueden usarse.
El campo isPublic del modelo StudentProfile controla el acceso:
isPublic: false→ la API devuelve 404 con códigoPORTFOLIO_PRIVATEisPublic: true→ acceso libre sin autenticación
El contador de visitas (viewCount) se incrementa de forma fire-and-forget en cada visualización.
Nivel XP y progresión
Section titled “Nivel XP y progresión”El nivel del estudiante se calcula en tiempo real a partir de dos fuentes:
XP total = Σ xpEvents.xp + Σ badges.tierXP| Tier badge | XP aportado |
|---|---|
| Bronze (tier 1) | 25 XP |
| Silver (tier 2) | 50 XP |
| Gold (tier 3) | 100 XP |
| Platinum (tier 4) | 200 XP |
| Legend (tier 5) | 500 XP |
La progresión de nivel es lineal: 500 XP por nivel, máximo nivel 10.
| Nivel | Nombre |
|---|---|
| 1–3 | Aprendiz IA |
| 4–5 | Practicante IA |
| 6–7 | Arquitecto IA |
| 8–9 | Experto IA |
| 10 | Maestro IA |
Relación con certificados
Section titled “Relación con certificados”Los certificados se integran en el portafolio con verificación en tiempo real.
flowchart LR C[Certificate\nverificationHash SHA-256] -->|verifyUrl| V[/verify/:uuid] C -->|linkedinUrl| LI[LinkedIn Add Credential] C --> P[Portafolio público\n/u/:username] P -->|verifyAll POST| R[VerificationReport[]]Cada Certificate expone:
verifyUrl—https://fluency.nappai.com/verify/:id(verificación pública sin auth)linkedinUrl— URL prefabricada para añadir la credencial a LinkedIn (incluyeorganizationId: 99009889, año/mes de emisión y lacertUrl)verificationHash— SHA-256 generado en el backend:SHA-256(userId + pathId + issuedAt + score). Nunca se genera en el cliente.
El endpoint POST /public/portfolio/:username/verify-all devuelve un VerificationReport[] con el estado de validez de todos los certificados del portafolio, sin requerir autenticación.
Los tipos de certificado expuestos en el portafolio:
CertType en BD | Tipo público |
|---|---|
PATH_CERTIFICATE | PATH |
SPECIALIZATION_CERTIFICATE | SPECIALIZATION |
CORPORATE_CERTIFICATE | CORPORATE |
Habilidades (skills)
Section titled “Habilidades (skills)”Las habilidades se derivan automáticamente de los paths en los que el estudiante está matriculado, a partir del campo skills[] de cada Path. Una habilidad queda marcada como verificada (verified: true) si el estudiante tiene un certificado del path que la define.
// Estructura devuelta por la APIinterface SkillItem { name: string // e.g. "Prompt Engineering" verified: boolean // true si hay certificado del path origen certId: string | null}Línea de tiempo
Section titled “Línea de tiempo”La línea de tiempo (timeline) agrega los 10 eventos más recientes de tres tipos:
| Tipo | Evento |
|---|---|
certificate | Certificado obtenido |
badge | Badge conseguido |
enrollment | Inscripción en una ruta |
Frontend — páginas y componentes
Section titled “Frontend — páginas y componentes”Página pública (StudentPortfolioPage)
Section titled “Página pública (StudentPortfolioPage)”Ruta: apps/web/src/pages/public/StudentPortfolioPage.tsx
Layout de dos columnas en desktop (1fr + 320px sidebar), una columna en móvil (< 860px). Gestiona tres estados:
- Cargando — spinner
Spinner - Error / privado —
ErrorStatecon CTA al catálogo - Portafolio activo — grid completo con todas las secciones
Soporta impresión en PDF (window.print()) mediante media queries @media print que ocultan navegación, botones y sidebar.
Sección de ajustes (PortfolioSection)
Section titled “Sección de ajustes (PortfolioSection)”Ruta: apps/web/src/pages/settings/sections/PortfolioSection.tsx
Permite al estudiante:
- Activar/desactivar el portafolio público
- Elegir o generar automáticamente un username
- Editar bio, ubicación, links (web, Twitter/X, GitHub)
- Controlar la visibilidad de cada sección mediante toggles
- Ver el contador de visitas de su portafolio
El botón “Auto-generar” llama a POST /student/portfolio/generate-username, que produce un slug basado en el fullName del usuario (normalizado, sin acentos, con guiones). Si hay colisión, añade sufijo numérico (name2, name3, etc.).
API — endpoints
Section titled “API — endpoints”Portafolio público (sin autenticación)
Section titled “Portafolio público (sin autenticación)”/api/public/portfolio/:username Devuelve el portafolio completo del estudiante (solo si isPublic = true) /api/public/portfolio/:username/verify-all Verifica todos los certificados del portafolio y devuelve un informe de validez Configuración del estudiante (requiere JWT)
Section titled “Configuración del estudiante (requiere JWT)”/api/student/portfolio/settings Obtiene la configuración actual del portafolio del usuario autenticado /api/student/portfolio/settings Actualiza configuración: username, isPublic, bio, links, toggles de visibilidad /api/student/portfolio/check-username?username=:u Comprueba disponibilidad de un username; devuelve {available, suggestion?} /api/student/portfolio/generate-username Auto-genera un username a partir del nombre completo del usuario Modelo en base de datos
Section titled “Modelo en base de datos”model StudentProfile { id String @id @default(uuid()) userId String @unique user User @relation(...) username String @unique // 3-40 chars, [a-zA-Z0-9-] isPublic Boolean @default(false) bio String? @db.Text location String? websiteUrl String? twitterUrl String? githubUrl String? // Toggles de visibilidad showCertificates Boolean @default(true) showBadges Boolean @default(true) showPaths Boolean @default(true) showTimeline Boolean @default(true) showXP Boolean @default(true) showScore Boolean @default(true) usernameChangedAt DateTime? // cooldown 30 días para cambios viewCount Int @default(0)}Slice RTK Query
Section titled “Slice RTK Query”useGetPublicPortfolioQuery(username) // carga portafolio público (cache 120s)useVerifyAllMutation() // verifica todos los certificadosuseGetPortfolioSettingsQuery() // ajustes del usuario autenticadouseUpdatePortfolioSettingsMutation() // guarda ajustesuseCheckUsernameQuery(username) // comprueba disponibilidaduseGenerateUsernameMutation() // auto-genera username