From 65f62281bb84eb73cd64e4dac74b2622a263ea39 Mon Sep 17 00:00:00 2001 From: Nurfog Date: Thu, 16 Apr 2026 15:37:32 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20mejorar=20la=20gesti=C3=B3n=20de=20nive?= =?UTF-8?q?les=20de=20ingl=C3=A9s=20en=20la=20selecci=C3=B3n=20de=20cursos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms-service/src/handlers_question_bank.rs | 81 +++++++++++++++++++ web/studio/src/app/admin/materials/page.tsx | 29 ++++--- 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/services/cms-service/src/handlers_question_bank.rs b/services/cms-service/src/handlers_question_bank.rs index 151556b..13fdb2f 100644 --- a/services/cms-service/src/handlers_question_bank.rs +++ b/services/cms-service/src/handlers_question_bank.rs @@ -1210,6 +1210,87 @@ pub async fn get_mysql_courses_by_plan( .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Error al obtener los cursos: {}", e)))?; + // Intentar refrescar desde MySQL (fuente SAM) para evitar listas incompletas por espejo desactualizado. + if let Ok(mysql_pool) = connect_mysql_pool("MYSQL_DATABASE_URL").await { + let live_courses: Result, sqlx::Error> = sqlx::query_as( + r#" + SELECT DISTINCT + c.idCursos AS id_cursos, + c.NombreCurso AS nombre_curso, + c.NivelCurso AS nivel_curso, + pe.idPlanDeEstudios AS id_plan_de_estudios, + pe.Nombre AS nombre_plan, + c.Duracion AS duracion + FROM curso c + JOIN plandeestudios pe ON c.idPlanDeEstudios = pe.idPlanDeEstudios + WHERE c.Activo = 1 + AND pe.Activo = 1 + AND pe.idPlanDeEstudios = ? + ORDER BY c.NivelCurso, c.NombreCurso + "#, + ) + .bind(filters.plan_id) + .fetch_all(&mysql_pool) + .await; + + match live_courses { + Ok(live) if !live.is_empty() => { + if live.len() != courses.len() { + tracing::info!( + "Refrescando cursos SAM desde MySQL para plan {}: espejo={} mysql={}", + filters.plan_id, + courses.len(), + live.len() + ); + } + + // Best effort: sincronizar también el plan consultado al espejo PostgreSQL. + let live_plans: Result, sqlx::Error> = sqlx::query_as( + r#" + SELECT DISTINCT + pe.idPlanDeEstudios AS id_plan_de_estudios, + pe.Nombre AS nombre_plan + FROM plandeestudios pe + WHERE pe.Activo = 1 + AND pe.idPlanDeEstudios = ? + "#, + ) + .bind(filters.plan_id) + .fetch_all(&mysql_pool) + .await; + + if let Ok(plan_rows) = live_plans { + if !plan_rows.is_empty() { + if let Err(err) = save_mysql_courses_and_plans(&pool, org_ctx.id, plan_rows, live.clone()).await { + tracing::warn!( + "No se pudo actualizar espejo SAM para plan {}: {}", + filters.plan_id, + err + ); + } + } + } + + courses = live; + } + Ok(_) => { + tracing::debug!( + "MySQL devolvio 0 cursos activos para plan {}; se mantiene espejo local", + filters.plan_id + ); + } + Err(err) => { + tracing::warn!( + "No se pudo refrescar cursos SAM desde MySQL para plan {}: {}", + filters.plan_id, + err + ); + } + } + + mysql_pool.close().await; + } + // Respaldo compatible con versiones anteriores: si el reflejo SAM está vacío, usar el reflejo de metadatos heredado. if courses.is_empty() { courses = sqlx::query_as( diff --git a/web/studio/src/app/admin/materials/page.tsx b/web/studio/src/app/admin/materials/page.tsx index 1844ddd..2cf6fbc 100644 --- a/web/studio/src/app/admin/materials/page.tsx +++ b/web/studio/src/app/admin/materials/page.tsx @@ -249,14 +249,15 @@ export default function AdminSharedMaterialsPage() { setSelectedCourseId(value); setSplitToRegular(false); const selected = courses.find((c) => c.idCursos === value); - if (selected?.NivelCurso !== undefined && selected?.NivelCurso !== null) { - const n = selected.NivelCurso; - if (n <= 2) setEnglishLevel('beginner_1'); - else if (n <= 4) setEnglishLevel('beginner_2'); - else if (n <= 6) setEnglishLevel('intermediate_1'); - else if (n <= 8) setEnglishLevel('intermediate_2'); - else if (n <= 10) setEnglishLevel('advanced_1'); - else setEnglishLevel('advanced_2'); + if (selected?.NombreCurso) { + const normalized = selected.NombreCurso.toUpperCase().replace(/\s*INTENSIVE\s*/g, '').trim(); + if (normalized.includes('ELEMENTARY')) setEnglishLevel('elementary'); + else if (normalized.includes('BEGINNER')) setEnglishLevel('beginner'); + else if (normalized.includes('PRE-INTERMEDIATE') || normalized.includes('PRE INTERMEDIATE')) setEnglishLevel('pre_intermediate'); + else if (normalized.includes('LOW INTERMEDIATE')) setEnglishLevel('low_intermediate'); + else if (normalized.includes('UPPER-INTERMEDIATE') || normalized.includes('UPPER INTERMEDIATE')) setEnglishLevel('upper_intermediate'); + else if (normalized.includes('PRE ADVANCED') || normalized.includes('PRE-ADVANCED')) setEnglishLevel('pre_advanced'); + else if (normalized.includes('ADVANCED')) setEnglishLevel('advanced'); } }} disabled={!selectedPlanId} @@ -359,15 +360,13 @@ export default function AdminSharedMaterialsPage() { className="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm" > + - - - - - + + + + - -