26 lines
7.8 KiB
JavaScript
Raw Permalink Normal View History

2025-12-11 09:09:57 -06:00
// @ts-nocheck
!function(t){"use strict";class e{
/**
2025-12-11 17:00:53 -06:00
* @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),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