'use client'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { cmsApi, questionBankApi, TestTemplate, TestTemplateFilters, TestType, Course, Module, Lesson, MySqlPlan, MySqlCourse, } from '@/lib/api'; import { Plus, Search, Filter, Edit2, Trash2, Eye, Copy, BookOpen, Clock, Target, Tag, X } from 'lucide-react'; interface TestTemplateManagerProps { onSelectTemplate?: (template: TestTemplate) => void; onCreateTemplate?: () => void; onEditTemplate?: (template: TestTemplate) => void; } export default function TestTemplateManager({ onSelectTemplate, onCreateTemplate, onEditTemplate }: TestTemplateManagerProps) { const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [filters, setFilters] = useState({}); const [showFilters, setShowFilters] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [filterPlans, setFilterPlans] = useState([]); const [filterCourses, setFilterCourses] = useState([]); const [filterPlanId, setFilterPlanId] = useState(''); const [filterCourseId, setFilterCourseId] = useState(''); const [showApplyModal, setShowApplyModal] = useState(false); const [selectedTemplate, setSelectedTemplate] = useState(null); const [courses, setCourses] = useState([]); const [selectedTargetCourseId, setSelectedTargetCourseId] = useState(''); const [selectedLesson, setSelectedLesson] = useState(''); const [applying, setApplying] = useState(false); const resetApplyState = () => { setSelectedTemplate(null); setSelectedTargetCourseId(''); setSelectedLesson(''); }; const loadTemplates = useCallback(async () => { try { setLoading(true); const data = await cmsApi.listTestTemplates(filters); setTemplates(data); } catch (error) { console.error('Failed to load test templates:', error); } finally { setLoading(false); } }, [filters]); useEffect(() => { loadTemplates(); }, [loadTemplates]); useEffect(() => { questionBankApi.getMySQLPlans().then(setFilterPlans).catch(() => {}); }, []); useEffect(() => { if (!filterPlanId) { setFilterCourses([]); setFilterCourseId(''); setFilters((prev) => ({ ...prev, mysql_course_id: undefined })); return; } questionBankApi .getMySQLCoursesByPlan(filterPlanId) .then(setFilterCourses) .catch(() => { setFilterCourses([]); }); }, [filterPlanId]); const filteredTemplates = useMemo(() => { if (!searchTerm.trim()) return templates; const q = searchTerm.toLowerCase(); return templates.filter((t) => { const inName = t.name?.toLowerCase().includes(q); const inDesc = t.description?.toLowerCase().includes(q); const inTags = (t.tags || []).some((tag) => tag.toLowerCase().includes(q)); return Boolean(inName || inDesc || inTags); }); }, [templates, searchTerm]); const handleDelete = async (id: string) => { if (!confirm('¿Estás seguro de que deseas eliminar esta plantilla?')) return; try { await cmsApi.deleteTestTemplate(id); await loadTemplates(); } catch (error) { console.error('Failed to delete template:', error); alert('Error al eliminar la plantilla'); } }; const handleApplyTemplate = async (template: TestTemplate) => { setSelectedTemplate(template); setShowApplyModal(true); setSelectedTargetCourseId(''); setSelectedLesson(''); cmsApi.getCourses() .then(setCourses) .catch((e) => console.error('Failed to load courses:', e)); }; const handleApplyToLesson = async () => { if (!selectedTemplate || !selectedTargetCourseId || !selectedLesson) { alert('Selecciona curso y lección'); return; } try { setApplying(true); await cmsApi.applyTemplateToLesson(selectedTemplate.id, selectedLesson); alert('Plantilla aplicada exitosamente a la lección'); setShowApplyModal(false); resetApplyState(); } catch (error) { console.error('Failed to apply template:', error); alert('Error al aplicar la plantilla'); } finally { setApplying(false); } }; const getTestTypeLabel = (type: TestType) => { const labels: Record = { CA: 'Evaluación Continua', MWT: 'Examen Escrito Parcial', MOT: 'Examen Oral Parcial', FOT: 'Examen Oral Final', FWT: 'Examen Escrito Final', }; return labels[type] || type; }; const getTestTypeColor = (type: TestType) => { if (type === 'CA') return 'bg-blue-100 text-blue-800'; if (type.includes('MWT') || type.includes('MOT')) return 'bg-purple-100 text-purple-800'; if (type.includes('FWT') || type.includes('FOT')) return 'bg-orange-100 text-orange-800'; return 'bg-gray-100 text-gray-800'; }; const getLinkedMaterialsCount = (template: TestTemplate): number => { const data = template.template_data; if (!data || typeof data !== 'object') return 0; const selected = (data as Record).selected_assets; return Array.isArray(selected) ? selected.length : 0; }; return (

Plantillas de Pruebas

Gestiona plantillas y vincúlalas a una lección específica de un curso

setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{showFilters && (
)} {loading ? (

Cargando plantillas...

) : filteredTemplates.length === 0 ? (

No hay plantillas

Crea tu primera plantilla de prueba

) : (
{filteredTemplates.map((template) => (

{template.name}

{template.description && (

{template.description}

)}
{getTestTypeLabel(template.test_type)} {template.mysql_course_id && ( Curso SAM #{template.mysql_course_id} )}
{template.duration_minutes} minutos
Puntuación mínima: {template.passing_score}%
Puntos totales: {template.total_points}
Materiales vinculados: {getLinkedMaterialsCount(template)}
{template.tags && template.tags.length > 0 && (
{template.tags.map((tag, idx) => ( {tag} ))}
)}
Usos: {template.usage_count}
))}
)} {showApplyModal && selectedTemplate && (

Aplicar Plantilla a Lección

{selectedTemplate.name}

Información de la Plantilla

Tipo: {getTestTypeLabel(selectedTemplate.test_type)}
Duración: {selectedTemplate.duration_minutes} min
Puntos: {selectedTemplate.total_points}
Aprobación: {selectedTemplate.passing_score}%
Materiales vinculados: {getLinkedMaterialsCount(selectedTemplate)}
{selectedTargetCourseId && (
)}

¿Qué sucederá?

  • • La lección se convertirá en un quiz con las preguntas de la plantilla
  • • Se configurará con 1 solo intento por alumno
  • • Los alumnos podrán revisar sus respuestas después de completar
)}
); } function LessonSelector({ courseId, selectedLesson, onSelect, }: { courseId: string; selectedLesson: string; onSelect: (lessonId: string) => void; }) { const [modules, setModules] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const loadLessons = async () => { try { setLoading(true); const course = await cmsApi.getCourseWithFullOutline(courseId); setModules(course.modules || []); } catch (error) { console.error('Failed to load lessons:', error); setModules([]); } finally { setLoading(false); } }; loadLessons(); }, [courseId]); if (loading) { return
Cargando lecciones...
; } const totalLessons = modules.reduce((acc, m) => acc + (m.lessons?.length || 0), 0); if (totalLessons === 0) { return
Este curso no tiene lecciones
; } return ( ); }