25 lines
7.3 KiB
JavaScript
Raw Normal View History

2025-12-14 20:53:27 -06:00
!function(t){"use strict";class e{
/**
* @constructor
* @param {Object} config - Configuración del cuestionario.
* @param {string} config.excelFileUrl - URL del archivo Excel que contiene las preguntas.
* @param {Object} [config.mandatoryFields] - Campos obligatorios en el archivo Excel para el cuestionario.
* @param {string} [config.mandatoryFields.question="pregunta"] - Nombre de la columna para el texto de la pregunta.
* @param {string} [config.mandatoryFields.correctOption="opcion_c"] - Nombre de la columna que identifica la respuesta correcta.
* @param {string} [config.mandatoryFields.correctFeedback="retroalimentacion_correcta"] - Nombre de la columna para la retroalimentación en respuestas correctas.
* @param {string} [config.mandatoryFields.incorrectFeedback="retroalimentacion_incorrecta"] - Nombre de la columna para la retroalimentación en respuestas incorrectas.
* @param {string} [config.optionPrefix="opcion"] - Prefijo utilizado para identificar las columnas de las opciones de respuesta.
* @param {boolean} [config.randomizeQuestions=false] - Indica si las preguntas deben presentarse en orden aleatorio.
* @param {boolean} [config.randomizeOptions=false] - Indica si las opciones dentro de una pregunta deben ordenarse aleatoriamente.
* @param {number} [config.passingScore=80] - Puntaje mínimo necesario para aprobar el cuestionario, expresado en porcentaje.
* @param {number} [config.defaultWeight=1] - Ponderación por defecto asignada a cada pregunta, si no se especifica en el archivo Excel.
* @param {number} [config.maxQuestions=Infinity] - Número máximo de preguntas a incluir en el cuestionario. Si no se especifica, se incluyen todas las preguntas.
* @param {number} [config.maxAttempts=Infinity] - Número máximo de intentos permitidos para completar el cuestionario. Por defecto, es infinito.
* @license Licencia Comercial Propietaria
* @version 1.0.0
* @author Salvador Martínez
* @copyright Este software está protegido por derechos de autor y es propiedad de E360 Digital Solutions S.A. de C.V.
* El uso está restringido a los términos y condiciones establecidos en el contrato de licencia.
* Para obtener una licencia y acceder a las características completas y soporte, contáctenos en licencias@espacio360.com.mx
*/
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),s=t[Object.keys(t)[0]];if(!Array.isArray(s))return;let r=e.processQuestions(s,this.config);this.config.randomizeQuestions&&(r=e.shuffle(r));const i=r.length;if(this.config.maxQuestions!==1/0&&this.config.maxQuestions>i)throw new Error(`El número máximo de preguntas especificado (${this.config.maxQuestions}) excede el total disponible (${i}).`);this.questions=this.config.maxQuestions===1/0?r:r.slice(0,this.config.maxQuestions)}static async readExcelFile(t){try{const e=await fetch(t),s=await e.arrayBuffer(),r=new Uint8Array(s),i=XLSX.read(r,{type:"array"}),n={};return i.SheetNames.forEach((t=>{const e=i.Sheets[t];n[t]=XLSX.utils.sheet_to_json(e)})),n}catch(t){throw t}}static processQuestions(t,s){return t.map((t=>{const r=s.mandatoryFields;if(!t[r.question])return null;if(!t[r.correctAnswers]&&!t[r.correctOption])return null;let i=Object.keys(t).filter((t=>t.startsWith(s.optionPrefix))).map(((e,s)=>{let i=!1;if(t[r.correctAnswers]){i=t[r.correctAnswers].toString().split(",").map((t=>parseInt(t.trim())-1)).includes(s)}else t[r.correctOption]&&(i=e===r.correctOption);return{id:e,text:t[e]?.trim(),isCorrect:i}}));const n=s.specialOptions||[];let o=[],c=[];i.forEach((t=>{n.some((e=>t.text.toLowerCase().includes(e.toLowerCase())))?o.push(t):c.push(t)})),s.randomizeOptions&&(c=e.shuffle(c)),i=[...c,...o];const a=Object.keys(t).filter((t=>!Object.values(r).includes(t)&&!t.startsWith(s.optionPrefix))).reduce(((e,s)=>{const r=null!==t[s]&&void 0!==t[s]?String(t[s]).trim():"";return e[s]=r,e}),{}),u=parseFloat(t.ponderacion)||s.defaultWeight;return{question:t[r.question].trim(),options:i,correctFeedback:t[r.correctFeedback]?.trim()||"",incorrectFeedback:t[r.incorrectFeedback]?.trim()||"",additionalInfo:a,weight:u}})).filter(Boolean)}static shuffle(t){for(let e=t.length-1;e>0;e--){const s=Math.floor(Math.random()*(e+1));[t[e],t[s]]=[t[s],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;const s=this.questions[t];s.answered||(s.answered=!0,s.isAnswerCorrect=e,e?(this.correctAnswersCount++,this.totalScore+=s.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),s=Math.round(100*this.totalScore/e),r=s>=this.config.passingScore;return{correct:this.correctAnswersCount,incorrect:this.incorrectAnswersCount,total:t,score:s,maxScore:e,passed:r,attempts:this.attempts,maxAttempts:this.config.maxAttempts}}generateSummaryFromSet(t){const e=Array.from(t),s=e.length,r=e.reduce(((t,e)=>t+(e.weight||1)),0),i=e.filter((t=>t.isAnswerCorrect)).length,n=s-i,o=e.reduce(((t,e)=>e.isAnswerCorrect?t+(e.weight||1):t),0),c=r>0?Math.round(100*o/r):0;return{correct:i,incorrect:n,total:s,score:c,maxScore:r,passed:c>=this.config.passingScore,attempts:this.attempts,maxAttempts:this.config.maxAttempts}}getRenderData(){const t=this.getCurrentQuestion();return t?{text:t.question,options:t.options.ma