'use client'; import React, { useState, useEffect } from 'react'; import { cmsApi, questionBankApi, CreateTestTemplatePayload, CourseLevel, CourseType, TestType, QuestionType, MySqlPlan, MySqlCourse } from '@/lib/api'; import { X, Save, Plus, Trash2, Sparkles, ChevronDown, ChevronUp, Copy, GripVertical, Edit2 } from 'lucide-react'; interface Section { id: string; title: string; description?: string; section_order: number; points: number; instructions?: string; } interface Question { id: string; section_id?: string; question_order: number; question_type: QuestionType; question_text: string; options?: string[]; correct_answer?: number | number[] | string; explanation?: string; points: number; metadata?: any; } interface TestTemplateFormProps { onSuccess?: () => void; onCancel?: () => void; } export default function TestTemplateForm({ onSuccess, onCancel }: TestTemplateFormProps) { const [formData, setFormData] = useState({ name: '', description: '', mysql_course_id: undefined, test_type: 'CA', duration_minutes: 60, passing_score: 70, total_points: 100, instructions: '', template_data: { sections: [], questions: [] }, tags: [], }); const [sections, setSections] = useState([]); const [questions, setQuestions] = useState([]); const [newTag, setNewTag] = useState(''); const [saving, setSaving] = useState(false); const [generatingAI, setGeneratingAI] = useState(false); const [expandedQuestion, setExpandedQuestion] = useState(null); const [aiContext, setAiContext] = useState(''); // MySQL course selection state const [mysqlPlans, setMysqlPlans] = useState([]); const [mysqlCourses, setMysqlCourses] = useState([]); const [selectedPlanId, setSelectedPlanId] = useState(''); const [selectedCourseId, setSelectedCourseId] = useState(''); const [loadingPlans, setLoadingPlans] = useState(false); const [loadingCourses, setLoadingCourses] = useState(false); // Load MySQL plans on mount useEffect(() => { const loadPlans = async () => { try { setLoadingPlans(true); const plans = await questionBankApi.getMySQLPlans(); setMysqlPlans(plans); } catch (error) { console.error('Failed to load MySQL plans:', error); } finally { setLoadingPlans(false); } }; loadPlans(); }, []); // Load courses when plan is selected useEffect(() => { const loadCourses = async () => { if (!selectedPlanId) { setMysqlCourses([]); return; } try { setLoadingCourses(true); const courses = await questionBankApi.getMySQLCoursesByPlan(selectedPlanId as number); setMysqlCourses(courses); } catch (error) { console.error('Failed to load MySQL courses:', error); } finally { setLoadingCourses(false); } }; loadCourses(); }, [selectedPlanId]); // Handle course selection - store mysql_course_id (preferred approach) const handleCourseSelect = (courseId: number | '') => { setSelectedCourseId(courseId); // Store the MySQL course ID directly - level/course_type can be derived from mysql_courses table setFormData({ ...formData, mysql_course_id: courseId === '' ? undefined : courseId, }); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.name.trim()) { alert('El nombre es obligatorio'); return; } if (questions.length === 0) { alert('Debes agregar al menos una pregunta'); return; } // Validate: either mysql_course_id OR level+course_type must be provided if (!formData.mysql_course_id && (!formData.level || !formData.course_type)) { alert('Debes seleccionar un curso de MySQL o especificar nivel y tipo de curso manualmente'); return; } try { setSaving(true); // Primero crear la plantilla const template = await cmsApi.createTestTemplate(formData); // Luego agregar secciones for (const section of sections) { await cmsApi.createTemplateSection(template.id, { title: section.title, description: section.description, section_order: section.section_order, points: section.points, instructions: section.instructions, }); } // Finalmente agregar preguntas for (const question of questions) { await cmsApi.createTemplateQuestion(template.id, { section_id: question.section_id, question_order: question.question_order, question_type: question.question_type, question_text: question.question_text, options: question.options, correct_answer: question.correct_answer, explanation: question.explanation, points: question.points, metadata: question.metadata, }); } alert('Plantilla creada exitosamente'); onSuccess?.(); } catch (error) { console.error('Failed to create template:', error); alert('Error al crear la plantilla'); } finally { setSaving(false); } }; const handleAddTag = () => { if (newTag.trim() && !formData.tags?.includes(newTag.trim())) { setFormData({ ...formData, tags: [...(formData.tags || []), newTag.trim()], }); setNewTag(''); } }; const handleRemoveTag = (tagToRemove: string) => { setFormData({ ...formData, tags: formData.tags?.filter(tag => tag !== tagToRemove) || [], }); }; const handleAddSection = () => { const newSection: Section = { id: `section-${Date.now()}`, title: `Sección ${sections.length + 1}`, description: '', section_order: sections.length, points: 0, instructions: '', }; setSections([...sections, newSection]); }; const handleRemoveSection = (sectionId: string) => { setSections(sections.filter(s => s.id !== sectionId)); setQuestions(questions.filter(q => q.section_id !== sectionId)); }; const handleUpdateSection = (sectionId: string, updates: Partial
) => { setSections(sections.map(s => s.id === sectionId ? { ...s, ...updates } : s)); }; const handleAddQuestion = (sectionId?: string) => { const newQuestion: Question = { id: `question-${Date.now()}`, section_id: sectionId, question_order: questions.filter(q => q.section_id === sectionId).length, question_type: 'multiple-choice', question_text: '', options: ['Opción 1', 'Opción 2', 'Opción 3', 'Opción 4'], correct_answer: 0, explanation: '', points: 1, }; setQuestions([...questions, newQuestion]); setExpandedQuestion(newQuestion.id); }; const handleGenerateWithAI = async () => { if (!aiContext.trim()) { alert('Ingresa el contexto para generar las preguntas (ej: tema de la lección, contenido, etc.)'); return; } const token = localStorage.getItem('studio_token'); if (!token) { alert('No hay sesión activa. Por favor inicia sesión nuevamente.'); return; } try { setGeneratingAI(true); // Usar el endpoint RAG de generación de preguntas desde banco MySQL const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/test-templates/generate-with-rag`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ topic: aiContext, num_questions: 5, }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Error ${response.status}: ${errorText}`); } const generatedQuestions = await response.json(); // Parsear las preguntas generadas if (Array.isArray(generatedQuestions) && generatedQuestions.length > 0) { const questionsToAdd: Question[] = generatedQuestions.map((q: any, idx: number) => ({ id: `q-${Date.now()}-${idx}`, section_id: undefined, question_order: questions.length + idx, question_type: q.question_type || 'multiple-choice', question_text: q.question_text || q.text, options: q.options || [], correct_answer: q.correct_answer || q.correct, explanation: q.explanation || '', points: q.points || 1, })); setQuestions([...questions, ...questionsToAdd]); alert(`Se generaron ${questionsToAdd.length} preguntas con IA`); } } catch (error) { console.error('AI generation error:', error); alert(`Error al generar preguntas con IA: ${error instanceof Error ? error.message : 'Verifica que Ollama esté configurado y el banco de preguntas MySQL tenga datos'}`); } finally { setGeneratingAI(false); } }; const handleDuplicateQuestion = (question: Question) => { const duplicate: Question = { ...question, id: `question-${Date.now()}`, question_order: questions.length, question_text: `${question.question_text} (copia)`, }; setQuestions([...questions, duplicate]); }; const handleUpdateQuestion = (questionId: string, updates: Partial) => { setQuestions(questions.map(q => q.id === questionId ? { ...q, ...updates } : q )); }; const handleRemoveQuestion = (questionId: string) => { setQuestions(questions.filter(q => q.id !== questionId)); if (expandedQuestion === questionId) { setExpandedQuestion(null); } }; const getQuestionTypeLabel = (type: QuestionType) => { const labels: Record = { 'multiple-choice': 'Opción Múltiple', 'true-false': 'Verdadero/Falso', 'short-answer': 'Respuesta Corta', 'essay': 'Ensayo', 'matching': 'Emparejamiento', 'ordering': 'Ordenar', }; return labels[type] || type; }; return (
{/* Header */}

Nueva Plantilla de Prueba

Crea preguntas y secciones para tu evaluación

{/* Form */}
{/* Basic Info */}

Información Básica

setFormData({ ...formData, name: e.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Ej: Final Exam - Beginner 1" required />