workspace/projects/gamilit/docs/01-fase-alcance-inicial/EAI-003-gamificacion/requerimientos/RF-GAM-003-rangos-maya.md
rckrdmrd a249c99be2
Some checks failed
CI Pipeline / changes (push) Has been cancelled
CI Pipeline / core (push) Has been cancelled
CI Pipeline / trading-backend (push) Has been cancelled
CI Pipeline / trading-data-service (push) Has been cancelled
CI Pipeline / trading-frontend (push) Has been cancelled
CI Pipeline / erp-core (push) Has been cancelled
CI Pipeline / erp-mecanicas (push) Has been cancelled
CI Pipeline / gamilit-backend (push) Has been cancelled
CI Pipeline / gamilit-frontend (push) Has been cancelled
feat: Sincronizacion completa workspace 2025-12-26
## Backend
- fix(ranks): Reordenar rutas en RanksController para evitar conflictos 404
- feat(gamification): Agregar MayaRankEntity al modulo
- feat(ml-coins): Expandir funcionalidad del servicio
- feat(teacher): Mejoras en dashboard, mensajes y reportes
- feat(entities): Nuevas entidades admin, educational, progress, social

## Frontend
- feat(gamificationAPI): API completa para ranks con endpoints
- feat(RubricEvaluator): Nuevo componente para evaluacion docente
- refactor(admin): Mejoras en hooks y paginas
- refactor(teacher): Mejoras en paginas del portal

## Database
- fix(initialize_user_stats): Agregar is_current y achieved_at a user_ranks
- fix(notifications-policies): Corregir RLS con JOIN correcto
- feat(friendships): Agregar columna status con estados
- sync(seeds): Homologacion completa DEV <-> PROD

## Docs & Orchestration
- docs(api): Actualizar API-TEACHER-MODULE.md
- docs(frontend): COMPONENTES-INVENTARIO.md
- docs(database): VIEWS-INVENTARIO.md, VALIDACION-DDL-SEEDS
- Reportes de analisis y validacion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 17:53:38 -06:00

36 KiB

RF-GAM-003: Sistema de Rangos Maya

📋 Metadata

Campo Valor
ID RF-GAM-003
Módulo 02 - Gamificación
Título Sistema de Rangos Maya
Prioridad Alta
Estado Implementado
Versión 1.0
Fecha Creación 2025-11-07
Última Actualización 2025-11-07
Autor Database Team
Stakeholders Product Owner, UX Team, Backend Team, Frontend Team, Cultural Advisor

🔗 Referencias

Implementación DDL

🗄️ ENUM Canónico:

  • Ubicación: apps/database/ddl/schemas/gamification_system/enums/maya_rank.sql
  • Tipo: gamification_system.maya_rank
  • Valores: Ajaw, Nacom, Ah K'in, Halach Uinic, K'uk'ulkan

Nota cultural: Los nombres de rangos se escriben con ortografía moderna maya yucateca. Cada rango representa un nivel jerárquico histórico de la civilización maya.

🗄️ Tablas Relacionadas:

  1. gamification_system.user_stats

    • Ubicación: apps/database/ddl/schemas/gamification_system/tables/user_stats.sql
    • Columnas clave:
      • current_rank (ENUM maya_rank, DEFAULT 'Ajaw')
      • total_xp (INTEGER, para calcular promociones)
      • rank_achieved_at (TIMESTAMPTZ)
      • previous_rank (ENUM maya_rank, nullable)
  2. gamification_system.rank_history

    • Ubicación: apps/database/ddl/schemas/gamification_system/tables/rank_history.sql
    • Propósito: Historial completo de promociones de rango
    • Columnas clave:
      • user_id (UUID)
      • old_rank (ENUM maya_rank)
      • new_rank (ENUM maya_rank)
      • xp_at_promotion (INTEGER)
      • promoted_at (TIMESTAMPTZ)

🗄️ Funciones SQL:

  1. check_rank_promotion(user_id UUID)

    • Ubicación: apps/database/ddl/schemas/gamification_system/functions/check_rank_promotion.sql
    • Propósito: Verificar si usuario califica para promoción
    • Retorno: BOOLEAN (true si promovido)
  2. promote_to_next_rank(user_id UUID)

    • Ubicación: apps/database/ddl/schemas/gamification_system/functions/promote_to_next_rank.sql
    • Propósito: Promover usuario al siguiente rango
    • Side effects:
      • Actualizar current_rank en user_stats
      • Crear achievement rank_promotion
      • Registrar en rank_history
      • Enviar notificación rank_up
  3. get_rank_benefits(rank maya_rank)

    • Ubicación: apps/database/ddl/schemas/gamification_system/functions/get_rank_benefits.sql
    • Propósito: Obtener beneficios del rango
    • Retorno: JSONB con permisos y bonuses

🗄️ Trigger:

  • trg_check_rank_promotion_on_xp_gain
    • Ubicación: apps/database/ddl/schemas/gamification_system/triggers/trg_check_rank_promotion.sql
    • Tabla: gamification_system.user_stats
    • Evento: AFTER UPDATE OF total_xp
    • Acción: Llamar a check_rank_promotion(user_id)

Especificación Técnica

📘 Documento ET Relacionado:

Documentos Relacionados

ADRs


📖 Descripción General

Propósito

El Sistema de Rangos Maya es un sistema de progresión jerárquico que reconoce el avance de los estudiantes mediante títulos inspirados en la civilización maya. A medida que los usuarios acumulan XP (Experiencia), desbloquean rangos más prestigiosos que:

  • Dan identidad cultural y sentido de pertenencia
  • Reconocen visualmente el nivel de maestría
  • Desbloquean beneficios progresivos
  • Crean motivación a largo plazo

Contexto Cultural

Los rangos se basan en la jerarquía histórica de la civilización maya clásica (250-900 d.C.):

Rango Maya Rol Histórico Nivel en Gamilit
Ajaw Señor local, noble menor Iniciante
Nacom Capitán guerrero, líder militar Intermedio bajo
Ah K'in Sacerdote, guardián del conocimiento Intermedio alto
Halach Uinic Hombre verdadero, gobernante de ciudad Avanzado
K'uk'ulkan Serpiente emplumada, deidad del conocimiento Maestro

Consultoría Cultural: Nombres y significados validados con asesor cultural maya para asegurar respeto y precisión.

Alcance

Incluye:

  • 5 rangos progresivos basados en XP
  • Umbrales definidos de XP para cada rango
  • Promoción automática al alcanzar umbral
  • Badges visuales únicos por rango
  • Beneficios progresivos (permisos, bonuses)
  • Historial completo de promociones
  • Achievement especial al promover
  • Notificación celebrando promoción

Excluye:

  • Degradación de rango (solo ascenso)
  • Compra de rangos con ML Coins
  • Rangos especiales para eventos
  • Subniveles dentro de cada rango

⚙️ Requerimientos Funcionales

1. Jerarquía de Rangos

Rango 1: Ajaw (Señor) 🌱

Umbral: 0 - 499 XP Estado: Rango inicial (todos empiezan aquí)

Significado histórico:

"Ajaw" era un título nobiliario para señores locales. Representa el inicio del camino del conocimiento.

Características:

  • Rango predeterminado al crear cuenta
  • Sin requisitos previos
  • Acceso a funcionalidades básicas

Badge visual:

┌─────────┐
│   🌱    │  Semilla del conocimiento
│  AJAW   │
└─────────┘

Beneficios:

  • Acceso completo a ejercicios básicos
  • Puede unirse a aulas
  • Puede usar comodines básicos

Rango 2: Nacom (Capitán Guerrero) ⚔️

Umbral: 500 - 999 XP Requisito: Ganar 500 XP

Significado histórico:

"Nacom" era el capitán de guerra elegido por 3 años. Representa disciplina y consistencia en el aprendizaje.

Características:

  • Primer gran hito de progresión
  • Demuestra compromiso con la plataforma

Badge visual:

┌─────────┐
│   ⚔️    │  Guerrero del aprendizaje
│  NACOM  │
└─────────┘

Beneficios:

  • Desbloquea ejercicios de dificultad media-alta
  • +10% bonus en XP ganado por ejercicio
  • Puede crear equipos de estudio
  • Avatar frame especial "Nacom"

Recompensa de promoción:

  • Achievement: "Ascenso a Nacom"
  • 100 ML Coins bonus
  • Notificación especial con animación

Rango 3: Ah K'in (Sacerdote del Sol) ☀️

Umbral: 1,000 - 1,499 XP Requisito: Ganar 1,000 XP

Significado histórico:

"Ah K'in" era un sacerdote, guardián del calendario y del conocimiento astronómico. Representa sabiduría y dominio.

Características:

  • Rango intermedio-alto
  • Solo ~20% de usuarios activos lo alcanzan

Badge visual:

┌─────────┐
│   ☀️    │  Guardián del conocimiento
│ AH K'IN │
└─────────┘

Beneficios:

  • +15% bonus en XP ganado
  • Desbloquea ejercicios avanzados
  • Puede ser tutor de estudiantes Ajaw/Nacom
  • Acceso a "Biblioteca Avanzada" (contenido exclusivo)
  • Avatar frame "Ah K'in" con efectos dorados

Recompensa de promoción:

  • Achievement: "Ascenso a Ah K'in"
  • 250 ML Coins bonus
  • Título especial en perfil público
  • Certificado digital descargable

Rango 4: Halach Uinic (Hombre Verdadero) 👑

Umbral: 1,500 - 2,249 XP Requisito: Ganar 1,500 XP

Significado histórico:

"Halach Uinic" significa "Hombre Verdadero", título del gobernante supremo de una ciudad-estado maya. Representa liderazgo y maestría.

Características:

  • Rango avanzado, elite
  • Solo ~5% de usuarios activos lo alcanzan
  • Reconocimiento público en plataforma

Badge visual:

┌─────────────┐
│     👑      │  Gobernante del saber
│ HALACH UINIC│
└─────────────┘

Beneficios:

  • +20% bonus en XP ganado
  • Acceso a "Desafíos de Maestría" (contenido experto)
  • Puede crear aulas como maestro invitado
  • Aparece en "Hall of Fame" de la plataforma
  • Avatar frame animado "Halach Uinic"
  • Badge especial en foros y comentarios

Recompensa de promoción:

  • Achievement: "Ascenso a Halach Uinic"
  • 500 ML Coins bonus
  • Mención en newsletter mensual
  • Entrevista opcional para blog de Gamilit

Rango 5: K'uk'ulkan (Serpiente Emplumada) 🐉

Umbral: 1,900+ XP Requisito: Ganar 1,900 XP

Nota v2.1: Umbral ajustado de 2,250 a 1,900 XP para ser alcanzable completando Módulos 1-3 (~1,950 XP disponibles).

Significado histórico:

"K'uk'ulkan" (Kukulkán en español) es la deidad maya asociada con el conocimiento, el viento y el planeta Venus. Equivalente a Quetzalcóatl. Representa la máxima sabiduría y trascendencia.

Características:

  • Rango legendario
  • Solo ~1% de usuarios lo alcanzan
  • Máximo prestigio en la comunidad

Badge visual:

┌──────────────┐
│      🐉      │  Deidad del conocimiento
│  K'UK'ULKAN  │
└──────────────┘

Beneficios:

  • +25% bonus en XP ganado
  • Acceso a contenido exclusivo experimental (beta features)
  • Puede participar en creación de contenido (contribuir ejercicios)
  • Sesiones de mentoría 1:1 con equipo pedagógico
  • Avatar frame épico animado "K'uk'ulkan"
  • Badge legendario con efectos especiales
  • Nombre en placa de honor permanente
  • Acceso a comunidad privada de K'uk'ulkan

Recompensa de promoción:

  • Achievement: "Ascenso a K'uk'ulkan"
  • 1,000 ML Coins bonus
  • Certificado físico enviado por correo
  • Reconocimiento en redes sociales oficiales
  • Invitación a eventos presenciales de Gamilit

2. Umbrales de XP

Tabla de Umbrales

Rango Umbral XP Mínimo Umbral XP Máximo XP Requerido para Promover
Ajaw 0 499 500
Nacom 500 999 1,000
Ah K'in 1,000 1,499 1,500
Halach Uinic 1,500 1,899 1,900
K'uk'ulkan 1,900 - (rango final)

Nota v2.1: Umbrales actualizados según migración v2.1. K'uk'ulkan ahora alcanzable con M1-M3.

Progresión de Dificultad

Diseño intencional: Los umbrales crecen exponencialmente para:

  1. Rápida gratificación inicial (Ajaw → Nacom en ~50 ejercicios)
  2. Progresión sostenida (Nacom → Ah K'in requiere compromiso)
  3. Objetivo a largo plazo (K'uk'ulkan es aspiracional)

Tiempo estimado para alcanzar:

Rango Ejercicios (~20 XP/ej) Tiempo (estudiante activo 5 ej/día)
Nacom ~25 ejercicios 5 días
Ah K'in ~50 ejercicios 10 días
Halach Uinic ~75 ejercicios 15 días
K'uk'ulkan ~113 ejercicios 23 días (~3 semanas)

3. Mecánica de Promoción

Flujo de Promoción Automática

Usuario gana XP (completa ejercicio)
        ↓
    total_xp += xp_earned
        ↓
Trigger: trg_check_rank_promotion_on_xp_gain
        ↓
    ┌────────────────────────────────┐
    │ check_rank_promotion(user_id) │
    └────────────┬───────────────────┘
                 ↓
    ┌────────────────────────────────┐
    │ ¿total_xp >= próximo umbral?  │
    └────────────┬───────────────────┘
                 ↓ Sí
    ┌────────────────────────────────┐
    │ promote_to_next_rank(user_id) │
    │ - Actualizar current_rank      │
    │ - Crear achievement            │
    │ - Registrar en rank_history    │
    │ - Otorgar ML Coins bonus       │
    │ - Enviar notificación          │
    └────────────┬───────────────────┘
                 ↓
    ┌────────────────────────────────┐
    │ Frontend muestra modal celebra │
    │ "¡Ascendiste a Nacom!"         │
    └────────────────────────────────┘

Validaciones de Promoción

Validación Condición Acción
Umbral alcanzado total_xp >= next_rank_threshold Promover
Ya en rango máximo current_rank = 'K'uk'ulkan' No promover, es final
Cuenta activa profile.status = 'active' Solo cuentas activas
Sin promoción duplicada Verificar último registro en rank_history Evitar double promotion

4. Beneficios por Rango

4.1. Bonus de XP

Cada rango otorga un multiplicador de XP permanente:

Rango Multiplicador XP Ejemplo
Ajaw 1.00x 20 XP → 20 XP
Nacom 1.10x (+10%) 20 XP → 22 XP
Ah K'in 1.15x (+15%) 20 XP → 23 XP
Halach Uinic 1.20x (+20%) 20 XP → 24 XP
K'uk'ulkan 1.25x (+25%) 20 XP → 25 XP

Implementación:

-- Al completar ejercicio
xp_to_award = base_xp * rank_multiplier;

4.2. Desbloqueo de Contenido

Contenido Rango Requerido
Ejercicios básicos (easy) Ajaw
Ejercicios medios (medium) Ajaw
Ejercicios difíciles (hard) Nacom
Ejercicios expertos (expert) Ah K'in
Desafíos de Maestría Halach Uinic
Contenido experimental K'uk'ulkan

4.3. Funciones Sociales

Función Rango Requerido
Unirse a aulas Ajaw
Crear equipos de estudio Nacom
Ser tutor de otros Ah K'in
Crear aulas como maestro invitado Halach Uinic
Contribuir ejercicios a plataforma K'uk'ulkan

4.4. Beneficios Visuales

Benefit Ajaw Nacom Ah K'in Halach Uinic K'uk'ulkan
Badge básico
Avatar frame -
Frame con efectos - - (dorado) (animado) (épico)
Badge en comentarios - - -
Efectos especiales - - - -

5. Historial de Rangos

Tabla: rank_history

Cada promoción se registra permanentemente:

CREATE TABLE gamification_system.rank_history (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES auth.users(id),
    old_rank gamification_system.maya_rank NOT NULL,
    new_rank gamification_system.maya_rank NOT NULL,
    xp_at_promotion INTEGER NOT NULL,
    days_in_old_rank INTEGER, -- Cuántos días estuvo en rango anterior
    promoted_at TIMESTAMPTZ DEFAULT NOW(),
    achievement_id UUID REFERENCES gamification_system.user_achievements(id)
);

Propósito:

  • Tracking completo de progresión
  • Análisis de retención por rango
  • Verificación de integridad
  • Visualización de timeline personal

💼 Casos de Uso

CU-GAM-003-001: Promoción Automática a Nacom

Actor: Sistema (automático) Trigger: Usuario alcanza 1,000 XP

Precondiciones:

  • Usuario tiene current_rank = 'Ajaw'
  • total_xp >= 1000
  • Cuenta activa

Flujo Principal:

  1. Usuario completa un ejercicio que le da 15 XP
  2. Sistema actualiza user_stats.total_xp: 990 → 1005
  3. Trigger trg_check_rank_promotion_on_xp_gain se dispara
  4. Función check_rank_promotion(user_id) evalúa:
    IF total_xp >= 1000 AND current_rank = 'Ajaw' THEN
        RETURN promote_to_next_rank(user_id);
    END IF;
    
  5. Función promote_to_next_rank(user_id) ejecuta:
    • Actualiza current_rank de 'Ajaw' a 'Nacom'
    • Crea achievement rank_promotion con metadata:
      {
        "old_rank": "Ajaw",
        "new_rank": "Nacom",
        "xp": 1005
      }
      
    • Otorga 50 ML Coins bonus
    • Registra en rank_history
    • Crea notificación tipo rank_up:
      {
        "type": "rank_up",
        "title": "¡Ascendiste a Nacom!",
        "body": "Eres ahora un Capitán Guerrero del conocimiento. +5% XP bonus desbloqueado.",
        "data": {
          "new_rank": "Nacom",
          "bonus_coins": 50
        }
      }
      
  6. Frontend detecta notificación y muestra modal celebratorio:
    ┌────────────────────────────────────┐
    │          ⚔️ ¡PROMOCIÓN! ⚔️         │
    │                                    │
    │     Has ascendido a Nacom          │
    │     Capitán Guerrero               │
    │                                    │
    │  🎁 +50 ML Coins                   │
    │  ⚡ +5% Bonus XP                   │
    │  🏅 Achievement desbloqueado       │
    │                                    │
    │          [Continuar]               │
    └────────────────────────────────────┘
    

Postcondiciones:

  • user_stats.current_rank = 'Nacom'
  • user_stats.ml_coins += 50
  • rank_history contiene nuevo registro
  • user_achievements contiene achievement rank_promotion
  • Usuario recibe notificación

CU-GAM-003-002: Ver Progreso Hacia Próximo Rango

Actor: Estudiante Precondiciones:

  • Usuario autenticado
  • No está en rango máximo (K'uk'ulkan)

Flujo Principal:

  1. Usuario navega a "Mi Perfil" o "Progreso"
  2. Sistema muestra componente RankProgressBar:
    ┌────────────────────────────────────────────┐
    │  ⚔️ NACOM                    🎯 Ah K'in   │
    │  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
    │  ████████████░░░░░░░░░░░░░░░░░░░░░░ 38%   │
    │                                            │
    │  3,540 / 5,000 XP                          │
    │  1,460 XP para Ah K'in                     │
    └────────────────────────────────────────────┘
    
  3. Usuario puede hacer click para ver detalles:
    • XP total actual
    • XP requerido para próximo rango
    • Estimado de ejercicios restantes
    • Beneficios que desbloqueará

Postcondiciones:

  • Ninguna (solo lectura)

CU-GAM-003-003: Ver Historial de Promociones

Actor: Estudiante Precondiciones:

  • Usuario autenticado
  • Ha sido promovido al menos una vez

Flujo Principal:

  1. Usuario navega a "Mi Perfil" → "Historial de Rangos"
  2. Sistema muestra timeline de promociones:
    ┌────────────────────────────────────────────┐
    │  📜 Tu Camino Maya                         │
    │                                            │
    │  🐉 K'uk'ulkan (actual)                    │
    │  └─ 2025-10-15 • 102,450 XP               │
    │                                            │
    │  👑 Halach Uinic                           │
    │  └─ 2025-07-20 • 21,300 XP                │
    │     180 días en este rango                 │
    │                                            │
    │  ☀️ Ah K'in                                │
    │  └─ 2025-05-01 • 6,200 XP                 │
    │     80 días en este rango                  │
    │                                            │
    │  ⚔️ Nacom                                  │
    │  └─ 2025-03-15 • 1,050 XP                 │
    │     47 días en este rango                  │
    │                                            │
    │  🌱 Ajaw (inicio)                          │
    │  └─ 2025-01-27 • 0 XP                     │
    │     47 días en este rango                  │
    └────────────────────────────────────────────┘
    
  3. Usuario puede hacer click en cada rango para ver:
    • Fecha exacta de promoción
    • XP al momento de promover
    • Tiempo en rango anterior
    • Achievement asociado

Postcondiciones:

  • Ninguna (solo lectura)

CU-GAM-003-004: Desbloquear Contenido con Rango

Actor: Estudiante Precondiciones:

  • Usuario autenticado

Flujo Principal:

  1. Usuario (rango Ajaw) navega a "Ejercicios"
  2. Sistema muestra lista de ejercicios:
    ✅ Conjugación Básica (easy) - Disponible
    ✅ Números Mayas (medium) - Disponible
    🔒 Subjuntivo Avanzado (hard) - Requiere Nacom
    🔒 Literatura Maya (expert) - Requiere Ah K'in
    
  3. Usuario hace click en ejercicio bloqueado
  4. Sistema muestra modal:
    ┌────────────────────────────────────────────┐
    │  🔒 Ejercicio Bloqueado                    │
    │                                            │
    │  Este ejercicio requiere rango Nacom      │
    │  o superior.                               │
    │                                            │
    │  Tu progreso actual:                       │
    │  ━━━━━━░░░░░░░░░░░░░░░░ 25%               │
    │  250 / 1,000 XP para Nacom                 │
    │                                            │
    │  Completa 38 ejercicios más para           │
    │  desbloquear este contenido.               │
    │                                            │
    │  [Ver Ejercicios Disponibles]              │
    └────────────────────────────────────────────┘
    

Flujo Alternativo 3a: Usuario alcanza Nacom

  • Usuario completa ejercicios y alcanza 1,000 XP
  • Promoción a Nacom ocurre
  • Usuario regresa a lista de ejercicios
  • Ejercicio "Subjuntivo Avanzado" ahora está disponible

Postcondiciones:

  • Usuario entiende requisitos de rango
  • Motivación para ganar más XP

CU-GAM-003-005: K'uk'ulkan Contribuye Ejercicio

Actor: Estudiante (rango K'uk'ulkan) Precondiciones:

  • Usuario tiene current_rank = 'K'uk'ulkan'
  • Cuenta verificada

Flujo Principal:

  1. Usuario K'uk'ulkan ve opción "Contribuir Ejercicio" en menú (solo visible para K'uk'ulkan)
  2. Usuario hace click y abre formulario de contribución
  3. Usuario completa formulario:
    • Mecánica del ejercicio
    • Texto/pregunta
    • Respuestas correctas
    • Explicación
    • Dificultad sugerida
  4. Usuario envía contribución
  5. Sistema crea ticket de revisión para equipo pedagógico
  6. Si aprobado:
    • Ejercicio se publica en plataforma
    • Usuario recibe 500 ML Coins bonus
    • Usuario recibe achievement especial "Creador de Contenido"
    • Nombre del usuario aparece como "Contribuido por [nombre]"

Postcondiciones:

  • Nuevo ejercicio en revisión
  • Usuario K'uk'ulkan se siente valorado
  • Comunidad se beneficia

🔒 Consideraciones de Seguridad

1. Prevención de Manipulación

Riesgo Mitigación
XP farming (ganar XP artificialmente) Rate limiting, detección de patrones sospechosos
Manipulación de total_xp Campo protegido, solo modificable por funciones SQL
Bypass de requisitos de rango Validaciones en backend y DB con RLS
Promoción manual indebida Función promote_to_next_rank solo llamable por trigger automático

2. Validaciones en Código

// ❌ NUNCA confiar en current_rank del frontend
// ✅ SIEMPRE consultar desde DB

async checkContentAccess(userId: string, contentId: string) {
  // 1. Obtener rango REAL desde DB
  const userStats = await this.db.query(
    'SELECT current_rank FROM gamification_system.user_stats WHERE user_id = $1',
    [userId]
  );

  // 2. Obtener requisito de contenido
  const content = await this.contentService.findOne(contentId);

  // 3. Validar que rango actual >= rango requerido
  const rankOrder = ['Ajaw', 'Nacom', 'Ah K\'in', 'Halach Uinic', 'K\'uk\'ulkan'];
  const userRankIndex = rankOrder.indexOf(userStats.current_rank);
  const requiredRankIndex = rankOrder.indexOf(content.required_rank);

  if (userRankIndex < requiredRankIndex) {
    throw new ForbiddenException(
      `Content requires rank ${content.required_rank} or higher`
    );
  }

  return true;
}

3. Auditoría

Todas las promociones se registran en audit_logging.audit_logs:

INSERT INTO audit_logging.audit_logs (
    user_id,
    action,
    resource_type,
    resource_id,
    details,
    severity
) VALUES (
    user_id,
    'rank_promotion',
    'rank',
    new_rank::TEXT,
    jsonb_build_object(
        'old_rank', old_rank,
        'new_rank', new_rank,
        'xp_at_promotion', total_xp,
        'days_in_old_rank', days_in_old_rank
    ),
    'info'
);

Criterios de Aceptación

CA-GAM-003-001: Promoción Automática

  • Usuario se promueve automáticamente al alcanzar umbral de XP
  • Promoción ocurre inmediatamente después de ganar XP suficiente
  • Solo se puede promover un rango a la vez (no saltar rangos)
  • Achievement rank_promotion se crea correctamente
  • ML Coins bonus se otorgan según rango alcanzado
  • Notificación rank_up se envía
  • Registro en rank_history es correcto

CA-GAM-003-002: Umbrales de XP (v2.1)

  • Ajaw: 0-499 XP
  • Nacom: 500-999 XP
  • Ah K'in: 1,000-1,499 XP
  • Halach Uinic: 1,500-1,899 XP
  • K'uk'ulkan: 1,900+ XP
  • Usuario en K'uk'ulkan no puede promover más (es final)

Nota: Umbrales actualizados en migración v2.1 para ser alcanzables.

CA-GAM-003-003: Multiplicador XP por Rango (v2.1)

  • Ajaw: 1.00x (sin bonus)
  • Nacom: 1.10x (+10%)
  • Ah K'in: 1.15x (+15%)
  • Halach Uinic: 1.20x (+20%)
  • K'uk'ulkan: 1.25x (+25%)
  • Multiplicador se aplica correctamente en cada ejercicio completado

CA-GAM-003-004: Desbloqueo de Contenido

  • Ejercicios hard requieren Nacom o superior
  • Ejercicios expert requieren Ah K'in o superior
  • Desafíos de Maestría requieren Halach Uinic
  • Contenido experimental requiere K'uk'ulkan
  • Validación en backend previene bypass

CA-GAM-003-005: Visualización

  • Badge de rango se muestra en perfil
  • RankProgressBar muestra progreso hacia próximo rango
  • Historial de promociones muestra timeline completo
  • Modal de promoción celebra correctamente
  • Avatar frames se desbloquean según rango

CA-GAM-003-006: Historial

  • Cada promoción se registra en rank_history
  • Historial incluye fecha, XP, días en rango anterior
  • Historial es inmutable (no se puede editar)

🧪 Testing

Test Case 1: Promoción de Ajaw a Nacom

test('User promotes from Ajaw to Nacom at 1000 XP', async () => {
  // Arrange
  const user = await createUser({
    current_rank: 'Ajaw',
    total_xp: 990,
  });

  // Act - Completar ejercicio que da 15 XP
  await completeExercise(user.id, { base_xp: 15 });

  // Assert
  const updatedUser = await getUser(user.id);
  expect(updatedUser.current_rank).toBe('Nacom');
  expect(updatedUser.total_xp).toBe(1005); // 990 + 15

  // Verificar achievement
  const achievements = await getUserAchievements(user.id);
  const rankAchievement = achievements.find(
    (a) => a.achievement_type === 'rank_promotion'
  );
  expect(rankAchievement).toBeDefined();
  expect(rankAchievement.metadata.new_rank).toBe('Nacom');

  // Verificar ML Coins bonus
  expect(updatedUser.ml_coins).toBeGreaterThanOrEqual(50);

  // Verificar historial
  const history = await getRankHistory(user.id);
  expect(history).toHaveLength(1);
  expect(history[0].old_rank).toBe('Ajaw');
  expect(history[0].new_rank).toBe('Nacom');
});

Test Case 2: No Promociona si No Alcanza Umbral

test('User does not promote if below threshold', async () => {
  // Arrange
  const user = await createUser({
    current_rank: 'Ajaw',
    total_xp: 950,
  });

  // Act - Completar ejercicio que da 30 XP (total: 980)
  await completeExercise(user.id, { base_xp: 30 });

  // Assert
  const updatedUser = await getUser(user.id);
  expect(updatedUser.current_rank).toBe('Ajaw'); // Sigue en Ajaw
  expect(updatedUser.total_xp).toBe(980); // No alcanzó 1000

  // No hay achievement de promoción
  const achievements = await getUserAchievements(user.id);
  const rankAchievement = achievements.find(
    (a) => a.achievement_type === 'rank_promotion'
  );
  expect(rankAchievement).toBeUndefined();
});

Test Case 3: Bonus de XP por Rango

test('Nacom receives +5% XP bonus', async () => {
  // Arrange
  const user = await createUser({
    current_rank: 'Nacom',
    total_xp: 2000,
  });

  const exercise = await createExercise({ base_xp: 20 });

  // Act
  const result = await completeExercise(user.id, exercise.id);

  // Assert
  expect(result.xp_earned).toBe(21); // 20 * 1.05 = 21

  const updatedUser = await getUser(user.id);
  expect(updatedUser.total_xp).toBe(2021); // 2000 + 21
});

Test Case 4: K'uk'ulkan No Promociona Más Allá

test('K\'uk\'ulkan cannot promote further (max rank)', async () => {
  // Arrange
  const user = await createUser({
    current_rank: 'K\'uk\'ulkan',
    total_xp: 150000,
  });

  // Act - Ganar 10,000 XP más
  await giveXP(user.id, 10000);

  // Assert
  const updatedUser = await getUser(user.id);
  expect(updatedUser.current_rank).toBe('K\'uk\'ulkan'); // Sigue en K'uk'ulkan
  expect(updatedUser.total_xp).toBe(160000); // XP sigue acumulando

  // No hay nueva promoción en historial
  const history = await getRankHistory(user.id);
  const latestPromotion = history[history.length - 1];
  expect(latestPromotion.new_rank).not.toBe('beyond_kukulkan'); // No existe rango superior
});

Test Case 5: Restricción de Contenido por Rango

test('Ajaw cannot access hard exercise (requires Nacom)', async () => {
  // Arrange
  const user = await createUser({ current_rank: 'Ajaw' });
  const hardExercise = await createExercise({
    difficulty: 'hard',
    required_rank: 'Nacom',
  });

  // Act & Assert
  await expect(accessExercise(user.id, hardExercise.id)).rejects.toThrow(
    'Content requires rank Nacom or higher'
  );
});

test('Nacom can access hard exercise', async () => {
  // Arrange
  const user = await createUser({ current_rank: 'Nacom' });
  const hardExercise = await createExercise({
    difficulty: 'hard',
    required_rank: 'Nacom',
  });

  // Act
  const result = await accessExercise(user.id, hardExercise.id);

  // Assert
  expect(result.allowed).toBe(true);
});

Test Case 6: Promoción Múltiple en Secuencia

test('User promotes through multiple ranks correctly', async () => {
  // Arrange
  const user = await createUser({
    current_rank: 'Ajaw',
    total_xp: 0,
  });

  // Act - Dar suficiente XP para Ah K'in (5,000 XP)
  await giveXP(user.id, 5500);

  // Assert - Debería estar en Ah K'in, pasando por Nacom
  const updatedUser = await getUser(user.id);
  expect(updatedUser.current_rank).toBe('Ah K\'in');

  // Verificar que pasó por Nacom (historial tiene 2 entradas)
  const history = await getRankHistory(user.id);
  expect(history).toHaveLength(2);

  expect(history[0].old_rank).toBe('Ajaw');
  expect(history[0].new_rank).toBe('Nacom');

  expect(history[1].old_rank).toBe('Nacom');
  expect(history[1].new_rank).toBe('Ah K\'in');
});

📊 Métricas y Análisis

KPIs a Monitorear

Métrica Cálculo Objetivo
Distribución de rangos COUNT(*) GROUP BY current_rank Pirámide: 50% Ajaw, 30% Nacom, 15% Ah K'in, 4% Halach, 1% K'uk'ulkan
Tiempo promedio por rango AVG(days_in_old_rank) GROUP BY old_rank Ajaw: 30-45 días, Nacom: 60-90 días
Tasa de progresión usuarios que promovieron / total usuarios >60% alcanzan Nacom, >20% Ah K'in
Retención por rango DAU/MAU por rango K'uk'ulkan: >80%, Ajaw: ~40%
XP promedio al promover Detectar si usuarios esperan más del umbral Cerca de umbral = bien balanceado

Dashboards para Product Team

Dashboard 1: Distribución de Rangos

SELECT
    current_rank,
    COUNT(*) as user_count,
    ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) as percentage
FROM gamification_system.user_stats
WHERE user_id IN (SELECT id FROM auth.users WHERE status = 'active')
GROUP BY current_rank
ORDER BY
    CASE current_rank
        WHEN 'Ajaw' THEN 1
        WHEN 'Nacom' THEN 2
        WHEN 'Ah K''in' THEN 3
        WHEN 'Halach Uinic' THEN 4
        WHEN 'K''uk''ulkan' THEN 5
    END;

Dashboard 2: Tiempo Promedio por Rango

SELECT
    old_rank,
    AVG(days_in_old_rank) as avg_days,
    MIN(days_in_old_rank) as min_days,
    MAX(days_in_old_rank) as max_days
FROM gamification_system.rank_history
GROUP BY old_rank
ORDER BY
    CASE old_rank
        WHEN 'Ajaw' THEN 1
        WHEN 'Nacom' THEN 2
        WHEN 'Ah K''in' THEN 3
        WHEN 'Halach Uinic' THEN 4
    END;

🔗 Referencias Adicionales

Documentación Relacionada

ADRs

Consultoría Cultural

  • Documento: "Validación de Nombres y Significados Maya"
  • Asesor: [Nombre del consultor cultural]
  • Fecha: 2025-10-01

Referencias Históricas

  • Thompson, J. E. S. (1954). "The Rise and Fall of Maya Civilization"
  • Sharer, R. J. (2006). "The Ancient Maya" (6th Edition)
  • Coe, M. D. (2011). "The Maya" (8th Edition)

Inspiración de Juegos

  • World of Warcraft: Sistema de niveles épicos
  • League of Legends: Ranked tiers (Bronze, Silver, Gold...)
  • Duolingo: Leagues (Bronze, Silver, Gold, Platinum...)

Diferenciador de Gamilit: Uso de jerarquía maya auténtica en lugar de metales genéricos.


📅 Historial de Cambios

Versión Fecha Autor Cambios
1.0 2025-11-07 Database Team Creación del documento

Documento: docs/01-requerimientos/02-gamificacion/RF-GAM-003-rangos-maya.md Propósito: Requerimientos funcionales del sistema de rangos Maya Audiencia: Product Owner, Developers, QA Team, Cultural Advisor