YUPI CRM
🔔 Activez les rappels
/* Academy assessment engine: multi-question MCQ/true-false/text exams with timer and practical evidence. */ const academyRenderV12=window.renderAcademy; window.renderAcademy=async function(){if(navigator.onLine&&AUTH.user){try{const cloud=await yupiCloud('/academy/state');if(cloud?.courses){S.settings.courses=cloud.courses;await idb.put('settings',{key:'courses',value:cloud.courses,updatedAt:cloud.updatedAt||Date.now()})}}catch(e){console.warn('Academy cloud refresh deferred',e)}}const base=await academyRenderV12();const c=academyData(),f=fr(),items=[];(c.modules||[]).forEach((m,mi)=>(m.lessons||[]).forEach((l,li)=>{const a=l.assessment||l.test||{};if((a.questions||[]).length||l.practical?.enabled)items.push({m,l,mi,li,a})}));if(!items.length)return base;return base+`
🧪 ${f?'Examens complets & mises en pratique':'Full exams & practical assessments'}

${f?'Les évaluations chronométrées sont corrigées automatiquement lorsque c’est possible. Les réponses rédigées et les preuves pratiques peuvent être évaluées par Gemini si une connexion est disponible.':'Timed assessments are graded automatically where possible. Written answers and practical evidence can be evaluated by Gemini when online.'}

${items.map(({m,l,mi,li,a})=>`
${escx(academyText(m.title,f))} · ${escx(academyText(l.title,f))}
${(a.questions||[]).length} ${f?'questions':'questions'}${l.practical?.enabled?' · '+(f?'Épreuve pratique':'Practical task'):''}
${(a.questions||[]).length?``:''}${l.practical?.enabled?``:''}
`).join('')}
`}; function academyVal(v){if(v&&typeof v==='object')return academyText(v,fr());return String(v??'')} window.yupiAcademyStartExam=function(mi,li){const c=academyData(),m=c.modules[mi],l=m?.lessons?.[li];if(!l)return;const a=l.assessment||l.test||{},qs=Array.isArray(a.questions)?a.questions:[];if(!qs.length){toast(fr()?'Aucune question configurée.':'No questions configured.','warning');return}const f=fr(),mins=Math.max(0,Number(a.timerMinutes||a.timeLimitMinutes||10));let body=qs.map((q,i)=>{const type=String(q.type||'text').toLowerCase(),question=escx(academyText(q.question,f)||q.prompt||'');if(type==='mcq'||type==='multiple_choice'||type==='multiple-choice'||type==='truefalse'||type==='true_false'){let opts=q.options;if(opts&& !Array.isArray(opts))opts=academyText(opts,f);if(!Array.isArray(opts)&&type.includes('true'))opts=f?['Vrai','Faux']:['True','False'];if(!Array.isArray(opts))opts=[];return `
${i+1}. ${question}${opts.map((o,j)=>``).join('')}
`}return `
${i+1}. ${question}
`}).join('');openModal(`
${f?'Réponds à toutes les questions. Le chronomètre arrêtera automatiquement l’examen.':'Answer all questions. The timer will automatically submit the exam.'} ${mins?mins+':00':'—'}
${body}
`);window._yupiExamEnd=mins?Date.now()+mins*60000:0;if(window._yupiExamTimer)clearInterval(window._yupiExamTimer);if(mins){window._yupiExamTimer=setInterval(()=>{const left=Math.max(0,window._yupiExamEnd-Date.now()),el=document.getElementById('yupiExamClock');if(el)el.textContent=Math.floor(left/60000)+':'+String(Math.floor(left/1000)%60).padStart(2,'0');if(left<=0)window.yupiAcademySubmitExam(mi,li,true)},500)}}; window.yupiAcademyEndTimer=function(){if(window._yupiExamTimer)clearInterval(window._yupiExamTimer);window._yupiExamTimer=null}; window.yupiAcademySubmitExam=async function(mi,li,auto){if(window._yupiExamSubmitting)return;window._yupiExamSubmitting=true;yupiAcademyEndTimer();const c=academyData(),l=c.modules[mi]?.lessons?.[li],qs=(l?.assessment||l?.test||{}).questions||[],root=document.getElementById('yupiExamQuestions');if(!l||!root){window._yupiExamSubmitting=false;return}const answers=[],textQs=[];let points=0,max=0;qs.forEach((q,i)=>{const card=root.querySelector(`[data-exam-q="${i}"]`),type=String(q.type||'text').toLowerCase(),given=card?.querySelector(`input[name="exam_${i}"]:checked`)?.value||card?.querySelector(`[data-exam-answer="${i}"]`)?.value||'',expected=academyVal(q.answer??q.correctAnswer??'');const pts=Number(q.points||1);max+=pts;let local=false;if(['mcq','multiple_choice','multiple-choice','truefalse','true_false'].includes(type)){local=norm(given)===norm(expected)||((/^[0-9]+$/.test(expected))&&Number(expected)>=0&&String((Array.isArray(q.options)?q.options:[])[Number(expected)]||'')===given);if(local)points+=pts}else textQs.push({question:academyText(q.question,fr()),answer:given,expected,points:pts});answers.push({question:academyText(q.question,fr()),answer:given,expected,type,points:pts,localCorrect:local})});let textGrade='';if(textQs.length&&navigator.onLine){try{textGrade=await yupiAskAI((fr()?'Évalue les réponses rédigées de cet examen. Retourne une note totale pour ces questions sur ':'Grade the written responses for this exam. Return a total score for these questions out of ')+textQs.reduce((n,x)=>n+x.points,0)+'. Réponds avec score numérique puis justification brève. Données: ':' points. Return a numeric score first, then brief rationale. Data: ')+JSON.stringify(textQs),'','academy_grading');const match=textGrade.match(/(?:score|note)?\s*[:=]?\s*(\d+(?:\.\d+)?)/i);if(match)points+=Math.max(0,Math.min(textQs.reduce((n,x)=>n+x.points,0),Number(match[1])))}catch(e){textGrade=(fr()?'Correction IA indisponible : ':'AI grading unavailable: ')+e.message}}const pct=max?Math.round(points/max*100):0;try{await academySaveProgress(String(m.id||mi),String(l.id||li),pct)}catch{}const result=document.getElementById('yupiExamResult');if(result)result.innerHTML=`
${auto?(fr()?'Temps écoulé. ':'Time is up. '):''}${fr()?'Résultat':'Result'}: ${pct}% (${points}/${max})

${pct>=Number((l.assessment||l.test||{}).passScore||70)?(fr()?'Réussi':'Passed'):(fr()?'À retravailler':'Needs review')}

${textGrade?`
${escx(textGrade)}
`:''}
`;const btn=document.querySelector('#yupiExamResult')?.previousElementSibling;if(btn)btn.disabled=true;window._yupiExamSubmitting=false;}; window.yupiAcademyPractical=function(mi,li){const c=academyData(),m=c.modules[mi],l=m?.lessons?.[li],p=l?.practical;if(!p?.enabled)return;const f=fr(),instructions=academyText(p.instructions,f)||academyText(l.description,f)||'';openModal(`
${escx(academyText(m.title,f))} — ${escx(academyText(l.title,f))}

${escx(instructions)}

${f?'Objectif':'Target'}: ${escx(p.metric||'')}${p.target!=null?' · '+escx(p.target):''}

`)}; window.yupiAcademyGradePractical=async function(mi,li){const c=academyData(),m=c.modules[mi],l=m?.lessons?.[li],p=l?.practical;if(!l||!p)return;const evidence=document.getElementById('yupiPracticalEvidence')?.value.trim(),out=document.getElementById('yupiPracticalResult');if(!evidence)return toast(fr()?'Décris ta pratique avant de soumettre.':'Describe your practical work first.','warning');out.textContent='🤖 …';try{const prompt=(fr()?'Évalue cette épreuve pratique de formation. Instructions: ':'Evaluate this training practical task. Instructions: ')+academyText(p.instructions,fr())+'\nCritère: '+(p.metric||'')+'; cible: '+(p.target||'')+'\nPreuve de l’apprenant: '+evidence+'\nDonne une note de 0 à 100, les points forts, les lacunes et la prochaine action. N’invente pas de preuves.';const scoreText=await yupiAskAI(prompt,null,'academy_practical_grading');out.innerHTML=`
${escx(scoreText)}
`;const mm=scoreText.match(/(?:score|note)\D{0,10}(\d{1,3})\s*%?/i);if(mm)await academySaveProgress(String(m.id||mi),String(l.id||li),Math.min(100,Number(mm[1])))}catch(e){out.textContent=(fr()?'Évaluation impossible : ':'Evaluation failed: ')+e.message}};