trading-platform/docs/02-definicion-modulos/OQI-007-llm-agent/especificaciones/ET-LLM-004-integracion-educacion.md
rckrdmrd c1b5081208 feat(ml): Complete FASE 11 - BTCUSD update and comprehensive documentation alignment
ML Engine Updates:
- Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records
- Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence)
- Backtest results: +176.71R profit with aggressive_filter strategy

Documentation Consolidation:
- Created docs/99-analisis/_MAP.md index with 13 new analysis documents
- Consolidated inventories: removed duplicates from orchestration/inventarios/
- Updated ML_INVENTORY.yml with BTCUSD metrics and training results
- Added execution reports: FASE11-BTCUSD, correction issues, alignment validation

Architecture & Integration:
- Updated all module documentation with NEXUS v3.4 frontmatter
- Fixed _MAP.md indexes across all folders
- Updated orchestration plans and traces

Files: 229 changed, 5064 insertions(+), 1872 deletions(-)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 09:31:29 -06:00

23 KiB

id title type status priority epic project version created_date updated_date
ET-LLM-004 Integración con Módulo Educativo Technical Specification Done Alta OQI-007 trading-platform 1.0.0 2025-12-05 2026-01-04

ET-LLM-004: Integración con Módulo Educativo

Épica: OQI-007 - LLM Strategy Agent Versión: 1.0 Fecha: 2025-12-05 Estado: Planificado Prioridad: P1 - Alto


Resumen

Esta especificación define cómo el agente LLM se integra con el módulo educativo (OQI-002) para proporcionar asistencia personalizada, explicaciones contextuales y recomendaciones de aprendizaje.


Arquitectura de Integración

┌─────────────────────────────────────────────────────────────────────────┐
│                     EDUCATION INTEGRATION LAYER                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌────────────────────┐     ┌────────────────────┐                      │
│  │   OQI-002          │     │   OQI-007          │                      │
│  │   Education Module │     │   LLM Agent        │                      │
│  │                    │     │                    │                      │
│  │  ┌──────────────┐  │     │  ┌──────────────┐  │                      │
│  │  │   Courses    │◄─┼─────┼─►│  Education   │  │                      │
│  │  │   Service    │  │     │  │  Assistant   │  │                      │
│  │  └──────────────┘  │     │  │  Agent       │  │                      │
│  │                    │     │  └──────────────┘  │                      │
│  │  ┌──────────────┐  │     │         │         │                      │
│  │  │   Progress   │◄─┼─────┼─────────┘         │                      │
│  │  │   Tracker    │  │     │                    │                      │
│  │  └──────────────┘  │     │  ┌──────────────┐  │                      │
│  │                    │     │  │   Learning   │  │                      │
│  │  ┌──────────────┐  │     │  │  Recommender │  │                      │
│  │  │   Content    │◄─┼─────┼─►│              │  │                      │
│  │  │   Library    │  │     │  └──────────────┘  │                      │
│  │  └──────────────┘  │     │                    │                      │
│  └────────────────────┘     └────────────────────┘                      │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Componentes

1. Education Context Builder

// src/modules/copilot/education/context-builder.ts

interface EducationContext {
  currentCourse: CourseInfo | null;
  currentLesson: LessonInfo | null;
  overallProgress: ProgressSummary;
  recentQuizzes: QuizResult[];
  strugglingTopics: Topic[];
  masteredTopics: Topic[];
  suggestedNextSteps: Suggestion[];
}

interface CourseInfo {
  id: string;
  title: string;
  level: 'beginner' | 'intermediate' | 'advanced';
  progress: number;
  currentModule: string;
}

interface LessonInfo {
  id: string;
  title: string;
  type: 'video' | 'reading' | 'interactive';
  concepts: string[];
  duration: number;
  completed: boolean;
}

@Injectable()
export class EducationContextBuilder {
  constructor(
    private readonly courseService: CourseService,
    private readonly progressService: ProgressService,
  ) {}

  async build(userId: string): Promise<EducationContext> {
    const [
      enrollments,
      progress,
      quizResults,
      analytics,
    ] = await Promise.all([
      this.courseService.getUserEnrollments(userId),
      this.progressService.getUserProgress(userId),
      this.courseService.getRecentQuizzes(userId, 10),
      this.progressService.getLearningAnalytics(userId),
    ]);

    // Find current course (most recently accessed)
    const currentEnrollment = enrollments
      .filter(e => e.status === 'in_progress')
      .sort((a, b) => b.lastAccessedAt.getTime() - a.lastAccessedAt.getTime())[0];

    let currentCourse: CourseInfo | null = null;
    let currentLesson: LessonInfo | null = null;

    if (currentEnrollment) {
      const course = await this.courseService.getCourse(currentEnrollment.courseId);
      const lesson = await this.courseService.getCurrentLesson(
        userId,
        currentEnrollment.courseId,
      );

      currentCourse = {
        id: course.id,
        title: course.title,
        level: course.level,
        progress: currentEnrollment.progress,
        currentModule: lesson?.moduleName || 'N/A',
      };

      if (lesson) {
        currentLesson = {
          id: lesson.id,
          title: lesson.title,
          type: lesson.type,
          concepts: lesson.concepts,
          duration: lesson.duration,
          completed: lesson.completed,
        };
      }
    }

    // Analyze struggling topics
    const strugglingTopics = this.identifyStrugglingTopics(quizResults, analytics);
    const masteredTopics = this.identifyMasteredTopics(quizResults, analytics);

    // Generate suggestions
    const suggestedNextSteps = await this.generateSuggestions(
      userId,
      strugglingTopics,
      progress,
    );

    return {
      currentCourse,
      currentLesson,
      overallProgress: progress.summary,
      recentQuizzes: quizResults.slice(0, 5),
      strugglingTopics,
      masteredTopics,
      suggestedNextSteps,
    };
  }

  private identifyStrugglingTopics(
    quizResults: QuizResult[],
    analytics: LearningAnalytics,
  ): Topic[] {
    const topicScores = new Map<string, number[]>();

    // Aggregate quiz scores by topic
    for (const result of quizResults) {
      for (const topic of result.topics) {
        const scores = topicScores.get(topic) || [];
        scores.push(result.topicScores[topic] || result.score);
        topicScores.set(topic, scores);
      }
    }

    // Identify topics with consistently low scores
    const struggling: Topic[] = [];
    for (const [topic, scores] of topicScores) {
      const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
      if (avgScore < 70 && scores.length >= 2) {
        struggling.push({
          name: topic,
          averageScore: avgScore,
          attempts: scores.length,
          lastAttempt: this.findLastAttemptDate(quizResults, topic),
        });
      }
    }

    return struggling.sort((a, b) => a.averageScore - b.averageScore);
  }

  private async generateSuggestions(
    userId: string,
    strugglingTopics: Topic[],
    progress: UserProgress,
  ): Promise<Suggestion[]> {
    const suggestions: Suggestion[] = [];

    // Suggest review for struggling topics
    for (const topic of strugglingTopics.slice(0, 3)) {
      const relatedLesson = await this.courseService.findLessonByTopic(topic.name);
      if (relatedLesson) {
        suggestions.push({
          type: 'review',
          priority: 'high',
          title: `Revisar: ${topic.name}`,
          description: `Tu puntuación promedio es ${topic.averageScore}%. Te recomiendo repasar este tema.`,
          action: {
            type: 'goto_lesson',
            lessonId: relatedLesson.id,
            lessonTitle: relatedLesson.title,
          },
        });
      }
    }

    // Suggest next lesson if current course in progress
    const nextLesson = await this.progressService.getNextLesson(userId);
    if (nextLesson) {
      suggestions.push({
        type: 'continue',
        priority: 'medium',
        title: `Continuar: ${nextLesson.title}`,
        description: `Estás en el ${progress.summary.currentCourseProgress}% del curso.`,
        action: {
          type: 'goto_lesson',
          lessonId: nextLesson.id,
          lessonTitle: nextLesson.title,
        },
      });
    }

    // Suggest practice if no recent activity
    if (progress.summary.daysSinceLastActivity > 3) {
      suggestions.push({
        type: 'practice',
        priority: 'medium',
        title: 'Práctica recomendada',
        description: 'Han pasado algunos días. ¿Qué tal un poco de práctica?',
        action: {
          type: 'start_practice',
          topics: strugglingTopics.map(t => t.name),
        },
      });
    }

    return suggestions;
  }
}

2. Education Assistant Agent

// src/modules/copilot/education/assistant-agent.ts

const EDUCATION_SYSTEM_PROMPT = `
# Education Assistant - Trading Platform Academy

## Tu Rol
Eres un tutor personalizado de trading, especializado en ayudar a los usuarios
a aprender conceptos de trading e inversión. Tu objetivo es hacer que el aprendizaje
sea accesible, interesante y aplicable al mundo real.

## Contexto del Usuario
{user_education_context}

## Principios de Enseñanza

1. **Adapta al nivel:** Ajusta la complejidad de tus explicaciones al nivel del usuario
2. **Usa ejemplos reales:** Conecta conceptos con situaciones de mercado actuales
3. **Fomenta la práctica:** Sugiere ejercicios en paper trading
4. **Sé paciente:** Si el usuario no entiende, intenta explicar de forma diferente
5. **Celebra el progreso:** Reconoce los logros del usuario

## Reglas Importantes

1. NUNCA des respuestas directas a quizzes o evaluaciones
2. Si detectas confusión persistente, sugiere revisar lecciones específicas
3. Siempre relaciona la teoría con la práctica
4. No hagas promesas de rentabilidad
5. Usa analogías simples para conceptos complejos
6. Si no conoces un tema específico del curso, admítelo

## Herramientas Disponibles

- get_lesson_content(lessonId): Obtener contenido de una lección
- get_user_progress(): Ver progreso del usuario
- find_related_lessons(topic): Buscar lecciones relacionadas a un tema
- get_practice_questions(topic): Obtener preguntas de práctica
- get_market_example(concept): Buscar ejemplo real en mercado actual

## Formato de Respuesta para Explicaciones

### Explicación de Concepto
1. Definición simple (1-2 oraciones)
2. Analogía o metáfora
3. Ejemplo con mercado real
4. Cómo aplicarlo en trading
5. Recurso adicional (lección relacionada)

### Asistencia en Quiz (sin dar respuestas)
1. Clarificar qué se pregunta
2. Recordar conceptos relevantes
3. Dar pista indirecta
4. Sugerir revisar sección específica
`;

@Injectable()
export class EducationAssistantAgent {
  constructor(
    private readonly llmProvider: LLMProviderService,
    private readonly contextBuilder: EducationContextBuilder,
    private readonly lessonService: LessonService,
    private readonly marketDataService: MarketDataService,
  ) {}

  async processEducationQuery(
    userId: string,
    query: string,
    conversationHistory: Message[],
  ): Promise<AsyncGenerator<StreamChunk>> {
    // Build education context
    const educationContext = await this.contextBuilder.build(userId);

    // Format context for system prompt
    const formattedContext = this.formatEducationContext(educationContext);

    // Detect query intent
    const intent = await this.detectIntent(query);

    // Build tools based on intent
    const tools = this.getToolsForIntent(intent);

    // Create completion
    const systemPrompt = EDUCATION_SYSTEM_PROMPT.replace(
      '{user_education_context}',
      formattedContext,
    );

    return this.llmProvider.createChatCompletion({
      model: 'gpt-4o',
      messages: [
        { role: 'system', content: systemPrompt },
        ...conversationHistory,
        { role: 'user', content: query },
      ],
      tools,
      stream: true,
    });
  }

  private formatEducationContext(context: EducationContext): string {
    let formatted = '';

    if (context.currentCourse) {
      formatted += `## Curso Actual\n`;
      formatted += `- **Curso:** ${context.currentCourse.title}\n`;
      formatted += `- **Nivel:** ${context.currentCourse.level}\n`;
      formatted += `- **Progreso:** ${context.currentCourse.progress}%\n`;
      formatted += `- **Módulo actual:** ${context.currentCourse.currentModule}\n\n`;
    }

    if (context.currentLesson) {
      formatted += `## Lección Actual\n`;
      formatted += `- **Lección:** ${context.currentLesson.title}\n`;
      formatted += `- **Conceptos:** ${context.currentLesson.concepts.join(', ')}\n`;
      formatted += `- **Estado:** ${context.currentLesson.completed ? 'Completada' : 'En progreso'}\n\n`;
    }

    if (context.strugglingTopics.length > 0) {
      formatted += `## Temas con Dificultad\n`;
      for (const topic of context.strugglingTopics) {
        formatted += `- ${topic.name} (promedio: ${topic.averageScore}%)\n`;
      }
      formatted += '\n';
    }

    if (context.masteredTopics.length > 0) {
      formatted += `## Temas Dominados\n`;
      formatted += context.masteredTopics.map(t => t.name).join(', ') + '\n\n';
    }

    if (context.suggestedNextSteps.length > 0) {
      formatted += `## Sugerencias de Siguiente Paso\n`;
      for (const suggestion of context.suggestedNextSteps) {
        formatted += `- [${suggestion.priority}] ${suggestion.title}\n`;
      }
    }

    return formatted;
  }

  private async detectIntent(query: string): Promise<EducationIntent> {
    // Simple keyword-based intent detection
    const lowerQuery = query.toLowerCase();

    if (lowerQuery.includes('quiz') || lowerQuery.includes('examen') ||
        lowerQuery.includes('respuesta')) {
      return 'quiz_help';
    }

    if (lowerQuery.includes('qué es') || lowerQuery.includes('explica') ||
        lowerQuery.includes('cómo funciona')) {
      return 'concept_explanation';
    }

    if (lowerQuery.includes('ejemplo') || lowerQuery.includes('práctica')) {
      return 'practical_example';
    }

    if (lowerQuery.includes('siguiente') || lowerQuery.includes('qué debo')) {
      return 'learning_path';
    }

    return 'general_help';
  }

  private getToolsForIntent(intent: EducationIntent): Tool[] {
    const baseTools = [
      {
        type: 'function',
        function: {
          name: 'get_lesson_content',
          description: 'Obtiene el contenido de una lección específica',
          parameters: {
            type: 'object',
            properties: {
              lessonId: { type: 'string' },
            },
            required: ['lessonId'],
          },
        },
      },
      {
        type: 'function',
        function: {
          name: 'find_related_lessons',
          description: 'Busca lecciones relacionadas a un tema',
          parameters: {
            type: 'object',
            properties: {
              topic: { type: 'string' },
            },
            required: ['topic'],
          },
        },
      },
    ];

    if (intent === 'practical_example') {
      baseTools.push({
        type: 'function',
        function: {
          name: 'get_market_example',
          description: 'Busca un ejemplo real en el mercado actual',
          parameters: {
            type: 'object',
            properties: {
              concept: { type: 'string' },
            },
            required: ['concept'],
          },
        },
      });
    }

    if (intent === 'quiz_help') {
      baseTools.push({
        type: 'function',
        function: {
          name: 'get_practice_questions',
          description: 'Obtiene preguntas de práctica (no del quiz oficial)',
          parameters: {
            type: 'object',
            properties: {
              topic: { type: 'string' },
              difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'] },
            },
            required: ['topic'],
          },
        },
      });
    }

    return baseTools;
  }
}

3. Learning Recommender

// src/modules/copilot/education/learning-recommender.ts

interface LearningRecommendation {
  type: 'course' | 'lesson' | 'practice' | 'review';
  item: CourseItem | LessonItem | PracticeItem;
  reason: string;
  priority: number;
  estimatedTime: number; // minutes
}

@Injectable()
export class LearningRecommender {
  constructor(
    private readonly courseService: CourseService,
    private readonly progressService: ProgressService,
    private readonly analyticsService: LearningAnalyticsService,
  ) {}

  async getRecommendations(
    userId: string,
    limit: number = 5,
  ): Promise<LearningRecommendation[]> {
    const [
      progress,
      analytics,
      availableCourses,
      completedCourses,
    ] = await Promise.all([
      this.progressService.getUserProgress(userId),
      this.analyticsService.getUserAnalytics(userId),
      this.courseService.getAvailableCourses(userId),
      this.courseService.getCompletedCourses(userId),
    ]);

    const recommendations: LearningRecommendation[] = [];

    // 1. Continue current course
    if (progress.currentCourse) {
      const nextLesson = await this.progressService.getNextLesson(userId);
      if (nextLesson) {
        recommendations.push({
          type: 'lesson',
          item: nextLesson,
          reason: 'Continúa donde lo dejaste',
          priority: 100,
          estimatedTime: nextLesson.duration,
        });
      }
    }

    // 2. Review struggling topics
    const strugglingTopics = analytics.strugglingTopics || [];
    for (const topic of strugglingTopics.slice(0, 2)) {
      const reviewLesson = await this.courseService.findReviewMaterial(topic.name);
      if (reviewLesson) {
        recommendations.push({
          type: 'review',
          item: reviewLesson,
          reason: `Refuerza tu comprensión de ${topic.name}`,
          priority: 90 - topic.averageScore,
          estimatedTime: 15,
        });
      }
    }

    // 3. Practice recommendations
    if (analytics.daysSinceLastPractice > 2) {
      const practiceTopics = this.selectPracticeTopics(analytics);
      recommendations.push({
        type: 'practice',
        item: {
          topics: practiceTopics,
          questionCount: 10,
        },
        reason: 'La práctica hace al maestro',
        priority: 70,
        estimatedTime: 20,
      });
    }

    // 4. Next course recommendation
    if (progress.currentCourse?.progress > 80) {
      const nextCourse = this.selectNextCourse(
        completedCourses,
        availableCourses,
        analytics,
      );
      if (nextCourse) {
        recommendations.push({
          type: 'course',
          item: nextCourse,
          reason: 'Siguiente paso en tu aprendizaje',
          priority: 50,
          estimatedTime: nextCourse.totalDuration,
        });
      }
    }

    // Sort by priority and limit
    return recommendations
      .sort((a, b) => b.priority - a.priority)
      .slice(0, limit);
  }

  private selectPracticeTopics(analytics: LearningAnalytics): string[] {
    const topics: string[] = [];

    // Mix of struggling and mastered topics (70/30 ratio)
    const struggling = (analytics.strugglingTopics || []).slice(0, 3);
    const mastered = (analytics.masteredTopics || []).slice(0, 1);

    topics.push(...struggling.map(t => t.name));
    topics.push(...mastered.map(t => t.name));

    return topics;
  }

  private selectNextCourse(
    completed: Course[],
    available: Course[],
    analytics: LearningAnalytics,
  ): Course | null {
    // Filter out completed courses
    const completedIds = new Set(completed.map(c => c.id));
    const candidates = available.filter(c => !completedIds.has(c.id));

    if (candidates.length === 0) return null;

    // Score candidates based on prerequisites and user level
    const scored = candidates.map(course => {
      let score = 0;

      // Check prerequisites
      const prereqsMet = course.prerequisites.every(p => completedIds.has(p));
      if (!prereqsMet) return { course, score: -1 };

      // Score based on level progression
      const levelOrder = ['beginner', 'intermediate', 'advanced'];
      const userLevel = analytics.estimatedLevel || 'beginner';
      const courseLevel = course.level;

      const userLevelIndex = levelOrder.indexOf(userLevel);
      const courseLevelIndex = levelOrder.indexOf(courseLevel);

      if (courseLevelIndex === userLevelIndex) score += 50;
      if (courseLevelIndex === userLevelIndex + 1) score += 30;

      // Score based on topic interest
      const userInterests = analytics.topicInterests || [];
      const matchingTopics = course.topics.filter(t =>
        userInterests.includes(t)
      ).length;
      score += matchingTopics * 10;

      return { course, score };
    });

    const best = scored
      .filter(s => s.score >= 0)
      .sort((a, b) => b.score - a.score)[0];

    return best?.course || null;
  }
}

Tools para el Agente

get_lesson_content

async function getLessonContent(lessonId: string): Promise<LessonContent> {
  const lesson = await lessonService.getLesson(lessonId);

  return {
    id: lesson.id,
    title: lesson.title,
    summary: lesson.summary,
    keyPoints: lesson.keyPoints,
    concepts: lesson.concepts,
    examples: lesson.examples,
    relatedLessons: lesson.relatedLessons.map(l => ({
      id: l.id,
      title: l.title,
    })),
  };
}

get_market_example

async function getMarketExample(concept: string): Promise<MarketExample> {
  // Map concepts to market patterns
  const conceptPatterns: Record<string, string[]> = {
    'soporte': ['support_bounce', 'support_break'],
    'resistencia': ['resistance_rejection', 'breakout'],
    'rsi': ['oversold_bounce', 'overbought_reversal'],
    'macd': ['bullish_crossover', 'bearish_crossover'],
    'tendencia': ['uptrend', 'downtrend'],
  };

  const patterns = conceptPatterns[concept.toLowerCase()] || [];

  // Find recent example in market
  const example = await marketDataService.findPatternExample(patterns, {
    lookbackDays: 30,
    preferLiquid: true,
  });

  if (!example) {
    return {
      found: false,
      message: `No encontré un ejemplo reciente de ${concept} en el mercado.`,
    };
  }

  return {
    found: true,
    symbol: example.symbol,
    pattern: example.pattern,
    date: example.date,
    description: example.description,
    chartUrl: `/charts/${example.symbol}?highlight=${example.date}`,
  };
}

Dependencias

Módulos Internos

  • CourseService (OQI-002)
  • ProgressService (OQI-002)
  • LessonService (OQI-002)
  • MarketDataService (OQI-003)

Base de Datos

  • courses
  • lessons
  • enrollments
  • lesson_progress
  • quiz_results

Referencias


Especificación técnica - Sistema NEXUS Trading Platform