File: /home/storage/5/78/dd/wicomm2/public_html/blackfriday/lead.php
<?php
// lead.php — visualização interna do lead salvo na BF (VTEX MD)
require_once __DIR__ . '/config/config.php'; // ajuste o caminho se necessário
@date_default_timezone_set(defined('APP_TIMEZONE') ? APP_TIMEZONE : 'America/Sao_Paulo');
/* ---------- helpers ---------- */
function esc($s){ return htmlspecialchars((string)$s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); }
function clamp100($v){ $n=is_numeric($v)?floatval($v):0; return max(0,min(100,$n)); }
function maturity_to_score($m){
$m = mb_strtolower(trim((string)$m),'UTF-8');
if (strpos($m,'avan')===0) return 92;
if (strpos($m,'inter')===0) return 65;
if (strpos($m,'bás')===0 || strpos($m,'bas')===0) return 35;
return 50;
}
function gauge_svg($score, $label=''){
$score = clamp100($score);
$color = ($score>=90)?'#0cce6b':(($score>=50)?'#ffa400':'#ff4e42');
$R=45;$T=10;$C=2*pi()*$R;$off=$C*(1-$score/100);
return '<svg width="120" height="120" viewBox="0 0 120 120" role="img" aria-label="'.esc($label).' '.$score.'">'.
'<circle cx="60" cy="60" r="'.$R.'" fill="none" stroke="#1f2731" stroke-width="'.$T.'"/>'.
'<circle cx="60" cy="60" r="'.$R.'" fill="none" stroke="'.$color.'" stroke-width="'.$T.
'" stroke-dasharray="'.number_format($C,1,'.','').'" stroke-dashoffset="'.number_format($off,1,'.','').
'" stroke-linecap="round" transform="rotate(-90 60 60)"/>'.
'<text x="60" y="66" text-anchor="middle" font-size="28" font-weight="700" fill="'.$color.'">'.$score.'</text>'.
'<text x="60" y="90" text-anchor="middle" font-size="12" fill="#8ea0b4">'.esc($label).'</text>'.
'</svg>';
}
function vt_md_base(){ return 'https://'.VTEX_ACCOUNT.'.'.VTEX_ENVIRONMENT.'.com.br/api/dataentities/'.VTEX_DATA_ENTITY; }
function vt_lh_base(){ return 'https://'.VTEX_ACCOUNT.'.'.VTEX_ENVIRONMENT.'.com.br/api/dataentities/LH'; }
function http_get_json($url,$connect_ms=8000,$timeout_ms=15000){
$h=[
'Accept: application/json',
'Content-Type: application/json',
'X-VTEX-API-AppKey: '.VTEX_APPKEY,
'X-VTEX-API-AppToken: '.VTEX_APPTOKEN,
];
$ch=curl_init($url);
curl_setopt_array($ch,[
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_HTTPHEADER=>$h,
CURLOPT_CONNECTTIMEOUT_MS=>$connect_ms,
CURLOPT_TIMEOUT_MS=>$timeout_ms,
CURLOPT_SSL_VERIFYPEER=>true,
CURLOPT_SSL_VERIFYHOST=>2,
CURLOPT_IPRESOLVE=>CURL_IPRESOLVE_V4
]);
$body=curl_exec($ch); $err=curl_error($ch); $code=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
if ($code>=400||!$body) return [null,$body?:$err?:('HTTP '.$code)];
$j=json_decode($body,true);
return [$j,null];
}
function parse_json_field($raw){
if (is_array($raw)) return $raw;
if (!is_string($raw) || trim($raw)==='') return null;
$j=json_decode($raw,true);
return is_array($j)?$j:null;
}
function lh_extract_slides($raw){
// Normalize into [{headline, conteudo}]
$slides = [];
// Helper: push headline/conteudo if present
$push = function($obj) use (&$slides){
if (!is_array($obj)) return;
$h = trim((string)($obj['headline'] ?? ''));
$c = trim((string)($obj['conteudo'] ?? ''));
if ($h !== '' || $c !== '') $slides[] = ['headline'=>$h, 'conteudo'=>$c];
};
// If it's already JSON string, try decode directly
if (is_string($raw)) {
$try = json_decode($raw, true);
if (is_array($try)) $raw = $try;
}
// Case A: already an array of slides (headline/conteudo)
if (is_array($raw) && isset($raw[0]) && (isset($raw[0]['headline']) || isset($raw[0]['conteudo']))) {
foreach ($raw as $it) $push($it);
// Special: single "Análise indisponível" containing fenced ```json blocks
if (count($slides) === 1
&& stripos($slides[0]['headline'], 'análise indisponível') !== false
&& strpos($slides[0]['conteudo'], '```') !== false) {
$content = $slides[0]['conteudo'];
$slides = []; // reset and re-extract
if (preg_match_all('/```json\\s*([\\s\\S]*?)```/mi', $content, $blocks)) {
foreach ($blocks[1] as $blk) {
if (preg_match_all('/\\{[\\s\\S]*?\\}/m', $blk, $objs)) {
foreach ($objs[0] as $atom) $push(json_decode($atom, true));
}
}
}
}
return $slides;
}
// Case B: string with fenced ```json containing one or many JSON objects
if (is_string($raw) && strpos($raw, '```') !== false) {
if (preg_match_all('/```json\\s*([\\s\\S]*?)```/mi', $raw, $blocks)) {
foreach ($blocks[1] as $blk) {
// Extract EACH JSON object inside a single fence (they may be adjacent without commas)
if (preg_match_all('/\\{[\\s\\S]*?\\}/m', $blk, $objs)) {
foreach ($objs[0] as $atom) $push(json_decode($atom, true));
}
}
if (count($slides)) return $slides;
}
}
// Case C: raw string with multiple "{...}" JSON objects (no fences)
if (is_string($raw)) {
if (preg_match_all('/\\{[\\s\\S]*?\\}/m', $raw, $objs)) {
foreach ($objs[0] as $atom) $push(json_decode($atom, true));
if (count($slides)) return $slides;
}
}
// Case D: classic Lighthouse opportunities (title/id/savingsMs)
if (is_array($raw) && isset($raw[0]) && (isset($raw[0]['title']) || isset($raw[0]['id']))) {
foreach ($raw as $op) {
$t = trim((string)($op['title'] ?? ($op['id'] ?? 'Oportunidade')));
$sv = (isset($op['savingsMs']) && is_numeric($op['savingsMs'])) ? (int)$op['savingsMs'] : null;
$desc = $sv !== null ? ('Economia potencial ~'.$sv.'ms') : '';
$slides[] = ['headline'=>$t, 'conteudo'=>$desc];
}
return $slides;
}
return $slides;
}
/**
* Lê o payload bruto do Lighthouse (como salvo na entidade LH) e
* devolve [score:int|null, opportunities:array]
*/
function lighthouse_from_payload($payloadRaw){
$score = null;
$ops = [];
$p = is_string($payloadRaw) ? json_decode($payloadRaw, true) : (is_array($payloadRaw) ? $payloadRaw : null);
if (!is_array($p)) return [$score, $ops];
// payload pode já vir como o JSON do PSI completo ou um wrapper
$root = $p;
if (isset($p['lighthouseResult'])) {
$root = $p;
} elseif (isset($p['payload']) && is_string($p['payload'])) {
$pp = json_decode($p['payload'], true);
if (isset($pp['lighthouseResult'])) $root = $pp;
}
if (!isset($root['lighthouseResult'])) return [$score, $ops];
$lh = $root['lighthouseResult'];
$aud = $lh['audits'] ?? [];
$cats = $lh['categories'] ?? [];
if (isset($cats['performance']['score'])) {
$score = (int) round(($cats['performance']['score'] ?? 0) * 100);
}
foreach ($aud as $id => $a) {
if (isset($a['details']['type']) && $a['details']['type'] === 'opportunity') {
$ops[] = [
'id' => $id,
'title' => $a['title'] ?? $id,
'savingsMs' => $a['details']['overallSavingsMs'] ?? 0
];
}
}
usort($ops, fn($A,$B) => ($B['savingsMs'] ?? 0) <=> ($A['savingsMs'] ?? 0));
$ops = array_slice($ops, 0, 10);
return [$score, $ops];
}
/**
* Busca fallback na entidade LH:
* - Se tiver scanId, procura por ele;
* - Senão, pega o mais recente (_creationDate desc).
* Retorna [score:int|null, opportunities:array] se conseguir extrair.
*/
function try_fetch_lighthouse_from_LH($scanId = ''){
$base = vt_lh_base();
// 1) com scanId
if ($scanId !== '') {
$qs = http_build_query([
'_where' => 'scanId='.$scanId,
'_fields' => 'scanId,payload',
'_size' => 1
]);
[$res, $err] = http_get_json($base.'/search?'.$qs);
if (!$err && is_array($res) && count($res)) {
$row = $res[0];
return lighthouse_from_payload($row['payload'] ?? '');
}
}
// 2) último registro
$qs = http_build_query([
'_fields' => 'scanId,payload',
'_size' => 1,
'_sort' => '_creationDate desc'
]);
[$res2, $err2] = http_get_json($base.'/search?'.$qs);
if (!$err2 && is_array($res2) && count($res2)) {
$row = $res2[0];
return lighthouse_from_payload($row['payload'] ?? '');
}
return [null, []];
}
/* ---------- entrada ---------- */
$docId = trim((string)($_GET['id'] ?? ''));
if ($docId===''){ http_response_code(400); echo 'Parâmetro "id" é obrigatório (lead.php?id=DOCUMENT_ID).'; exit; }
/* ---------- busca no MD: documents -> fallback search ---------- */
$base = vt_md_base();
list($doc,$err) = http_get_json($base.'/documents/'.$docId);
$needFallback = false;
if ($err) { $needFallback = true; }
else {
$keys = is_array($doc) ? array_keys($doc) : [];
if (count($keys) <= 4 && isset($doc['id'])) $needFallback = true;
}
if ($needFallback){
$fields = [
'email','site','maturidade','explanation',
'lighthouse_score','lighthouse_sugestoes','questions','roadmap_ai','scan_id','scanId'
];
$qs = http_build_query([
'_where' => 'id='.$docId,
'_fields' => implode(',', $fields)
]);
list($res,$err2) = http_get_json($base.'/search?'.$qs);
if ($err2 || !is_array($res) || !count($res)){
http_response_code(502);
echo 'Falha ao buscar documento: '.esc($err2 ?: 'sem resultados');
exit;
}
$doc = $res[0];
}
/* ---------- campos ---------- */
$email = $doc['email'] ?? '';
$site = $doc['site'] ?? '';
$maturidade = $doc['maturidade'] ?? '';
$explanation = $doc['explanation'] ?? '';
$lighthouseScore = $doc['lighthouse_score'] ?? null;
$lighthouseScore = is_numeric($lighthouseScore) ? intval($lighthouseScore) : null;
$lighthouseOps = parse_json_field($doc['lighthouse_sugestoes'] ?? '');
$questions = parse_json_field($doc['questions'] ?? '');
$roadmapAI = parse_json_field($doc['roadmap_ai'] ?? '');
$mScore = maturity_to_score($maturidade);
/* ---------- Fallback Lighthouse pela entidade LH (se vazio) ---------- */
if (!is_numeric($lighthouseScore) || !is_array($lighthouseOps) || !count($lighthouseOps)) {
$scanIdDoc = trim((string)($doc['scan_id'] ?? ($doc['scanId'] ?? '')));
[$sc, $ops] = try_fetch_lighthouse_from_LH($scanIdDoc);
if (is_numeric($sc)) $lighthouseScore = intval($sc);
if (is_array($ops) && count($ops)) $lighthouseOps = $ops;
}
/* ---------- html ---------- */
?><!doctype html>
<html lang="pt-br">
<head>
<meta charset="utf-8" />
<title>Check Up | Prepare seu site para Black Friday | Grupo W</title>
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root{ --bg:#0b0d10; --card:#12161b; --muted:#8ea0b4; --text:#e6eef7; --bar:#1f2731; --brand:#6aa6ff; }
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto}
header{padding:20px 24px;border-bottom:1px solid var(--bar);position:sticky;top:0;background:#0b0d10cc;backdrop-filter:saturate(1.2) blur(2px)}
h1{margin:0 0 6px;font-size:20px}
.muted{color:var(--muted)}
main{padding:24px;display:grid;gap:20px}
.grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.card{background:var(--card);border:1px solid var(--bar);border-radius:14px;padding:16px}
.flex{display:flex;gap:16px;align-items:center}
.gauge{min-width:120px}
.kv{display:grid;grid-template-columns:120px 1fr;gap:6px;align-items:start}
.pill{display:inline-block;padding:2px 8px;border-radius:999px;background:#1a2330;border:1px solid #293241;color:#cfe3ff;font-size:12px}
.h3{font-size:16px;font-weight:700;margin:0 0 6px}
.small{font-size:12px}
ul{margin:8px 0 0 18px}
.opps li span{color:var(--muted)}
.gridCol{display:grid;gap:12px}
.qa-item{border-top:1px dashed #243042;padding-top:10px;margin-top:10px}
a{color:#cfe3ff;text-decoration:none}
</style>
</head>
<body>
<header>
<h1>Lead — Black Friday</h1>
<div class="muted">
ID: <?= esc($docId) ?> • Email:
<?php if ($email): ?><a href="mailto:<?= esc($email) ?>"><?= esc($email) ?></a><?php else: ?>—<?php endif; ?>
• Site:
<?php if ($site): ?><a href="<?= esc($site) ?>" target="_blank" rel="noopener"><?= esc($site) ?></a><?php else: ?>—<?php endif; ?>
</div>
</header>
<main>
<div class="grid2">
<!-- Maturidade -->
<section class="card">
<div class="h3">Maturidade</div>
<div class="flex">
<div class="gauge"><?= gauge_svg($mScore, 'Nível') ?></div>
<div class="gridCol">
<div><span class="pill">Nível</span> <?= esc($maturidade ?: '—') ?></div>
<div>
<div class="muted small">Resumo</div>
<div><?= nl2br(esc($explanation ?: '—')) ?></div>
</div>
</div>
</div>
</section>
<!-- Lighthouse -->
<section class="card">
<div class="h3">Lighthouse (PageSpeed)</div>
<div class="flex">
<div class="gauge">
<?php
if (is_numeric($lighthouseScore)) {
echo gauge_svg(intval($lighthouseScore), 'Performance');
} else {
echo '<div class="muted">Sem resultado salvo.</div>';
}
?>
</div>
<div>
<div class="muted small">Oportunidades principais</div>
<?php
$lhSlides = lh_extract_slides($lighthouseOps);
?>
<?php if (is_array($lhSlides) && count($lhSlides)): ?>
<ul class="opps">
<?php foreach (array_slice($lhSlides, 0, 8) as $sl): ?>
<li>
<div><b><?= esc($sl['headline'] ?? '') ?></b></div>
<?php if (!empty($sl['conteudo'])): ?>
<div class="muted small"><?= esc($sl['conteudo']) ?></div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php elseif (is_array($lighthouseOps) && count($lighthouseOps)): ?>
<ul class="opps">
<?php foreach (array_slice($lighthouseOps, 0, 8) as $op):
$t = $op['title'] ?? ($op['id'] ?? '');
$sv = isset($op['savingsMs']) ? (' (~'.round(floatval($op['savingsMs'])).'ms)') : '';
?>
<li><?= esc($t.$sv) ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<div class="muted">—</div>
<?php endif; ?>
</div>
</div>
</section>
</div>
<div class="grid2">
<!-- Perguntas & respostas -->
<section class="card">
<div class="h3">Perguntas & respostas (quiz)</div>
<?php if (is_array($questions) && count($questions)): ?>
<?php foreach ($questions as $qa): ?>
<div class="qa-item">
<div class="muted small">Pergunta</div>
<div><?= esc($qa['pergunta'] ?? $qa['question'] ?? '') ?></div>
<div class="muted small" style="margin-top:6px">Resposta</div>
<div><b><?= esc($qa['resposta'] ?? $qa['answer'] ?? '') ?></b></div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="muted">Sem respostas salvas.</div>
<?php endif; ?>
</section>
<!-- Estratégias IA -->
<section class="card">
<div class="h3">Estratégias sugeridas (IA)</div>
<?php if (is_array($roadmapAI) && count($roadmapAI)): ?>
<?php foreach ($roadmapAI as $i => $it): ?>
<div class="qa-item">
<div class="h3" style="font-size:15px"><?= esc($it['titulo'] ?? ('Item '.($i+1))) ?></div>
<?php if (!empty($it['area'])): ?>
<div class="muted small">Área: <b><?= esc($it['area']) ?></b></div>
<?php endif; ?>
<?php if (!empty($it['kpi'])): ?>
<div class="muted small">KPI: <b><?= esc($it['kpi']) ?></b></div>
<?php endif; ?>
<?php if (!empty($it['impacto']) || !empty($it['prazo'])): ?>
<div class="muted small">
<?php if (!empty($it['impacto'])): ?>Impacto: <b><?= esc($it['impacto']) ?></b><?php endif; ?>
<?php if (!empty($it['prazo'])): ?> • Prazo: <b><?= esc($it['prazo']) ?></b><?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($it['por_que'])): ?>
<div class="muted small" style="margin-top:6px">Por quê</div>
<div><?= esc($it['por_que']) ?></div>
<?php endif; ?>
<?php if (!empty($it['acao_estrategica'])): ?>
<div class="muted small" style="margin-top:6px">Ação estratégica</div>
<div><?= esc($it['acao_estrategica']) ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="muted">Sem estratégias salvas.</div>
<?php endif; ?>
</section>
</div>
</main>
</body>
</html>