├── AllTests.pdf ├── script automatico ├── .gitignore ├── README.md └── addQuestion.py ├── README.md ├── index.html ├── export └── export_html.py ├── style.css └── script.js /AllTests.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinBDev/DPPI_preguntator_2023/HEAD/AllTests.pdf -------------------------------------------------------------------------------- /script automatico/.gitignore: -------------------------------------------------------------------------------- 1 | **.copy.json 2 | Test - Tema ** 3 | **.cfg 4 | Scripts 5 | Lib 6 | Include -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DPPI_preguntator_2023 2 | 3 | App de tests para preparar el examen de DPPI 4 | 5 | - Autores: Martín Beltrán, Luis Vijande, Daniel Alvarez y Juan Torrente. 6 | 7 | # AGRADECIMIENTOS POR APORTACIONES # 8 | - Sara Fernandez Arias 9 | - Rosa García Lopez 10 | - Carlos Garriga Suarez 11 | - Jesus Alonso García 12 | - Diego Martín Fernandez 13 | - Joaquin Salustiano Britos Morales 14 | - Laís García Suarez 15 | - Laura Pernía Blanco 16 | - Mario Lombas Calderón de la Barca 17 | - Alejandro Alvarez Solís 18 | - Paula Suarez Prieto 19 | - Alvaro Rodriguez Gonzalez 20 | - Daniel Pascual 21 | - Pablo Diaz Rubio 22 | - Paula Puerta 23 | - Pablo Rodríguez Rodríguez 24 | - Stelian Adrian Stanci 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test Online 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Test Online

14 | 17 |
18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 |

Ir a pregunta

27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 | 36 |
37 |
38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /script automatico/README.md: -------------------------------------------------------------------------------- 1 | # Script Automático para añadir las preguntas 2 | ## Como funciona 3 | Muy simple, vete a los cuestionarios y descargalos, en firefox es con ctrl + s, no tengo ni idea como va en otros buscadores. 4 | Cuando lo descargues, guardalo en este carpeta, no se va a añadir al repo, no te preocupes. Una vez que tengas los cuestionarios descargados, solo tienes que usar el script, debería ir automático. Tampoco te preocupes por colisiones, el achivo se encarga de eso también. 5 | 6 | **FIREFOZ DESCARGA LOS FICHEROS CON EXTENSION .HTM; CHROME CON EXTENSION .HTML. EL SCRIPT USA HTML, RECOMENDAMOS CAMBIAR LA EXTENSIÓN DE LOS FICHEROS A .HTML O CAMBIAR LA LINEA 67 DEL SCRIPT PARA QUE LEA .HTM, LO QUE EL USUARIO QUIERA** 7 | 8 | ## Resultados 9 | El archivo **NO** reemplaza el questions.json original, crea una copia en esta carpeta llamada questions.copy.json. Una vez que haya confirmado que el script ha funcionado, puedes reemplazar el json original. Muchas gracias 10 | ## **⚠⚠IMPORTANTE⚠⚠** 11 | **NECESITAS BeautifullSoup para que esto funcione.** 12 | ```bash 13 | python -m pip install beautifulsoup4 14 | ``` 15 | Pon esto en la consola, y si tienes python metido en la ruta debería ir sin problemas. Si ves que te da error y no encuntra el módulo, haz lo siguiente: 16 | 17 | 1 - Entra en la carpeta del script 18 | 2 - Instala entorno virtual de python: 19 | ```bash 20 | python -m pip install --user virtualenv 21 | ``` 22 | 3 - Crea el entorno DENTRO DE LA CARPETA DEL SCRIPT 23 | ```bash 24 | python -m venv . 25 | ``` 26 | Importante incluir el punto "." 27 | 4 - Ejecuta el entorno: 28 | ```bash 29 | .\Scripts\activate 30 | ``` 31 | 5 - Instala de nuevo BeautifullSoup 32 | ```bash 33 | pip install beautifulsoup4 34 | ``` 35 | 36 | Ejecuta el script con los HTML en la carpeta (no les cambies el nombre): 37 | ```bash 38 | py .\addQuestion.py 39 | ``` -------------------------------------------------------------------------------- /script automatico/addQuestion.py: -------------------------------------------------------------------------------- 1 | import json 2 | from bs4 import BeautifulSoup 3 | import glob 4 | """ 5 | Un script para añadir y detectar colisiones directamente al json 6 | """ 7 | 8 | def cleanHtm(txt:str) -> str: 9 | return txt.replace('\t','').replace('\n',' ').replace(' ', ' ').strip() 10 | 11 | 12 | 13 | 14 | def parseHtm(htmFile:str) -> dict: 15 | with open(htmFile, 'r', encoding='utf-8') as f: 16 | htmlfile = f.read() 17 | soup = BeautifulSoup(htmlfile, 'html.parser') 18 | 19 | questions = [] 20 | for questionField in soup.find_all('div', {'class': 'formulation'}): 21 | question = { 22 | 'question': cleanHtm(questionField.find('div').text), 23 | 'options': [], 24 | 'answer': -1, 25 | 'explanation': '...' 26 | } 27 | correctTag = questionField.parent.find('div', {'class': 'rightanswer'}) 28 | correctText = cleanHtm(correctTag.text).replace('La respuesta correcta es: ','') 29 | # enumerar por preguntas 30 | for idx, answer in enumerate(questionField.find_all('div', {'data-region': 'answer-label'})): 31 | answerText = cleanHtm(answer.div.text) 32 | question['options'].append(answerText) 33 | if correctText == answerText: 34 | question['answer'] = idx 35 | 36 | questions.append(question) 37 | # conseguir retroalimentación 38 | feedback = questionField.parent.find('div', {'class': 'generalfeedback'}) 39 | if feedback: 40 | question['explanation'] = f'{cleanHtm(feedback.text)}\n{correctText}' 41 | 42 | return questions 43 | 44 | 45 | def mergeData(data:dict, topic:str, questions: dict): 46 | theme = data[topic - 1] 47 | 48 | for question in questions: 49 | # buscar colisiones 50 | repeated = False 51 | for originalQuestion in theme['questions']: 52 | # hay preguntas con el mismo enunciado pero distintas opcions, si la pregunta y la explicación es la misma, está repetida 53 | if question['question'] == originalQuestion['question']: 54 | if len(set(question['options'] + originalQuestion['options'])) == len(question['options']): 55 | repeated = True 56 | if not repeated: 57 | theme['questions'].append(question) 58 | 59 | return data 60 | 61 | 62 | 63 | def main(): 64 | # conseguir archivos 65 | htms = glob.glob('*.htm*') 66 | with open('../questions.json', 'r', encoding='utf-8') as f: 67 | data = json.loads(f.read()) 68 | 69 | for htm in htms: 70 | # conseguir tema 71 | topic = int(htm.split(' ')[3].replace('.','')) 72 | # conseguir preguntas 73 | questions = parseHtm(htm) 74 | # juntar datos 75 | data = mergeData(data, topic, questions) 76 | 77 | # guardar datos 78 | with open('questions.copy.json', 'w', encoding='utf-8') as f: 79 | f.write(json.dumps(data, ensure_ascii=False, indent=2)) 80 | 81 | 82 | if __name__ == '__main__': 83 | main() -------------------------------------------------------------------------------- /export/export_html.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def load_json(path): 4 | """ 5 | Loads json in the following form: 6 | 7 | [ 8 | { 9 | "topic": "Topic 1: title", 10 | "questions": [ 11 | { 12 | "question": "¿What is the result of 3*3?", 13 | "options": [ 14 | "2", 15 | "3", 16 | "9" 17 | ], 18 | "answer": 2, 19 | "explanation": "3 times 3 equals 9." 20 | }, 21 | {} 22 | ] 23 | }, 24 | { 25 | "topic": "Topic 2: title", 26 | "questions": [] 27 | } 28 | ] 29 | 30 | """ 31 | with open(path, 'r') as f: 32 | return json.load(f) 33 | 34 | 35 | def export_html(entries_json): 36 | html = """ 37 | 38 | 39 | Tesst DPPI 40 | 77 | 78 | 79 | """ 80 | 81 | 82 | for topic in entries_json: 83 | html += f"

{topic['topic']}

" 84 | for question in topic['questions']: 85 | html += f"

{question['question']}

" 86 | for i, option in enumerate(question['options']): 87 | if i == question['answer']: 88 | html += f"

{i+1}. {option}

" 89 | else: 90 | html += f"

{i+1}. {option}

" 91 | html += f"

Answer: {question['answer'] + 1}

" 92 | html += f"

Explanation: {question['explanation']}

" 93 | html += "
" 94 | 95 | html += "" 96 | 97 | with open('questionnaire.html', 'w') as f: 98 | f.write(html) 99 | 100 | def main(): 101 | debug = True 102 | 103 | 104 | #load json file 105 | entries = load_json('../questions.json') 106 | 107 | #print every entry in entries (para que el copilot se entere) 108 | if debug: 109 | for entry in entries: 110 | print(entry["topic"]) 111 | 112 | for question in entry["questions"]: 113 | print(question["question"]) 114 | print(question["options"]) 115 | print(question["answer"]) 116 | print(question["explanation"]) 117 | 118 | export_html(entries) 119 | if __name__ == '__main__': 120 | main() -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --c-app1 : #ffffff; 3 | --c-app2 : #dddddd; 4 | --c-btn1 : #007BFF; 5 | --c-btn2 : #0056b3; 6 | --c-main1 : #a1d5f8; 7 | --c-main2 : #8ccbf7; 8 | --c-correct : #5ce25c; 9 | --c-incorrect : #e25c5c; 10 | --c-background : #f4f4f4; 11 | --c-explanation1: #f8f8f0; 12 | --c-explanation2: #e8e8d0; 13 | --c-btn-disabled: #cccccc; 14 | } 15 | body { 16 | font-family: 'Arial', sans-serif; 17 | background-color: var(--c-background); 18 | color: #333; 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | #app { 24 | max-width: 45%; 25 | margin: 5px auto; 26 | padding: 20px; 27 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); 28 | border-radius: 10px; 29 | position: relative; 30 | min-height: 100vh; 31 | background-color: var(--c-app1); 32 | } 33 | 34 | #traversal { 35 | position: fixed; 36 | left: 5px; 37 | top: 5px; 38 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); 39 | border-radius: 10px; 40 | padding: 20px; 41 | min-height: 25vh; 42 | max-width: 18%; 43 | content: ""; 44 | background-color: #fff; 45 | 46 | } 47 | 48 | #traversal-container { 49 | display: flex; 50 | flex-wrap: wrap; 51 | justify-content: center; 52 | } 53 | 54 | .traversal-element { 55 | width: 2em; 56 | margin: 2px; 57 | height: 2em; 58 | border: 1px solid black; 59 | border-radius: .5rem; 60 | align-items: center; 61 | transition: all 300ms ease; /* Actualizado */ 62 | } 63 | 64 | .traversal-element:hover, .traversal-element:focus-within { 65 | transform: scale(1.02); 66 | background-color: var(--c-app2); 67 | } 68 | 69 | .traversal-button { 70 | width: 100%; 71 | height: 100%; 72 | background-color: var(--c-app); 73 | border-radius: .5rem; 74 | text-decoration: none; 75 | color: black; 76 | background-color: transparent; 77 | display: flex; 78 | align-items: center; 79 | } 80 | 81 | .traversal-text { 82 | display: inline-block; 83 | width: 100%; 84 | text-align: center; 85 | } 86 | 87 | .incorrect { 88 | background-color: var(--c-incorrect); 89 | } 90 | 91 | .correct { 92 | background-color: var(--c-correct); 93 | } 94 | .answered { 95 | background-color: var(--c-main1); 96 | } 97 | 98 | h1 { 99 | text-align: center; 100 | color: #333; 101 | padding-bottom: 20px; 102 | border-bottom: 1px solid var(--c-app2); 103 | } 104 | 105 | #topic-selector { 106 | width: 100%; 107 | margin: 20px 0; 108 | padding: 10px; 109 | border: 1px solid var(--c-app2); 110 | border-radius: 5px; 111 | font-size: 16px; 112 | background-color: var(--c-app1); 113 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 114 | } 115 | 116 | #question-container { 117 | margin-top: 20px; 118 | } 119 | 120 | .question { 121 | margin-top: 10px; 122 | padding-bottom: 5px; 123 | position: relative; 124 | } 125 | 126 | ul { 127 | margin: 0; 128 | } 129 | 130 | .question::before { 131 | position: absolute; 132 | content: ""; 133 | width: 100%; 134 | bottom: 0; 135 | left: 0; 136 | height: 2px; 137 | background-color: var(--c-app2); 138 | } 139 | 140 | .question label { 141 | display: block; 142 | margin-bottom: 10px; 143 | font-size: 18px; 144 | } 145 | 146 | .question ul { 147 | list-style-type: none; 148 | padding: 0; 149 | } 150 | 151 | .answer { 152 | display: flex; /* Nuevo */ 153 | align-items: center; /* Nuevo */ 154 | margin-bottom: 10px; 155 | background-color: var(--c-main1); 156 | padding: 10px; 157 | border-radius: 5px; 158 | cursor: pointer; 159 | transition: background-color 300ms ease, transform 300ms ease; /* Actualizado */ 160 | } 161 | 162 | .answer:hover, .answer:focus-within { 163 | background-color: var(--c-main2); 164 | transform: scale(1.005); /* Nuevo */ 165 | } 166 | 167 | .answer label { 168 | pointer-events: none; /* Nuevo */ 169 | margin-left: 10px; /* Nuevo */ 170 | margin: 0; /* Nuevo */ 171 | padding: 0; /* Nuevo */ 172 | display: flex; /* Nuevo */ 173 | align-items: center; /* Nuevo */ 174 | } 175 | 176 | 177 | .answer input { 178 | margin-right: 10px; 179 | margin: 10px; 180 | } 181 | 182 | #buttons-container { 183 | display: flex; 184 | flex-direction: row; 185 | position: fixed; 186 | bottom: 0; 187 | right: 0; 188 | padding: 20px; 189 | border-top-left-radius: 10px; 190 | background: transparent; 191 | } 192 | 193 | /* Estilo para el contenedor del switch */ 194 | .switch-container { 195 | position: fixed; 196 | bottom: 20px; 197 | left: 20px; 198 | background-color: #f5f5f5; 199 | padding: 10px 20px; 200 | border-radius: 20px; 201 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); 202 | display: flex; 203 | align-items: center; 204 | justify-content: space-between; 205 | z-index: 1000; 206 | max-width:20%; 207 | } 208 | 209 | /* Estilo para la etiqueta del switch */ 210 | .switch-container label { 211 | font-size: 16px; 212 | color: #333; 213 | margin-right: 10px; 214 | } 215 | 216 | /* Estilo para el switch */ 217 | .switch-container .switch { 218 | position: relative; 219 | display: inline-block; 220 | width: 60px; 221 | height: 34px; 222 | } 223 | 224 | /* Estilo para el slider del switch */ 225 | .switch-container .switch input { 226 | opacity: 0; 227 | width: 0; 228 | height: 0; 229 | } 230 | 231 | .switch-container .slider { 232 | position: absolute; 233 | cursor: pointer; 234 | top: 0; 235 | left: 0; 236 | right: 0; 237 | bottom: 0; 238 | background-color: #ccc; 239 | transition: .4s; 240 | border-radius: 34px; 241 | } 242 | 243 | .switch-container .slider:before { 244 | position: absolute; 245 | content: ""; 246 | height: 26px; 247 | width: 26px; 248 | left: 4px; 249 | bottom: 4px; 250 | background-color: white; 251 | transition: .4s; 252 | border-radius: 50%; 253 | } 254 | 255 | .switch-container input:checked + .slider { 256 | background-color: #2196F3; 257 | } 258 | 259 | .switch-container input:checked + .slider:before { 260 | transform: translateX(26px); 261 | } 262 | 263 | /* Estilo para el slider del switch - versión redondeada */ 264 | .switch-container .slider.round { 265 | border-radius: 34px; 266 | } 267 | 268 | .switch-container .slider.round:before { 269 | border-radius: 50%; 270 | } 271 | 272 | #scroll-to-top-button { 273 | display: none; 274 | align-self: right; 275 | } 276 | 277 | button.btn { 278 | padding: 10px 20px; 279 | border: none; 280 | background-color: var(--c-btn1); 281 | color: white; 282 | font-size: 16px; 283 | border-radius: 5px; 284 | margin: 10px; 285 | cursor: pointer; 286 | transition: background-color 0.3s ease; 287 | } 288 | 289 | button.btn:hover, button.btn:focus-within { 290 | background-color: var(--c-btn2); 291 | } 292 | 293 | button.btn:disabled { 294 | background-color: var(--c-btn-disabled); 295 | } 296 | 297 | .explanation { 298 | background-color: var(--c-explanation1); 299 | padding: 10px; 300 | border-radius: 5px; 301 | margin-top: 10px; 302 | border-left: 5px solid var(--c-explanation2); 303 | } 304 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", () => { 2 | // Cargar preguntas 3 | fetch("questions.json") 4 | .then(response => response.json()) 5 | .then(data => { 6 | populateTopics(data); 7 | createMockExam(data); 8 | loadQuestions(data[0].topic); 9 | loadTraversal(data[0].topic); 10 | }); 11 | 12 | // Escuchar evento de cambio en el selector de temas 13 | const topicSelector = document.getElementById("topic-selector"); 14 | topicSelector.addEventListener("change", (event) => { 15 | loadQuestions(event.target.value); 16 | // al cambiar de topic, asegurarse que el botón cambie de estado si se ha usado 17 | hideAnswers(); 18 | loadTraversal(event.target.value); 19 | }); 20 | 21 | // Escuchar evento de click en el botón de enviar 22 | const submitButton = document.getElementById("submit-button"); 23 | submitButton.addEventListener("click", () => { 24 | // Aquí va la lógica para validar las respuestas 25 | }); 26 | 27 | const viewAnswersButton = document.getElementById("view-answers-button"); 28 | viewAnswersButton.addEventListener("click", () => { 29 | viewAnswers(); 30 | }); 31 | 32 | 33 | 34 | }); 35 | 36 | 37 | let instantFeedback = false; 38 | const switchButton = document.getElementById('switch'); 39 | const switchInput = document.getElementById('instant-feedback-switch'); 40 | switchButton.onclick = function() { 41 | if (switchInput.checked === false) { 42 | console.log('checked'); 43 | document.documentElement.setAttribute('data-theme', 'dark'); 44 | switchInput.checked = true; 45 | updateInstantFeedback(true); 46 | } else { 47 | console.log('not checked'); 48 | document.documentElement.setAttribute('data-theme', 'light'); 49 | switchInput.checked = false; 50 | updateInstantFeedback(false); 51 | } 52 | } 53 | function calculateScore() { 54 | const selectedTopic = questionsData.find(q => q.topic === document.getElementById("topic-selector").value); 55 | const totalQuestions = selectedTopic.questions.length; 56 | const pointsPerQuestion = 10 / totalQuestions; 57 | let score = 0; 58 | 59 | selectedTopic.questions.forEach((question, index) => { 60 | const selectedOption = document.querySelector(`input[name="question-${index}"]:checked`); 61 | 62 | if (selectedOption) { 63 | const selectedIndex = parseInt(selectedOption.value); 64 | 65 | if (selectedIndex === question.answer) { 66 | score += pointsPerQuestion; 67 | } else { 68 | score -= pointsPerQuestion / totalQuestions; 69 | } 70 | } 71 | }); 72 | 73 | // Asegurarse de que la nota esté en el rango de 0 a 10 74 | score = Math.min(Math.max(score, 0), 10); 75 | alert(`Tu nota es: ${score.toFixed(2)}`); 76 | } 77 | 78 | // Escuchar evento de click en el botón de enviar 79 | const submitButton = document.getElementById("submit-button"); 80 | submitButton.addEventListener("click", () => { 81 | calculateScore(); 82 | displayResults(); 83 | }); 84 | 85 | let questionsData = []; 86 | 87 | function populateTopics(questions) { 88 | questionsData = questions; 89 | const topicSelector = document.getElementById("topic-selector"); 90 | questions.forEach(questionSet => { 91 | const option = document.createElement("option"); 92 | option.value = questionSet.topic; 93 | option.textContent = questionSet.topic; 94 | topicSelector.appendChild(option); 95 | }); 96 | } 97 | 98 | function displayResults() { 99 | const selectedTopic = questionsData.find(q => q.topic === document.getElementById("topic-selector").value); 100 | 101 | selectedTopic.questions.forEach((question, index) => { 102 | const selectedOption = document.querySelector(`input[name="question-${index}"]:checked`); 103 | const questionDiv = document.querySelector(`.question[data-index="${index}"]`); 104 | const traversalElement = document.querySelector(`.traversal-element[data-index="${index}"]`) 105 | if (selectedOption) { 106 | const selectedIndex = parseInt(selectedOption.value); 107 | const optionItem = selectedOption.parentElement; 108 | 109 | if (selectedIndex === question.answer) { 110 | optionItem.style.backgroundColor = "var(--c-correct)"; 111 | traversalElement.className = 'traversal-element correct' 112 | } else { 113 | optionItem.style.backgroundColor = "var(--c-incorrect)"; 114 | traversalElement.className = 'traversal-element incorrect' 115 | } 116 | } 117 | 118 | const explanation = document.createElement("p"); 119 | explanation.className = "explanation"; 120 | explanation.textContent = question.explanation; 121 | //check if the explanation is already displayed 122 | if(questionDiv.lastChild.className != "explanation"){ 123 | 124 | questionDiv.appendChild(explanation); 125 | } 126 | }); 127 | } 128 | 129 | 130 | function loadTraversal(topic) { 131 | // obtener los contenedores 132 | const traversalContainer = document.getElementById('traversal-container') 133 | // Vaciar los contenedores 134 | traversalContainer.innerHTML = ''; 135 | 136 | const selectedTopic = questionsData.find(q => q.topic === topic) 137 | selectedTopic.questions.forEach((question, index) => { 138 | // populate traversal 139 | const traversalDiv = document.createElement('div') 140 | const traversalA = document.createElement('a') 141 | const traversalText = document.createElement('span') 142 | 143 | traversalDiv.className = 'traversal-element' 144 | traversalDiv.dataset.index = index 145 | 146 | traversalA.className = 'traversal-button' 147 | traversalA.href = `#q_${index}` 148 | 149 | traversalText.textContent = index+1 150 | traversalText.classList = 'traversal-text' 151 | 152 | traversalA.appendChild(traversalText) 153 | traversalDiv.appendChild(traversalA) 154 | traversalContainer.appendChild(traversalDiv) 155 | }) 156 | } 157 | 158 | function loadQuestions(topic) { 159 | // obtener los contenedores 160 | const questionContainer = document.getElementById("question-container"); 161 | // Vaciar los contenedores 162 | questionContainer.innerHTML = ""; 163 | 164 | const selectedTopic = questionsData.find(q => q.topic === topic); 165 | selectedTopic.questions.forEach((question, index) => { 166 | const questionDiv = document.createElement("div"); 167 | questionDiv.classList.add("question"); 168 | // add unique index per question 169 | questionDiv.id = `q_${index}` 170 | questionDiv.dataset.index = index; 171 | 172 | const questionLabel = document.createElement("label"); 173 | questionLabel.textContent = `${index + 1}. ${question.question}`; 174 | questionDiv.appendChild(questionLabel); 175 | 176 | 177 | const optionList = document.createElement("ul"); 178 | question.options.forEach((option, optionIndex) => { 179 | const optionItem = document.createElement("li"); 180 | optionItem.className = "answer" 181 | 182 | 183 | const optionInput = document.createElement("input"); 184 | optionInput.type = "radio"; 185 | optionInput.name = `question-${index}`; 186 | optionInput.value = optionIndex; 187 | optionInput.id = `question-${index}-${optionIndex}`; 188 | 189 | const optionLabel = document.createElement("label"); 190 | optionLabel.textContent = option; 191 | optionLabel.htmlFor = `question-${index}-${optionIndex}`; 192 | 193 | optionItem.appendChild(optionInput); 194 | optionItem.appendChild(optionLabel); 195 | optionList.appendChild(optionItem); 196 | 197 | optionItem.addEventListener("click", (event) => { 198 | if(event.target !== optionInput) { 199 | optionInput.click(); 200 | if(!switchInput.checked) { 201 | document.querySelector(`.traversal-element[data-index="${index}"]`).classList.add('answered') 202 | } 203 | } 204 | }); 205 | 206 | }); 207 | questionDiv.appendChild(optionList); 208 | questionContainer.appendChild(questionDiv); 209 | }); 210 | } 211 | 212 | //Marks the correct answers and displays the explanation 213 | function viewAnswers(){ 214 | const selectedTopic = questionsData.find(q => q.topic === document.getElementById("topic-selector").value); 215 | 216 | selectedTopic.questions.forEach((question, index) => { 217 | const questionDiv = document.querySelector(`.question[data-index="${index}"]`); 218 | const explanation = document.createElement("p"); 219 | explanation.className = "explanation"; 220 | 221 | explanation.textContent = question.explanation; 222 | if(questionDiv.lastChild.className != "explanation"){ 223 | questionDiv.appendChild(explanation); 224 | } 225 | 226 | //Marks the correct answer 227 | const correctOption = document.getElementById(`question-${index}-${question.answer}`); 228 | const correctOptionItem = correctOption.parentElement; 229 | correctOptionItem.style.backgroundColor = "var(--c-correct)"; 230 | correctOption.checked = true; 231 | 232 | }); 233 | 234 | //change the button to hide answers 235 | const viewAnswersButton = document.getElementById("view-answers-button"); 236 | viewAnswersButton.textContent = "Ocultar respuestas"; 237 | viewAnswersButton.removeEventListener("click", viewAnswers); 238 | viewAnswersButton.addEventListener("click", hideAnswers); 239 | } 240 | 241 | function hideAnswers() { 242 | const explanationList = document.querySelectorAll(".explanation"); 243 | explanationList.forEach(explanation => { 244 | explanation.remove(); 245 | }); 246 | // reset traversal 247 | const traversalList = document.querySelectorAll('.traversal-element') 248 | traversalList.forEach(traversal => { 249 | traversal.className = 'traversal-element' 250 | }) 251 | 252 | const inputs = document.querySelectorAll("input"); 253 | inputs.forEach(input => { 254 | input.checked = false; 255 | }); 256 | 257 | const optionItems = document.querySelectorAll(".answer"); 258 | optionItems.forEach(optionItem => { 259 | optionItem.style.backgroundColor = "#a1d5f8"; 260 | optionItem.style.color = "black"; 261 | }); 262 | 263 | //change the button to view answers 264 | const viewAnswersButton = document.getElementById("view-answers-button"); 265 | viewAnswersButton.textContent = "Ver respuestas"; 266 | viewAnswersButton.removeEventListener("click", hideAnswers); 267 | viewAnswersButton.addEventListener("click", viewAnswers); 268 | 269 | } 270 | 271 | function createMockExam(data) { 272 | let mockExamQuestions = []; 273 | const numOfQuestionsPerTopic = 5; 274 | 275 | data.forEach(topic => { 276 | const questions = topic.questions; 277 | for(let i = 0; i < numOfQuestionsPerTopic; i++) { 278 | const randomIndex = Math.floor(Math.random() * questions.length); 279 | mockExamQuestions.push(questions[randomIndex]); 280 | questions.splice(randomIndex, 1); // Removemos la pregunta seleccionada para evitar duplicados 281 | } 282 | }); 283 | 284 | shuffleArray(mockExamQuestions); // Mezcla las preguntas del simulacro de examen 285 | 286 | // Añadimos el nuevo tema de "Simulacro de examen" a los datos de las preguntas 287 | questionsData.push({ 288 | topic: "Simulacro de examen", 289 | questions: mockExamQuestions 290 | }); 291 | 292 | // Añadimos la opción de "Simulacro de examen" al selector de temas 293 | const topicSelector = document.getElementById("topic-selector"); 294 | const option = document.createElement("option"); 295 | option.value = "Simulacro de examen"; 296 | option.textContent = "Simulacro de examen"; 297 | topicSelector.appendChild(option); 298 | } 299 | 300 | function shuffleArray(array) { 301 | for (let i = array.length - 1; i > 0; i--) { 302 | const j = Math.floor(Math.random() * (i + 1)); 303 | [array[i], array[j]] = [array[j], array[i]]; // Intercambia elementos 304 | } 305 | } 306 | 307 | 308 | const scrollToTopButton = document.getElementById('scroll-to-top-button') 309 | scrollToTopButton.addEventListener('click', () => { 310 | window.scrollTo({ 311 | top: 0, 312 | left: 0, 313 | behavior: 'smooth' 314 | }) 315 | }) 316 | 317 | // Show or hide the button depending on the scroll position 318 | window.addEventListener('scroll', () => { 319 | if (window.scrollY > 1) { 320 | scrollToTopButton.style.display = 'block' 321 | } else { 322 | scrollToTopButton.style.display = 'none' 323 | } 324 | } 325 | ) 326 | 327 | 328 | function updateInstantFeedback(enabled) { 329 | const allOptionInputs = document.querySelectorAll("input[type='radio']"); 330 | allOptionInputs.forEach((input) => { 331 | if (enabled) { 332 | input.addEventListener("change", instantFeedbackHandler); 333 | } else { 334 | input.removeEventListener("change", instantFeedbackHandler); 335 | } 336 | }); 337 | } 338 | 339 | function instantFeedbackHandler(event) { 340 | const input = event.target; 341 | const questionIndex = parseInt(input.name.split("-")[1]); 342 | const selectedOptionIndex = parseInt(input.value); 343 | 344 | const selectedTopic = questionsData.find(q => q.topic === document.getElementById("topic-selector").value); 345 | const question = selectedTopic.questions[questionIndex]; 346 | 347 | const optionItem = input.parentElement; 348 | 349 | if (selectedOptionIndex === question.answer) { 350 | optionItem.style.backgroundColor = "var(--c-correct)"; 351 | document.querySelector(`.traversal-element[data-index="${questionIndex}"]`).classList.add('correct') 352 | } else { 353 | optionItem.style.backgroundColor = "var(--c-incorrect)"; 354 | document.querySelector(`.traversal-element[data-index="${questionIndex}"]`).classList.add('incorrect') 355 | } 356 | 357 | //explination 358 | const explanation = document.createElement("p"); 359 | explanation.className = "explanation"; 360 | explanation.textContent = question.explanation; 361 | let questionDiv = document.querySelector(`.question[data-index="${questionIndex}"]`); 362 | if(questionDiv.lastChild.className != "explanation"){ 363 | questionDiv.appendChild(explanation); 364 | } 365 | 366 | 367 | } --------------------------------------------------------------------------------