File: /home/storage/5/78/dd/wicomm2/public_html/blackfriday/assets/js/index.js
/* index.js – Quiz + integrações (PSI, OpenAI, VTEX) — robusto a HTML/erros e com fallback de rotas */
let currentQuestion = 0;
let answers = {}; // respostas do BLOCO ATUAL (muda depois da avaliação)
let baseAnswers = {}; // snapshot das 5 perguntas iniciais (preservado)
let initialCompleted = false;
let selectedDifficulty = null;
let questions = initialQuestions;
// ---- endpoints com fallback (api/* depois raiz /*) ----
const ENDPOINTS = {
startScan: ["api/start_scan.php"],
evaluate: ["api/evaluate.php"],
submit: ["api/submit.php"],
};
// ---- estado integrações ----
let scanId = null;
let startScanPromise = null;
let startScanFailed = false;
let isEvaluatingPromise = null;
let maturity = null; // "basico" | "intermediario" | "avancado"
let lighthouse = null; // { score, metrics, oportunidades }
let roadmap = []; // [{ area, melhorias[] }]
// Guarda a função de validação da pergunta atual (para exibir erros ao clicar "Próximo")
let currentValidator = null;
// >>> NOVO: guardamos o retorno estratégico detalhado e a explicação
let roadmapAI = []; // [{ titulo, por_que, acao_estrategica, kpi, impacto, prazo, area }]
let explanation = ""; // string
// ---- UI elements ----
const statusEl = document.getElementById("status");
const questionCounter = document.getElementById("question-counter");
const progressBar = document.getElementById("progress-bar");
const questionText = document.getElementById("question-text");
const optionsContainer = document.getElementById("options-container");
const prevButton = document.querySelector(".prev-button");
const prevBtn = document.getElementById("prev-btn");
const nextButton = document.querySelector(".next-button");
const nextBtn = document.getElementById("next-btn");
const modalContainer = document.querySelector(".modal-container");
const mainModalContainer = document.querySelector("#modal");
const finishModalContainer = document.querySelector("#finishModal");
const modalLoading = document.querySelector(".modal-loading");
const finishModalLoading = document.querySelector(".finish-modal-loading");
const loadingTip = document.querySelector(".loading-tip");
// =================== Utils HTTP ===================
async function postJSONWithFallback(urls, payload) {
let lastErr = null;
for (const url of urls) {
try {
const resp = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const ctype = resp.headers.get("content-type") || "";
const raw = await resp.text();
if (!resp.ok) {
lastErr = new Error(`HTTP ${resp.status} em ${url}: ${raw.slice(0, 300)}`);
continue;
}
// tenta JSON, se não for, erro com preview
try {
return JSON.parse(raw);
} catch (e) {
throw new Error(`Resposta não-JSON de ${url}: ${raw.slice(0, 300)}`);
}
} catch (e) {
lastErr = e;
// tenta próxima rota
}
}
throw lastErr || new Error("Falha ao contatar servidor");
}
function getRandomTip() {
const randomIndex = Math.floor(Math.random() * tips.length)
return tips[randomIndex]
}
// =================== Modal toggle / reset ===================
window.tooglemodal = function tooglemodal() {
if (!modalContainer) return;
const showing = modalContainer.style.display === "block";
modalContainer.style.display = showing ? "none" : "block";
if (!showing) {
resetQuiz();
loadQuestion(currentQuestion);
}
};
// =================== Helpers de UI ===================
function setProgress(pct) { if (progressBar) progressBar.style.width = `${pct}%`; }
function setStatus(text) { if (statusEl) statusEl.textContent = text; }
function setCounter(text) { if (questionCounter) questionCounter.textContent = text; }
// ---- Visual validation helpers (non-intrusive) ----
function markInvalid(el) { if (el) el.classList.add('input-required'); }
function clearInvalid(el) { if (el) el.classList.remove('input-required'); }
function showFieldError(container, msg) {
if (!container) return;
let tip = container.querySelector('.field-error-msg');
if (!tip) {
tip = document.createElement('div');
tip.className = 'field-error-msg';
container.appendChild(tip);
}
tip.textContent = msg;
}
function hideFieldError(container) {
const tip = container ? container.querySelector('.field-error-msg') : null;
if (tip) tip.remove();
}
// =================== Acesso a e-mail e site ===================
function getEmailAndSite() {
const emailSnap = baseAnswers["0_part0"] || "";
let siteSnap = baseAnswers["0_part1"] || "";
const emailCur = answers["0_part0"] || "";
let siteCur = answers["0_part1"] || "";
const email = emailSnap || emailCur || "";
let site = siteSnap || siteCur || "";
if (site && !/^https?:\/\//i.test(site)) site = "https://" + site;
return { email, site };
}
// ============= Lighthouse local (salvo no start_scan) =============
function getLocalLighthouse() {
try {
const raw = localStorage.getItem('lighthouse');
if (!raw) return null;
const j = JSON.parse(raw);
// normaliza para o formato usado no back/front
if (j && j.ok) {
return {
ok: true,
scan_id: j.scan_id || null,
score: typeof j.score === 'number' ? j.score : null,
metrics: j.metrics || {},
oportunidades: Array.isArray(j.oportunidades) ? j.oportunidades : []
};
}
return null;
} catch (_) {
return null;
}
}
// =================== PSI (start scan) ===================
async function startScanIfNeeded() {
if (scanId || startScanFailed) return;
const { site } = getEmailAndSite();
if (!site) return;
if (!startScanPromise) {
startScanPromise = (async () => {
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const res = await postJSONWithFallback(ENDPOINTS.startScan, { site });
if (res && res.ok && res.scan_id) {
scanId = res.scan_id;
localStorage.setItem('lighthouse', JSON.stringify(res));
return;
}
} catch (_) { /* retry */ }
await new Promise((r) => setTimeout(r, 600));
}
startScanFailed = true;
})();
}
await startScanPromise;
}
// =================== Coleta 5 respostas iniciais ===================
function buildInitialAnswersArray() {
const out = [];
for (let i = 0; i < initialQuestions.length && i < 5; i++) {
const q = initialQuestions[i];
if (q.type === "double-text") {
q.questions.forEach((subQ, j) => {
const key = `${i}_part${j}`;
const val = baseAnswers[key] ?? answers[key] ?? "";
if (out.length < 5) out.push({ pergunta: subQ, resposta: val });
});
} else if (q.type === "text" || q.type === "choice") {
const val = baseAnswers[`${i}`] ?? answers[`${i}`] ?? "";
out.push({ pergunta: q.text, resposta: val });
}
}
return out.slice(0, 5);
}
// =================== Avaliação de maturidade ===================
async function evaluateMaturity() {
if (!scanId && !startScanFailed) await startScanIfNeeded();
const five = buildInitialAnswersArray();
if (five.length < 5) throw new Error("Responda as 5 perguntas iniciais.");
if (!isEvaluatingPromise) {
isEvaluatingPromise = (async () => {
const payload = { answers: five };
// >>> NOVO: envia o Lighthouse salvo localmente junto da avaliação
const lhLocal = getLocalLighthouse();
if (lhLocal && lhLocal.ok) {
payload.scan_id = lhLocal.scan_id || scanId || null;
payload.lighthouse = {
score: lhLocal.score,
metrics: lhLocal.metrics,
oportunidades: lhLocal.oportunidades
};
} else if (scanId) {
payload.scan_id = scanId;
}
const res = await postJSONWithFallback(ENDPOINTS.evaluate, payload);
if (!res.ok) throw new Error(res.error || "Erro na avaliação");
maturity = res.maturidade;
lighthouse = res.lighthouse || null;
// >>> NOVO: fallback — se o back não retornou Lighthouse, usa o local
if ((!lighthouse || lighthouse.score == null) && lhLocal && lhLocal.ok) {
lighthouse = {
score: lhLocal.score,
metrics: lhLocal.metrics,
oportunidades: lhLocal.oportunidades
};
}
roadmap = res.roadmap || [];
// >>> NOVO: capturamos IA estratégica e explicação
roadmapAI = Array.isArray(res.roadmap_ai) ? res.roadmap_ai : [];
explanation = typeof res.explanation === "string" ? res.explanation : "";
return { maturity, lighthouse, roadmap };
})();
}
return isEvaluatingPromise;
}
// =================== Mescla respostas p/ persistência ===================
function buildAllQA() {
const results = {};
questions.forEach((q, i) => {
if (q.type === "text") {
results[q.text] = answers[i] || "";
} else if (q.type === "double-text") {
q.questions.forEach((subQ, j) => {
results[subQ] = answers[`${i}_part${j}`] || "";
});
} else if (q.type === "choice") {
results[q.text] = answers[i] || "";
}
});
initialQuestions.forEach((q, i) => {
if (q.type === "text") {
if (baseAnswers[i] !== undefined) results[q.text] = baseAnswers[i];
} else if (q.type === "double-text") {
q.questions.forEach((subQ, j) => {
const k = `${i}_part${j}`;
if (baseAnswers[k] !== undefined) results[subQ] = baseAnswers[k];
});
} else if (q.type === "choice") {
if (baseAnswers[i] !== undefined) results[q.text] = baseAnswers[i];
}
});
return Object.entries(results).map(([pergunta, resposta]) => ({ pergunta, resposta }));
}
// =================== Salva no Master Data ===================
async function submitAll() {
const { email, site } = getEmailAndSite();
if (!email || !site || !maturity) {
throw new Error("Email, site e maturidade são obrigatórios.");
}
const qa = buildAllQA();
const res = await postJSONWithFallback(ENDPOINTS.submit, {
email,
site,
// envia a tag de maturidade "crua" (basico/intermediario/avancado)
maturidade: maturity,
answers: qa,
// continua enviando os campos já existentes para compatibilidade
lighthouse: lighthouse || {},
roadmap: roadmap || [],
// >>> NOVOS CAMPOS: para o backend transformar em HTML + strategies + explanation
scan_id: scanId || null,
roadmap_ai: roadmapAI || [],
explanation: explanation || ""
});
if (!res.ok) throw new Error(res.error || "Erro ao salvar no Master Data");
window.dataLayer.push({
event: "quiz_submit_event"
})
return res;
}
// =================== Renderização ===================
function setNextLabel() {
const isLast = currentQuestion === questions.length - 1;
nextBtn.textContent = isLast ? (initialCompleted ? "Finalizar" : "Próximo") : "Próximo";
}
function loadQuestion(index) {
const q = questions[index];
const pct = Math.round(((index + 1) / questions.length) * 100);
setStatus(`${pct}% concluído`);
setCounter(q.title || `Pergunta ${index + 1} de ${questions.length}`);
setProgress(pct);
if (questionText) questionText.textContent = q.text || "";
if (optionsContainer) optionsContainer.innerHTML = "";
let showErrors = false;
if (q.type === "text") {
const textarea = document.createElement("textarea");
textarea.rows = 4;
textarea.placeholder = "Digite sua resposta aqui...";
textarea.value = answers[index] || "";
optionsContainer.appendChild(textarea);
nextBtn.disabled = !textarea.value.trim();
textarea.addEventListener('input', () => {
if (textarea.value.trim()) {
clearInvalid(textarea);
hideFieldError(optionsContainer);
}
validateNext(false);
});
validateNext(false);
} else if (q.type === "double-text") {
q.questions.forEach((subQ, i) => {
const wrap = document.createElement("div");
wrap.className = "double-text-block";
const label = document.createElement("label");
label.className = "double-label";
label.textContent = subQ;
const input = document.createElement("input");
input.type = "text";
input.className = "double-input";
input.placeholder = (q.placeholders && q.placeholders[i]) || "Insira sua resposta";
const key = `${index}_part${i}`;
input.value = answers[key] || "";
input.name = `question${index}_part${i}`;
input.addEventListener('input', () => {
if (input.value.trim()) {
clearInvalid(input);
hideFieldError(optionsContainer);
}
validateNext(false);
});
wrap.appendChild(label);
wrap.appendChild(input);
optionsContainer.appendChild(wrap);
});
validateNext(false);
} else if (q.type === "choice") {
const isMulti = !!q.multiChoice;
q.options.forEach((opt, i) => {
const label = document.createElement("label");
label.className = "options-double";
const input = document.createElement("input");
input.type = isMulti ? "checkbox" : "radio";
input.name = `question${index}` + (isMulti ? `_${i}` : "");
input.value = opt;
// Se multiChoice, marcar checkbox conforme array salvo
if (isMulti && Array.isArray(answers[index]) && answers[index].includes(opt)) {
input.checked = true;
} else if (!isMulti && answers[index] === opt) {
input.checked = true;
}
input.addEventListener("change", validateNext);
input.addEventListener('change', () => {
Array.from(optionsContainer.querySelectorAll('label.options-double')).forEach(l => clearInvalid(l));
hideFieldError(optionsContainer);
validateNext(false);
});
label.appendChild(input);
label.appendChild(document.createTextNode(opt));
optionsContainer.appendChild(label);
});
validateNext(false);
}
prevBtn.disabled = index === 0;
setNextLabel();
function validateNext(forceShow = false) {
if (forceShow) showErrors = true;
let valid = false;
if (q.type === "text") {
const t = optionsContainer.querySelector("textarea");
valid = !!t && !!t.value.trim();
if (!valid && showErrors) markInvalid(t) && showFieldError(optionsContainer, 'Este campo é obrigatório.');
else if (t) clearInvalid(t);
} else if (q.type === "double-text") {
const arr = optionsContainer.querySelectorAll(`input[name^="question${index}_part"]`);
valid = Array.from(arr).every(el => el.value.trim());
Array.from(arr).forEach(el => el.value.trim() ? clearInvalid(el) : (showErrors ? markInvalid(el) : clearInvalid(el)));
if (!valid && showErrors) showFieldError(optionsContainer, 'Preencha todos os campos para continuar.');
} else if (q.type === "choice") {
const isMulti = !!q.multiChoice;
if (isMulti) {
const checked = optionsContainer.querySelectorAll(`input[type="checkbox"]:checked`);
valid = checked.length > 0;
} else {
valid = !!optionsContainer.querySelector(`input[type="radio"]:checked`);
}
const labels = optionsContainer.querySelectorAll('label.options-double');
if (!valid && showErrors) {
labels.forEach(l => markInvalid(l));
showFieldError(optionsContainer, 'Selecione pelo menos uma opção para continuar.');
} else {
labels.forEach(l => clearInvalid(l));
hideFieldError(optionsContainer);
}
}
nextBtn.disabled = !valid;
return valid;
}
currentValidator = validateNext;
}
function saveAnswer(index) {
const q = questions[index];
if (q.type === "text") {
const t = optionsContainer.querySelector(`textarea`);
if (t) answers[index] = t.value.trim();
} else if (q.type === "double-text") {
q.questions.forEach((_, i) => {
const inp = document.querySelector(`input[name="question${index}_part${i}"]`);
if (inp) answers[`${index}_part${i}`] = inp.value.trim();
});
} else if (q.type === "choice") {
if (q.multiChoice) {
const checked = optionsContainer.querySelectorAll(`input[type="checkbox"]:checked`);
answers[index] = Array.from(checked).map(el => el.value);
} else {
const selected = document.querySelector(`input[type="radio"]:checked`);
if (selected) answers[index] = selected.value;
}
}
if (!initialCompleted && index === 0) startScanIfNeeded();
}
// =================== Reset do Quiz ===================
function resetQuiz() {
currentQuestion = 0;
answers = {};
baseAnswers = {};
initialCompleted = false;
selectedDifficulty = null;
questions = initialQuestions;
scanId = null;
startScanPromise = null;
startScanFailed = false;
isEvaluatingPromise = null;
maturity = null;
lighthouse = null;
roadmap = [];
// >>> NOVO: limpar IA e explicação
roadmapAI = [];
explanation = "";
}
// =================== Função Recall ===================
function recall() {
if (finishModalContainer) finishModalContainer.style.display = "none";
resetQuiz();
if (mainModalContainer) mainModalContainer.style.display = "block";
loadQuestion(currentQuestion);
}
// =================== Navegação ===================
prevButton.addEventListener("click", () => {
saveAnswer(currentQuestion);
if (currentQuestion > 0) {
currentQuestion--;
loadQuestion(currentQuestion);
}
});
nextButton.addEventListener("click", async () => {
// Always validate current step and show errors if invalid
if (typeof currentValidator === 'function') {
const isValidNow = currentValidator(true);
if (!isValidNow) {
// Focus/scroll to the first element with error
const firstErr = optionsContainer.querySelector('.input-required, .field-error-msg');
if (firstErr) {
if (typeof firstErr.focus === 'function') firstErr.focus();
firstErr.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
return;
}
}
saveAnswer(currentQuestion);
const isLast = currentQuestion === questions.length - 1;
if (!isLast) {
currentQuestion++;
loadQuestion(currentQuestion);
return;
}
if (!initialCompleted) {
try {
setStatus("Carregando avaliação, aguarde...");
loadingTip.innerText = getRandomTip();
//modalLoading.style.display = "flex";
if (mainModalContainer) mainModalContainer.style.display = "none";
if (finishModalLoading) finishModalLoading.style.display = "flex";
nextBtn.disabled = true;
prevBtn.disabled = true;
baseAnswers = { ...answers };
const { maturity: m } = await evaluateMaturity();
const maturityDiv = document.querySelector(".maturity");
if (m === "basico") {
questions = basic;
selectedDifficulty = "basic";
maturityDiv.innerText = "Básico";
maturityDiv.className = "maturity basic";
} else if (m === "intermediario") {
questions = intermediary;
selectedDifficulty = "intermediary";
maturityDiv.innerText = "Intermediário";
maturityDiv.className = "maturity intermediary";
} else {
questions = advanced;
selectedDifficulty = "advanced";
maturityDiv.innerText = "Avançado";
maturityDiv.className = "maturity advanced";
}
if (finishModalLoading) finishModalLoading.style.display = "none";
if (mainModalContainer) mainModalContainer.style.display = "flex";
initialCompleted = true;
currentQuestion = 0;
answers = {};
isEvaluatingPromise = null;
setStatus("");
nextBtn.disabled = false;
prevBtn.disabled = false;
loadQuestion(currentQuestion);
} catch (err) {
setStatus("");
nextBtn.disabled = false;
prevBtn.disabled = false;
alert(err.message || "Erro ao avaliar maturidade");
}
} else {
try {
loadingTip.innerText = getRandomTip();
if (mainModalContainer) mainModalContainer.style.display = "none";
if (finishModalLoading) finishModalLoading.style.display = "flex";
const result = await submitAll();
console.log("selectedDifficulty", selectedDifficulty);
console.log("submit result:", result);
if (result?.success || result?.ok) {
if (finishModalLoading) finishModalLoading.style.display = "none";
if (finishModalContainer) finishModalContainer.style.display = "flex";
} else {
alert("O envio não foi concluído com sucesso. Tente novamente.");
if (finishModalLoading) finishModalLoading.style.display = "none";
if (mainModalContainer) mainModalContainer.style.display = "flex";
}
} catch (e) {
if (finishModalLoading) finishModalLoading.style.display = "none";
if (mainModalContainer) mainModalContainer.style.display = "flex";
alert(e.message || "Erro ao salvar no Master Data");
}
}
});
// =================== Conecta botão "Refazer teste" ===================
const recallBtn = document.querySelector(".recall");
if (recallBtn) {
recallBtn.addEventListener("click", recall);
}
// se modal abrir já visível
if (modalContainer && modalContainer.style.display === "block") {
loadQuestion(currentQuestion);
}