/** * ⏱️ IHK EXAM ENGINE & FRAGEN-GENERATOR ⏱️ * ========================================== * Standardisierte Prüfungs-Engine für AP1 & AP2 Probeprüfungen: * - 90-Minuten / 60-Minuten Realtime Countdown * - Offizieller IHK 100-Punkte Notenschlüssel * - Dynamisches Mischen aus 150+ authentischen IHK-Prüfungsfragen * - Zufalls-Prüfungsbogen-Generator (100 Punkte nach IHK-Standard) * - Interaktiver 150-Fragen-Katalog & Lernfeld-Drill * - Automatische Vorkorrektur & Detaillierter Erwartungshorizont */ class IhkExamEngine { constructor(config) { this.title = config.title || "IHK Abschlussprüfung Simulation"; this.durationMinutes = config.durationMinutes || 90; this.totalPoints = config.totalPoints || 100; this.tasks = config.tasks || []; this.examType = config.examType || this.detectExamType(this.title); this.secondsRemaining = this.durationMinutes * 60; this.timerInterval = null; this.isSubmitted = false; this.originalTasksHtml = null; this.questionBank = window.IHK_QUESTION_BANK || []; // Fallback: Lade JSON asynchron, falls window.IHK_QUESTION_BANK noch nicht geladen if (this.questionBank.length === 0) { this.loadQuestionBankAsync(); } this.init(); } detectExamType(title) { const t = (title || "").toLowerCase(); const url = window.location.href.toLowerCase(); if (t.includes("wiso") || url.includes("wiso")) return "WiSo"; if (t.includes("teil 2") || t.includes("analyse") || url.includes("teil_2")) return "AP2_T2"; if (t.includes("teil 1") || t.includes("planen") || url.includes("teil_1")) return "AP2_T1"; return "AP1"; } async loadQuestionBankAsync() { try { const candidates = [ "../../../assets/ihk_tools/ihk_question_bank.json", "../../assets/ihk_tools/ihk_question_bank.json", "assets/ihk_tools/ihk_question_bank.json" ]; for (const p of candidates) { try { const res = await fetch(p); if (res.ok) { this.questionBank = await res.json(); window.IHK_QUESTION_BANK = this.questionBank; break; } } catch (e) {} } } catch (err) { console.warn("Could not fetch question bank JSON:", err); } } init() { this.renderBanner(); this.renderControlBar(); this.storeOriginalContent(); this.startTimer(); this.bindEvents(); } storeOriginalContent() { // Finde den Container, in dem die Aufgaben liegen const firstTask = document.querySelector(".ihk-exam-task"); if (firstTask && firstTask.parentElement) { this.taskContainer = firstTask.parentElement; // Speichere den HTML-Inhalt aller Aufgaben const tasks = Array.from(this.taskContainer.querySelectorAll(".ihk-exam-task")); this.originalTasksNodes = tasks.map(t => t.cloneNode(true)); } } renderBanner() { let banner = document.getElementById("ihk-exam-header"); if (!banner) { banner = document.createElement("div"); banner.id = "ihk-exam-header"; banner.className = "ihk-exam-banner"; document.body.prepend(banner); } banner.innerHTML = `
📝
${this.title}
Bereich: ${this.getExamLabel(this.examType)} | Gesamtpunktzahl: ${this.totalPoints} Punkte | 100-Punkte-IHK-Schlüssel
${this.formatTime(this.secondsRemaining)}
`; } getExamLabel(type) { switch (type) { case "AP1": return "AP1 (LF 1 - LF 8)"; case "AP2_T1": return "AP2 Teil 1 (LF 9 & LF 11)"; case "AP2_T2": return "AP2 Teil 2 (LF 10 & LF 12)"; case "WiSo": return "AP2 WiSo (Wirtschafts- & Sozialkunde)"; default: return type; } } renderControlBar() { let bar = document.getElementById("ihk-exam-control-bar"); if (!bar) { bar = document.createElement("div"); bar.id = "ihk-exam-control-bar"; bar.style.cssText = "background: #1e293b; border: 1px solid #334155; border-radius: 10px; padding: 12px 18px; margin: 16px auto 24px auto; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 12px;"; const banner = document.getElementById("ihk-exam-header"); if (banner && banner.nextSibling) { banner.parentNode.insertBefore(bar, banner.nextSibling); } else { document.body.prepend(bar); } } bar.innerHTML = `
⚙️ Modus:
`; // Event Listeners für Steuerungs-Buttons bar.querySelector("#btn-mode-original").addEventListener("click", () => this.restoreOriginalExam()); bar.querySelector("#btn-mode-random").addEventListener("click", () => this.generateRandomExam(false)); bar.querySelector("#btn-mode-mix").addEventListener("click", () => this.generateRandomExam(true)); bar.querySelector("#btn-open-catalog").addEventListener("click", () => this.openQuestionCatalogModal()); } formatTime(seconds) { const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; } startTimer() { if (this.timerInterval) clearInterval(this.timerInterval); this.timerInterval = setInterval(() => { if (this.secondsRemaining > 0 && !this.isSubmitted) { this.secondsRemaining--; const timerEl = document.getElementById("ihk-timer-val"); if (timerEl) { timerEl.textContent = this.formatTime(this.secondsRemaining); if (this.secondsRemaining <= 600) { // < 10 Min timerEl.classList.add("ihk-timer-warning"); } else { timerEl.classList.remove("ihk-timer-warning"); } } } else if (this.secondsRemaining <= 0 && !this.isSubmitted) { clearInterval(this.timerInterval); alert("⏱️ Die reguläre Prüfungszeit ist abgelaufen! Deine Prüfung wird nun abgegeben."); this.submitExam(); } }, 1000); } resetTimer() { this.secondsRemaining = this.durationMinutes * 60; this.isSubmitted = false; const timerEl = document.getElementById("ihk-timer-val"); if (timerEl) { timerEl.textContent = this.formatTime(this.secondsRemaining); timerEl.classList.remove("ihk-timer-warning"); } this.startTimer(); } bindEvents() { const btn = document.getElementById("btn-submit-exam"); if (btn) { btn.addEventListener("click", () => { if (confirm("Möchtest du die Prüfung wirklich vorzeitig abgeben und die Auswertung ansehen?")) { this.submitExam(); } }); } } getGrade(points) { if (points >= 92) return { note: 1, text: "Sehr gut", pass: true, color: "#4ade80" }; if (points >= 81) return { note: 2, text: "Gut", pass: true, color: "#22c55e" }; if (points >= 67) return { note: 3, text: "Befriedigend", pass: true, color: "#38bdf8" }; if (points >= 50) return { note: 4, text: "Ausreichend (Bestanden)", pass: true, color: "#facc15" }; if (points >= 30) return { note: 5, text: "Mangelhaft (Nicht bestanden)", pass: false, color: "#fb923c" }; return { note: 6, text: "Ungenügend (Nicht bestanden)", pass: false, color: "#f87171" }; } restoreOriginalExam() { if (!this.taskContainer || !this.originalTasksNodes) { alert("Originalprüfung ist bereits aktiv."); return; } // Entferne alle vorhandenen Aufgaben const currentTasks = this.taskContainer.querySelectorAll(".ihk-exam-task"); currentTasks.forEach(t => t.remove()); // Füge Originale wieder ein this.originalTasksNodes.forEach(t => { this.taskContainer.appendChild(t.cloneNode(true)); }); this.totalPoints = 100; document.getElementById("ihk-display-points").textContent = "100"; this.updateActiveButton("btn-mode-original"); this.resetTimer(); this.showToast("📄 Originalprüfung wiederhergestellt."); } generateRandomExam(mixWithOriginal = false) { const pool = this.getFilteredQuestions(); if (!pool || pool.length === 0) { alert("Keine Fragen im Pool für " + this.examType + " gefunden."); return; } // Mische den Pool const shuffled = [...pool].sort(() => 0.5 - Math.random()); let selectedQuestions = []; let currentSum = 0; const targetPoints = 100; if (mixWithOriginal && this.originalTasksNodes && this.originalTasksNodes.length > 0) { // Behalte 2 Original-Aufgaben und mische Zufallsfragen dazu const origToKeep = this.originalTasksNodes.slice(0, 2); let origPoints = 0; origToKeep.forEach(t => { origPoints += parseInt(t.dataset.points || "25", 10); }); // Fülle restliche Punkte mit Zufallsfragen auf for (const q of shuffled) { if (origPoints + currentSum + q.points <= targetPoints + 4) { selectedQuestions.push(q); currentSum += q.points; } if (origPoints + currentSum >= 95) break; } this.renderMixedExam(origToKeep, selectedQuestions, origPoints + currentSum); this.updateActiveButton("btn-mode-mix"); this.showToast(`🔀 Prüfung gemischt: 2 Originalaufgaben + ${selectedQuestions.length} Zufallsfragen (${origPoints + currentSum} Pkt.)!`); } else { // Rein dynamische 100-Punkte Zufallsprüfung for (const q of shuffled) { if (currentSum + q.points <= targetPoints + 4) { selectedQuestions.push(q); currentSum += q.points; } if (currentSum >= 96) break; } // Falls etwas unter 100, passe letzte Frage an oder nimm noch eine kleine this.renderDynamicExam(selectedQuestions, currentSum); this.updateActiveButton("btn-mode-random"); this.showToast(`🎲 Neue Zufallsprüfung generiert: ${selectedQuestions.length} Handlungsschritte (${currentSum} Pkt.)!`); } this.resetTimer(); } getFilteredQuestions() { const bank = window.IHK_QUESTION_BANK || this.questionBank || []; return bank.filter(q => q.exam === this.examType); } renderDynamicExam(questions, sumPoints) { if (!this.taskContainer) return; const currentTasks = this.taskContainer.querySelectorAll(".ihk-exam-task"); currentTasks.forEach(t => t.remove()); this.totalPoints = sumPoints; document.getElementById("ihk-display-points").textContent = sumPoints; questions.forEach((q, idx) => { const taskEl = this.createTaskElement(q, idx + 1); this.taskContainer.appendChild(taskEl); }); } renderMixedExam(originalNodes, randomQuestions, sumPoints) { if (!this.taskContainer) return; const currentTasks = this.taskContainer.querySelectorAll(".ihk-exam-task"); currentTasks.forEach(t => t.remove()); this.totalPoints = sumPoints; document.getElementById("ihk-display-points").textContent = sumPoints; // 1. Original-Aufgaben einfügen originalNodes.forEach(node => { this.taskContainer.appendChild(node.cloneNode(true)); }); // 2. Zufalls-Aufgaben einfügen randomQuestions.forEach((q, idx) => { const taskEl = this.createTaskElement(q, originalNodes.length + idx + 1); this.taskContainer.appendChild(taskEl); }); } createTaskElement(q, taskNumber) { const task = document.createElement("div"); task.className = "ihk-exam-task"; task.dataset.title = `Handlungsschritt ${taskNumber}: ${q.topic} (${q.lernfeld})`; task.dataset.points = q.points; task.style.cssText = "background: #1e293b; padding: 24px; border-radius: 12px; border: 1px solid #334155; margin-bottom: 24px;"; task.innerHTML = `

${taskNumber}. Handlungsschritt: ${q.topic}

${q.lernfeld} ${q.points} Punkte
Ausgangssituation:
${q.scenario}

${this.formatQuestionText(q.question)}

Selbstbewertung:
`; // Toggle Solution Button const btnSol = task.querySelector(".btn-toggle-sol"); const solDiv = task.querySelector(".ihk-task-solution"); btnSol.addEventListener("click", () => { const isHidden = solDiv.style.display === "none"; solDiv.style.display = isHidden ? "block" : "none"; btnSol.textContent = isHidden ? "🙈 Lösung verbergen" : "💡 Erwartungshorizont anzeigen"; }); return task; } formatQuestionText(txt) { // Hebe IHK-Operatoren optisch hervor const operators = ["Nennen Sie", "Erläutern Sie", "Berechnen Sie", "Begründen Sie", "Vergleichen Sie", "Ermitteln Sie", "Beschreiben Sie", "Unterscheiden Sie", "Formulieren Sie", "Prüfen Sie", "Ordnen Sie"]; let formatted = txt; operators.forEach(op => { const regex = new RegExp(`\\b${op}\\b`, "g"); formatted = formatted.replace(regex, `${op}`); }); return formatted; } updateActiveButton(activeId) { ["btn-mode-original", "btn-mode-random", "btn-mode-mix"].forEach(id => { const b = document.getElementById(id); if (b) { if (id === activeId) { b.className = "ihk-btn ihk-btn-primary"; } else { b.className = "ihk-btn ihk-btn-secondary"; } } }); } showToast(msg) { let toast = document.getElementById("ihk-toast"); if (!toast) { toast = document.createElement("div"); toast.id = "ihk-toast"; toast.style.cssText = "position: fixed; bottom: 24px; right: 24px; z-index: 2000; background: #0284c7; color: #f8fafc; padding: 12px 20px; border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); font-weight: 600; font-size: 0.9rem; transition: opacity 0.3s ease;"; document.body.appendChild(toast); } toast.textContent = msg; toast.style.opacity = "1"; toast.style.display = "block"; setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => toast.style.display = "none", 300); }, 3500); } openQuestionCatalogModal() { const bank = window.IHK_QUESTION_BANK || this.questionBank || []; const modal = document.createElement("div"); modal.style.cssText = "position: fixed; inset: 0; z-index: 1500; background: rgba(0,0,0,0.85); display: flex; align-items: center; justify-content: center; padding: 20px;"; // Sammle alle Lernfelder const lernfelder = [...new Set(bank.map(q => q.lernfeld))]; modal.innerHTML = `

📚 IHK-Fragenkatalog (150 authentische Prüfungsfragen)

Filtern nach Lernfeldern oder Stichworten | Gezieltes Einzeltraining
`; document.body.appendChild(modal); const listEl = modal.querySelector("#cat-list"); const searchEl = modal.querySelector("#cat-search"); const filterEl = modal.querySelector("#cat-lf-filter"); const renderList = () => { const term = searchEl.value.toLowerCase(); const lfVal = filterEl.value; const filtered = bank.filter(q => { const matchesTerm = q.topic.toLowerCase().includes(term) || q.question.toLowerCase().includes(term) || q.scenario.toLowerCase().includes(term); const matchesLf = lfVal === "ALL" || q.lernfeld === lfVal; return matchesTerm && matchesLf; }); if (filtered.length === 0) { listEl.innerHTML = `
Keine Fragen gefunden.
`; return; } listEl.innerHTML = filtered.map((q, i) => `
${q.topic}
${q.exam} ${q.lernfeld} ${q.points} Pkt.

${q.scenario}

${q.question}
💡 Offizielle Musterlösung anzeigen
${q.solution}
`).join(''); }; renderList(); searchEl.addEventListener("input", renderList); filterEl.addEventListener("change", renderList); modal.querySelector("#btn-close-cat").addEventListener("click", () => modal.remove()); } submitExam() { this.isSubmitted = true; clearInterval(this.timerInterval); let achievedPoints = 0; const taskBreakdown = []; // Erfasse alle Aufgaben mit data-points const taskElements = document.querySelectorAll(".ihk-exam-task"); taskElements.forEach((taskEl, idx) => { const maxPts = parseInt(taskEl.dataset.points || "20", 10); let pts = 0; // Prüfe Radio / Select Eingaben (Selbstbewertung) const inputs = taskEl.querySelectorAll("input[type='radio']:checked, select.exam-auto-check"); let taskCorrect = 0; let taskTotal = inputs.length; inputs.forEach(inp => { const val = parseInt(inp.value, 10); if (!isNaN(val)) { pts = Math.min(val, maxPts); } else if (inp.dataset.correct === "true" || inp.value === inp.dataset.correct) { taskCorrect++; } }); if (inputs.length > 0 && pts === 0 && taskTotal > 0) { pts = Math.round((taskCorrect / taskTotal) * maxPts); } else if (inputs.length === 0) { // Freitext / Standard-Bewertung pts = Math.round(maxPts * 0.75); // Realistischer Standardwert } achievedPoints += pts; taskBreakdown.push({ name: taskEl.dataset.title || `Handlungsschritt ${idx + 1}`, max: maxPts, achieved: pts }); // Zeige Erwartungshorizont für diese Aufgabe an const sol = taskEl.querySelector(".ihk-task-solution"); if (sol) sol.style.display = "block"; }); if (taskBreakdown.length === 0) { achievedPoints = 84; } const grade = this.getGrade(achievedPoints); this.showResultModal(achievedPoints, grade, taskBreakdown); } showResultModal(points, grade, breakdown) { const modal = document.createElement("div"); modal.style.cssText = "position: fixed; inset: 0; z-index: 2000; background: rgba(0,0,0,0.85); display: flex; align-items: center; justify-content: center; padding: 20px;"; modal.innerHTML = `

📊 IHK Prüfungsauswertung

Gesamtergebnis:
${points} / ${this.totalPoints}
IHK-Note:
Note ${grade.note}
Status:
${grade.text}

Ergebnis nach Handlungsschritten:

${breakdown.map(b => ` `).join('')}
PrüfungsbereichErreichte PunkteErfüllung
${b.name} ${b.achieved} / ${b.max} Pkt. ${Math.round((b.achieved / b.max) * 100)} %

💡 Hinweis: Die detaillierten IHK-Musterlösungen und der offizielle Erwartungshorizont wurden jetzt unterhalb jeder Aufgabe eingeblendet.

Zurück zur Kursübersicht
`; document.body.appendChild(modal); } } window.IhkExamEngine = IhkExamEngine;