import { prisma } from "@/lib/db" import type { Exercise, Stage } from "@/curriculum/types" const STAGE_THRESHOLD = 0.8 const PROFILE_CURRICULA: Record> = { ISABELLA: { lectura: { folder: "lectura", stages: [1, 2, 3, 4, 5, 6, 7, 8, 9] }, numeros: { folder: "numeros", stages: [1, 2, 3, 4, 5, 6, 7, 8] }, }, FRANCESCA: { matematicas: { folder: "francesca", stages: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, lecturas: { folder: "francesca-lectura", stages: [1, 2, 3, 4, 5, 6, 7, 8, 9] }, ortografia: { folder: "francesca-ortografia", stages: [1, 2] }, }, SEBASTIAN: { matematicas: { folder: "sebastian", stages: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] }, lectura: { folder: "sebastian-lectura", stages: [1, 2, 3, 4, 5, 6] }, historia: { folder: "sebastian-historia", stages: [1, 2, 3, 4, 5] }, geografia: { folder: "sebastian-geografia", stages: [1, 2, 3] }, banderas: { folder: "sebastian-banderas", stages: [1, 2] }, }, } const curriculumCache: Record> = {} export async function getStagesForProfile( profile: string, topic: string, ): Promise> { const key = `${profile}-${topic}` if (curriculumCache[key]) return curriculumCache[key] const config = PROFILE_CURRICULA[profile]?.[topic] if (!config) return {} const stages: Record = {} for (const s of config.stages) { try { const mod = await import(`@/curriculum/${config.folder}/etapa-${s}`) stages[String(s)] = mod.default } catch { // stage not yet implemented } } curriculumCache[key] = stages return stages } async function ensureInitialStage(childId: string, topic: string) { try { const exists = await prisma.curriculumProgress.findFirst({ where: { childId, topic }, }) if (!exists) { await prisma.curriculumProgress.create({ data: { childId, topic, stage: 1 }, }) } } catch { // Child may not exist yet in FK constraint — proceed gracefully } } export async function getCurrentStage(childId: string, topic: string) { await ensureInitialStage(childId, topic) const progress = await prisma.curriculumProgress.findFirst({ where: { childId, topic }, orderBy: { stage: "asc" }, }) return progress } export async function getNextExercise( childId: string, topic: string, profile: string = "ISABELLA", ): Promise { await ensureInitialStage(childId, topic) const progress = await prisma.curriculumProgress.findFirst({ where: { childId, topic, completedAt: null }, orderBy: { stage: "asc" }, }) if (!progress) return null const stages = await getStagesForProfile(profile, topic) const stage = stages[String(progress.stage)] if (!stage || !stage.exercises.length) return null const allAttemps = await prisma.skillAttempt.findMany({ where: { exerciseId: { not: null }, sessionLog: { childId } }, select: { exerciseId: true, correct: true }, }) const mastered = new Set( allAttemps .filter((a) => a.correct) .map((a) => a.exerciseId), ) const attempted = new Set(allAttemps.map((a) => a.exerciseId)) const available = stage.exercises.filter((ex) => { if (mastered.has(ex.id)) return false const prereqsMet = ex.prerequisites.every((p) => mastered.has(p)) return prereqsMet }) if (available.length === 0) { if (attempted.size > 0 && stage.exercises.length > 0) { const masteryRatio = mastered.size / stage.exercises.length if (masteryRatio >= STAGE_THRESHOLD) { await advanceStage(childId, topic, progress.stage) return getNextExercise(childId, topic, profile) } } return null } const fsrsCards = await prisma.fsrsCard.findMany({ where: { childId, skillCode: { in: available.map((e) => e.skillCode) } }, }) const dueMap = new Map(fsrsCards.map((c) => [c.skillCode, c.dueAt.getTime()])) const now = Date.now() const unsorted = [...available] unsorted.sort((a, b) => { const dueA = dueMap.get(a.skillCode) ?? 0 const dueB = dueMap.get(b.skillCode) ?? 0 const isDueA = dueA <= now ? 0 : 1 const isDueB = dueB <= now ? 0 : 1 if (isDueA !== isDueB) return isDueA - isDueB if (!attempted.has(a.id) && !attempted.has(b.id)) return 0 if (!attempted.has(a.id)) return -1 if (!attempted.has(b.id)) return 1 return dueA - dueB }) return unsorted[0] || null } async function advanceStage(childId: string, topic: string, currentStage: number) { await prisma.curriculumProgress.updateMany({ where: { childId, topic, stage: currentStage }, data: { completedAt: new Date() }, }) const nextStage = currentStage + 1 const exists = await prisma.curriculumProgress.findUnique({ where: { childId_topic_stage: { childId, topic, stage: nextStage } }, }) if (!exists) { await prisma.curriculumProgress.create({ data: { childId, topic, stage: nextStage }, }) } } export function getErrorTypeForExercise( exercise: Exercise, correct: boolean, ): string | null { if (correct) return null return exercise.errorType || "discriminacion-auditiva" }