HEX
Server: Apache
System: Linux vpshost11508.publiccloud.com.br 5.15.179-grsec-vpshost-10.lc.el8.x86_64 #1 SMP Mon Apr 7 12:04:45 -03 2025 x86_64
User: wicomm2 (10002)
PHP: 8.3.0
Disabled: apache_child_terminate,dl,escapeshellarg,escapeshellcmd,exec,link,mail,openlog,passthru,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_wait,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,php_check_syntax,php_strip_whitespace,popen,proc_close,proc_open,shell_exec,symlink,system
Upload Files
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);
}