- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
37 KiB
37 KiB
Portal Teacher - Flujos de Datos e Integracion
Fecha de creacion: 2025-11-29 Version: 1.0.0 Estado: VIGENTE Complementa: PORTAL-TEACHER-GUIDE.md, PORTAL-TEACHER-API-REFERENCE.md
1. Flujos de Datos Principales
1.1 Flujo: Dashboard Load
┌─────────────────────────────────────────────────────────────────────────┐
│ TEACHER DASHBOARD LOAD │
└─────────────────────────────────────────────────────────────────────────┘
Usuario navega a /teacher
│
▼
┌───────────────────┐
│ TeacherDashboard │
│ Page │
└────────┬──────────┘
│
▼
┌───────────────────┐ ┌─────────────────────────────────────────────┐
│useTeacherDashboard│────►│ Lanza 5 queries en paralelo: │
└────────┬──────────┘ │ 1. GET /teacher/dashboard/stats │
│ │ 2. GET /teacher/dashboard/activities │
│ │ 3. GET /teacher/dashboard/alerts │
│ │ 4. GET /teacher/dashboard/top-performers │
│ │ 5. GET /teacher/classrooms?limit=5 │
│ └─────────────────────────────────────────────┘
▼
┌───────────────────┐
│ API Client │
│ (teacherApi) │
└────────┬──────────┘
│ HTTP GET x5 (paralelo)
▼
┌───────────────────────────────────────────────────────────────────────┐
│ BACKEND │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │TeacherController│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │TeacherDashboard │ │
│ │ Service │ │
│ └────────┬────────────┘ │
│ │ │
│ ├──────► ClassroomMember Repository (social schema) │
│ ├──────► ModuleProgress Repository (progress schema) │
│ ├──────► ExerciseSubmission Repository (progress schema) │
│ ├──────► StudentInterventionAlert Repository │
│ └──────► UserStats Repository (gamification schema) │
│ │
└───────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────┐
│ React Query │
│ Cache │
│ (5 min stale) │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Render Dashboard │
│ Components │
└───────────────────┘
1.2 Flujo: Grading Submission
┌─────────────────────────────────────────────────────────────────────────┐
│ GRADING SUBMISSION FLOW │
└─────────────────────────────────────────────────────────────────────────┘
Teacher clickea "Calificar" en submission
│
▼
┌───────────────────┐
│GradeSubmissionModal│
└────────┬──────────┘
│
▼
┌───────────────────┐
│ useGrading() │
│ .gradeSubmission│
└────────┬──────────┘
│
▼
┌───────────────────┐
│ POST /teacher/ │
│ submissions/:id/ │
│ feedback │
└────────┬──────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────┐
│ BACKEND │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │TeacherController│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ GradingService │ │
│ └────────┬────────┘ │
│ │ │
│ ├──────► 1. Validar ownership del classroom │
│ │ │
│ ├──────► 2. Actualizar ExerciseSubmission │
│ │ - status = 'graded' │
│ │ - final_score = score │
│ │ - feedback = feedback │
│ │ - graded_at = NOW() │
│ │ │
│ ├──────► 3. Calcular XP a otorgar │
│ │ - Base XP por completar │
│ │ - Bonus por score │
│ │ - Bonus del teacher │
│ │ │
│ └──────► 4. Llamar GamificationModule │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │UserStatsService │ │
│ └────────┬─────────┘ │
│ │ │
│ ├──► Actualizar total_xp │
│ ├──► Verificar level up │
│ ├──► Verificar achievements │
│ └──► Emitir eventos │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ EVENT EMISSION │ │
│ │ │ │
│ │ EventEmitter.emit('submission.graded', { │ │
│ │ submissionId, studentId, score, xpAwarded │ │
│ │ }) │ │
│ │ │ │
│ │ EventEmitter.emit('xp.awarded', { │ │
│ │ userId, amount, source: 'grading' │ │
│ │ }) │ │
│ │ │ │
│ │ EventEmitter.emit('achievement.check', { │ │
│ │ userId, trigger: 'grading' │ │
│ │ }) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────┐
│ Invalidar queries │
│ - submissions │
│ - dashboard stats │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Toast success + │
│ Modal close │
└───────────────────┘
1.3 Flujo: Intervention Alert Creation (CRON)
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERVENTION ALERT DETECTION │
└─────────────────────────────────────────────────────────────────────────┘
CRON Job (cada 6 horas)
│
▼
┌─────────────────────────┐
│StudentRiskAlertService │
│ @Cron('0 */6 * * *') │
└────────┬────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────┐
│ ANALYSIS │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Query todos los estudiantes activos │
│ └──► ClassroomMember.findAll({ status: 'active' }) │
│ │
│ 2. Por cada estudiante, analizar: │
│ │ │
│ ├──► Inactividad (no login en X dias) │
│ │ SELECT MAX(last_activity_at) FROM user_sessions │
│ │ │
│ ├──► Tendencia de desempeno │
│ │ SELECT AVG(score) FROM exercise_submissions │
│ │ WHERE submitted_at > NOW() - INTERVAL '14 days' │
│ │ vs │
│ │ SELECT AVG(score) FROM exercise_submissions │
│ │ WHERE submitted_at BETWEEN NOW() - INTERVAL '28 days' │
│ │ AND NOW() - INTERVAL '14 days' │
│ │ │
│ ├──► Patron de errores │
│ │ Multiples intentos fallidos en mismo ejercicio │
│ │ │
│ └──► ML Predictor (opcional) │
│ MlPredictorService.predictRisk(studentId) │
│ │
│ 3. Si riesgo detectado: │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │InterventionAlertsService│ │
│ │ .createAlert() │ │
│ └────────┬────────────────┘ │
│ │ │
│ ├──► INSERT INTO student_intervention_alerts │
│ │ │
│ └──► NotificationService.notify({ │
│ channel: 'push', │
│ recipient: teacher_id, │
│ template: 'student_at_risk', │
│ data: { student_name, risk_type } │
│ }) │
│ │
└───────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────┐
│ WebSocket emit │
│'teacher:new_alert'│
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Frontend recibe │
│ evento y muestra │
│ notificacion │
└───────────────────┘
1.4 Flujo: Grant Bonus Coins
┌─────────────────────────────────────────────────────────────────────────┐
│ GRANT BONUS COINS │
└─────────────────────────────────────────────────────────────────────────┘
Teacher otorga bonus
│
▼
┌───────────────────┐
│ GrantBonusModal │
│ amount: 100 │
│ reason: "..." │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ useGrantBonus() │
│ .mutate() │
└────────┬──────────┘
│
▼
POST /teacher/students/:id/bonus
│
▼
┌───────────────────────────────────────────────────────────────────────┐
│ BACKEND │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │TeacherController│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │BonusCoinsService│ │
│ └────────┬────────┘ │
│ │ │
│ ├──► 1. Verificar que teacher tiene acceso al estudiante │
│ │ - Query TeacherClassroom JOIN ClassroomMember │
│ │ │
│ ├──► 2. Validar limites │
│ │ - Max 1000 coins por transaccion │
│ │ - Max 5000 coins por dia por teacher │
│ │ │
│ └──► 3. Ejecutar transaccion │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ MlCoinsService │ │
│ └────────┬────────┘ │
│ │ │
│ ├──► INSERT INTO ml_coins_transactions { │
│ │ user_id, amount, type: 'teacher_bonus', │
│ │ description, granted_by │
│ │ } │
│ │ │
│ └──► UPDATE user_stats SET │
│ ml_coins_balance = ml_coins_balance + 100 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ NOTIFICATION │ │
│ │ │ │
│ │ NotificationService.send({ │ │
│ │ userId: studentId, │ │
│ │ type: 'bonus_received', │ │
│ │ title: 'Recibiste ML Coins!', │ │
│ │ message: 'Tu profesor te otorgo 100 ML Coins', │ │
│ │ data: { amount, reason, teacher_name } │ │
│ │ }) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────┐
│ Response: │
│ { success: true, │
│ new_balance } │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Invalidar: │
│ - student economy │
│ - analytics │
└───────────────────┘
2. Integracion con Otros Modulos
2.1 Diagrama de Integracion
┌─────────────────────────────────────────────────────────────────────────┐
│ TEACHER MODULE INTEGRATIONS │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ AUTH MODULE │
│ │
│ - JwtAuthGuard │
│ - RolesGuard │
│ - User entity │
└────────┬────────┘
│ Autenticacion
▼
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ SOCIAL │◄────────────►│ TEACHER MODULE │◄────────────►│ GAMIFICATION│
│ MODULE │ Classrooms │ │ XP, Coins │ MODULE │
│ │ Students │ │ Achievements│ │
│- Classroom │ Members │ - Dashboard │ Ranks │- UserStats │
│- Members │ │ - Progress │ │- MlCoins │
│- Teams │ │ - Grading │ │- Achievements│
└─────────────┘ │ - Analytics │ └─────────────┘
│ - Alerts │
│ - Reports │
┌─────────────┐ │ - Bonus │ ┌─────────────┐
│ PROGRESS │◄────────────►│ │◄────────────►│NOTIFICATIONS│
│ MODULE │ Submissions │ │ Alerts │ MODULE │
│ │ Attempts │ │ Messages │ │
│- ModuleProg │ Sessions │ │ │- Send │
│- Submissions│ │ │ │- Templates │
│- Attempts │ │ │ │- Queue │
└─────────────┘ └─────────────────┘ └─────────────┘
│
│ Exercises
▼
┌─────────────────┐
│ EDUCATIONAL │
│ MODULE │
│ │
│ - Modules │
│ - Exercises │
│ - Mechanics │
└─────────────────┘
2.2 Dependencias por Servicio
TeacherDashboardService
@Injectable()
export class TeacherDashboardService {
constructor(
// Social Module - Classrooms y miembros
@InjectRepository(Classroom, 'social')
private classroomRepo: Repository<Classroom>,
@InjectRepository(ClassroomMember, 'social')
private memberRepo: Repository<ClassroomMember>,
@InjectRepository(TeacherClassroom, 'social')
private teacherClassroomRepo: Repository<TeacherClassroom>,
// Progress Module - Progreso y submissions
@InjectRepository(ModuleProgress, 'progress')
private progressRepo: Repository<ModuleProgress>,
@InjectRepository(ExerciseSubmission, 'progress')
private submissionRepo: Repository<ExerciseSubmission>,
// Gamification Module - Stats
@InjectRepository(UserStats, 'gamification')
private statsRepo: Repository<UserStats>,
// Internal - Alertas
@InjectRepository(StudentInterventionAlert)
private alertRepo: Repository<StudentInterventionAlert>,
) {}
}
AnalyticsService
@Injectable()
export class AnalyticsService {
constructor(
// Cache para analytics pesados
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
// Progress Module
@InjectRepository(ModuleProgress, 'progress')
private progressRepo: Repository<ModuleProgress>,
@InjectRepository(ExerciseSubmission, 'progress')
private submissionRepo: Repository<ExerciseSubmission>,
@InjectRepository(LearningSession, 'progress')
private sessionRepo: Repository<LearningSession>,
// Gamification Module
@InjectRepository(UserStats, 'gamification')
private statsRepo: Repository<UserStats>,
@InjectRepository(UserAchievement, 'gamification')
private achievementRepo: Repository<UserAchievement>,
@InjectRepository(MlCoinsTransaction, 'gamification')
private transactionRepo: Repository<MlCoinsTransaction>,
// ML Predictions
private mlPredictorService: MlPredictorService,
) {}
}
BonusCoinsService
@Injectable()
export class BonusCoinsService {
constructor(
// Social - Verificar acceso
@InjectRepository(TeacherClassroom, 'social')
private teacherClassroomRepo: Repository<TeacherClassroom>,
@InjectRepository(ClassroomMember, 'social')
private memberRepo: Repository<ClassroomMember>,
// Gamification - Otorgar coins
private mlCoinsService: MlCoinsService,
// Notifications
private notificationService: NotificationService,
) {}
}
2.3 Eventos Entre Modulos
// Eventos que Teacher Module EMITE
@Injectable()
export class GradingService {
constructor(private eventEmitter: EventEmitter2) {}
async submitFeedback(submissionId: string, dto: SubmitFeedbackDto) {
// ... logica de grading
// Emitir evento para otros modulos
this.eventEmitter.emit('submission.graded', {
submissionId,
studentId,
classroomId,
score: dto.score,
xpAwarded,
});
}
}
// Eventos que Teacher Module ESCUCHA
@Injectable()
export class StudentRiskAlertService {
@OnEvent('student.inactive')
async handleStudentInactive(payload: StudentInactiveEvent) {
// Crear alerta de inactividad
await this.createAlert({
student_id: payload.studentId,
type: 'inactivity',
message: `Sin actividad por ${payload.days} dias`,
});
}
@OnEvent('submission.failed_multiple')
async handleMultipleFailures(payload: MultipleFailuresEvent) {
// Crear alerta de dificultad
await this.createAlert({
student_id: payload.studentId,
type: 'struggling',
message: `Dificultad en ${payload.exerciseName}`,
});
}
}
2.4 Queries Cross-Module
// Query que cruza multiples schemas
async getStudentProgressWithGamification(studentId: string) {
// 1. Progress schema
const moduleProgress = await this.progressRepo
.createQueryBuilder('mp')
.where('mp.user_id = :studentId', { studentId })
.getMany();
// 2. Gamification schema
const userStats = await this.statsRepo.findOne({
where: { user_id: studentId },
});
const achievements = await this.achievementRepo.find({
where: { user_id: studentId },
relations: ['achievement'],
});
// 3. Combinar resultados
return {
progress: moduleProgress,
gamification: {
totalXp: userStats.total_xp,
level: userStats.level,
mayaRank: userStats.maya_rank,
mlCoinsBalance: userStats.ml_coins_balance,
achievements: achievements.map(ua => ({
id: ua.achievement.id,
name: ua.achievement.name,
unlockedAt: ua.unlocked_at,
})),
},
};
}
3. Cache Strategy
3.1 Datos Cacheados
| Dato | TTL | Invalidacion |
|---|---|---|
| Dashboard stats | 60s | submission.graded, alert.created |
| Classroom list | 5min | classroom.updated |
| Student progress | 2min | submission.graded |
| Analytics | 5min | Manual o cada hora |
| Economy stats | 5min | coins.transaction |
3.2 Implementacion
// Cache en backend
@Injectable()
export class AnalyticsService {
async getClassroomAnalytics(classroomId: string) {
const cacheKey = `analytics:classroom:${classroomId}`;
// Intentar cache
const cached = await this.cacheManager.get(cacheKey);
if (cached) return cached;
// Calcular
const analytics = await this.calculateAnalytics(classroomId);
// Guardar en cache
await this.cacheManager.set(cacheKey, analytics, 300_000); // 5 min
return analytics;
}
}
// Cache en frontend (React Query)
const { data } = useQuery({
queryKey: ['teacher', 'analytics', 'classroom', classroomId],
queryFn: () => analyticsApi.getClassroomAnalytics(classroomId),
staleTime: 5 * 60 * 1000, // 5 min stale
gcTime: 30 * 60 * 1000, // 30 min en cache
});
4. Seguridad y Validacion
4.1 Flujo de Autorizacion
Request llega
│
▼
┌─────────────────┐
│ JwtAuthGuard │────► Verifica token JWT valido
└────────┬────────┘ - Extrae user del token
│ - Agrega user a request
▼
┌─────────────────┐
│ RolesGuard │────► Verifica rol del usuario
└────────┬────────┘ - Chequea @Roles() decorator
│ - admin_teacher o super_admin
▼
┌─────────────────┐
│ TeacherGuard │────► Verifica acceso teacher
└────────┬────────┘ - Usuario tiene rol teacher
│ - O es super_admin
▼
┌─────────────────┐
│ClassroomOwner │────► Verifica propiedad (si aplica)
│ Guard │ - Query teacher_classrooms
└────────┬────────┘ - teacher_id + classroom_id
│
▼
Controller
4.2 Validacion en Capas
// 1. DTO Validation (Entrada)
export class GrantBonusDto {
@IsNumber()
@Min(1, { message: 'Minimo 1 ML Coin' })
@Max(1000, { message: 'Maximo 1000 ML Coins por transaccion' })
amount!: number;
@IsString()
@MinLength(10, { message: 'Razon debe tener al menos 10 caracteres' })
@MaxLength(500)
reason!: string;
}
// 2. Service Validation (Negocio)
async grantBonus(teacherId: string, studentId: string, dto: GrantBonusDto) {
// Verificar acceso
const hasAccess = await this.verifyTeacherHasAccessToStudent(teacherId, studentId);
if (!hasAccess) {
throw new ForbiddenException('No tienes acceso a este estudiante');
}
// Verificar limite diario
const todayTotal = await this.getTodayBonusTotal(teacherId);
if (todayTotal + dto.amount > 5000) {
throw new BadRequestException('Limite diario de 5000 ML Coins alcanzado');
}
// Proceder
return this.mlCoinsService.addTransaction(/*...*/);
}
// 3. Entity Validation (Datos)
@Entity('student_intervention_alerts')
export class StudentInterventionAlert {
@Column({
type: 'enum',
enum: ['inactivity', 'declining_trend', 'struggling', 'at_risk'],
})
type!: string;
@Column({
type: 'enum',
enum: ['low', 'medium', 'high', 'critical'],
})
severity!: string;
}
5. Manejo de Errores
5.1 Error Handling Pattern
// Service con error handling
@Injectable()
export class StudentProgressService {
async getStudentProgress(studentId: string) {
try {
const student = await this.userRepo.findOne({ where: { id: studentId } });
if (!student) {
throw new NotFoundException(`Estudiante ${studentId} no encontrado`);
}
const progress = await this.progressRepo.find({
where: { user_id: studentId },
});
return this.mapToDto(student, progress);
} catch (error) {
if (error instanceof HttpException) {
throw error; // Re-throw HTTP exceptions
}
this.logger.error(`Error getting progress for ${studentId}`, error.stack);
throw new InternalServerErrorException('Error al obtener progreso');
}
}
}
// Frontend error handling
export function useStudentProgress(studentId: string) {
return useQuery({
queryKey: ['teacher', 'students', studentId, 'progress'],
queryFn: () => studentProgressApi.getProgress(studentId),
retry: (failureCount, error) => {
// No retry en 4xx
if (error.response?.status >= 400 && error.response?.status < 500) {
return false;
}
return failureCount < 3;
},
onError: (error) => {
if (error.response?.status === 404) {
toast.error('Estudiante no encontrado');
} else {
toast.error('Error al cargar progreso');
}
},
});
}
Changelog
| Version | Fecha | Cambios |
|---|---|---|
| 1.0.0 | 2025-11-29 | Creacion inicial |