|
|
|
|
constructor(t){this.config={excelFileUrl:t.excelFileUrl,mandatoryFields:t.mandatoryFields||{question:"pregunta",correctOption:"opcion_c",correctAnswers:"correctas",correctFeedback:"retroalimentacion_correcta",incorrectFeedback:"retroalimentacion_incorrecta"},optionPrefix:t.optionPrefix||"opcion",randomizeQuestions:t.randomizeQuestions||!1,randomizeOptions:t.randomizeOptions||!1,passingScore:t.passingScore||80,defaultWeight:t.defaultWeight||1,maxAttempts:t.maxAttempts||1/0,maxQuestions:t.maxQuestions||1/0},this.questions=[],this.currentQuestionIndex=0,this.correctAnswersCount=0,this.incorrectAnswersCount=0,this.totalScore=0,this.attempts=0}async loadQuestionsFromExcel(){const t=await e.readExcelFile(this.config.excelFileUrl),r=t[Object.keys(t)[0]];if(!Array.isArray(r))return void console.error("The selected sheet does not contain an array of data.");let s=e.processQuestions(r,this.config);this.config.randomizeQuestions&&(s=e.shuffle(s));const o=s.length;if(this.config.maxQuestions!==1/0&&this.config.maxQuestions>o)throw new Error(`El número máximo de preguntas especificado (${this.config.maxQuestions}) excede el total disponible (${o}).`);this.questions=this.config.maxQuestions===1/0?s:s.slice(0,this.config.maxQuestions)}static async readExcelFile(t){try{const e=await fetch(t),r=await e.arrayBuffer(),s=new Uint8Array(r),o=XLSX.read(s,{type:"array"}),n={};return o.SheetNames.forEach((t=>{const e=o.Sheets[t];n[t]=XLSX.utils.sheet_to_json(e)})),n}catch(t){throw console.error("Error reading Excel file:",t),t}}static processQuestions(t,r){return t.map((t=>{const s=r.mandatoryFields;if(!t[s.question])return console.error(`Missing mandatory field: '${s.question}'`),null;if(!t[s.correctAnswers]&&!t[s.correctOption])return console.error(`Missing correct answers field: need either '${s.correctAnswers}' or '${s.correctOption}'`),null;let o=Object.keys(t).filter((t=>t.startsWith(r.optionPrefix))).map(((e,r)=>{let o=!1;if(t[s.correctAnswers]){o=t[s.correctAnswers].toString().split(",").map((t=>parseInt(t.trim())-1)).includes(r)}else t[s.correctOption]&&(o=e===s.correctOption);return{id:e,text:t[e]?.trim(),isCorrect:o}}));const n=r.specialOptions||[];let i=[],c=[];o.forEach((t=>{n.some((e=>t.text.toLowerCase().includes(e.toLowerCase())))?i.push(t):c.push(t)})),r.randomizeOptions&&(c=e.shuffle(c)),o=[...c,...i];const a=Object.keys(t).filter((t=>!Object.values(s).includes(t)&&!t.startsWith(r.optionPrefix))).reduce(((e,r)=>{const s=null!==t[r]&&void 0!==t[r]?String(t[r]).trim():"";return e[r]=s,e}),{}),u=parseFloat(t.ponderacion)||r.defaultWeight;return{question:t[s.question].trim(),options:o,correctFeedback:t[s.correctFeedback]?.trim()||"",incorrectFeedback:t[s.incorrectFeedback]?.trim()||"",additionalInfo:a,weight:u}})).filter(Boolean)}static shuffle(t){for(let e=t.length-1;e>0;e--){const r=Math.floor(Math.random()*(e+1));[t[e],t[r]]=[t[r],t[e]]}return t}getCurrentQuestion(){return this.questions[this.currentQuestionIndex]}getNextQuestion(){return this.currentQuestionIndex<this.questions.length-1?(this.currentQuestionIndex++,this.questions[this.currentQuestionIndex]):null}answerQuestionById(t,e){if(t<0||t>=this.questions.length)return void console.error(`Invalid question ID: ${t}`);const r=this.questions[t];r.answered?console.warn(`Question ID ${t} has already been answered.`):(r.answered=!0,r.isAnswerCorrect=e,e?(this.correctAnswersCount++,this.totalScore+=r.weight):this.incorrectAnswersCount++)}answerCurrentQuestion(t){this.answerQuestionById(this.currentQuestionIndex,t)}answerCurrentQuestionById(t,e){this.answerQuestionById(t,e)}hasMoreQuestions(){return this.questions.filter((t=>t.answered)).length<this.questions.length}getSummary(){const t=this.questions.length,e=this.questions.reduce(((t,e)=>t+e.weight),0),r=Math.round(100*this.totalScore/e),s=r>=this.config.passingScore;return{correct:this.correctAnswersCount,incorrect:this.incorrectAnswersCount,total:t,score:r,maxScore:e,passed:s,attempts:this.attempts,maxAttempts:this.config.maxAttempts}}generateSummaryFromSet(t){const e=Array.from(t),r=e.length,s=e.reduce(((t,e)=>t+(e.weigh
|