File: /home/storage/5/78/dd/wicomm2/public_html/blackfriday/api/submit.php
<?php
require_once __DIR__ . '/../lib/Utils.php';
require_once __DIR__ . '/../lib/VTEX.php';
Utils::boot();
/* ---------------- Config / chaves opcionais (se existir config.php) ---------------- */
$OPENAI_API_KEY = getenv('OPENAI_API_KEY') ?: '';
$OPENAI_MODEL = getenv('OPENAI_MODEL') ?: 'gpt-4o-mini';
$STORAGE_DIR = __DIR__ . '/../storage';
if (is_file(__DIR__ . '/../config/config.php')) {
include_once __DIR__ . '/../config/config.php';
if (defined('OPENAI_API_KEY') && OPENAI_API_KEY)
$OPENAI_API_KEY = OPENAI_API_KEY;
if (defined('OPENAI_MODEL') && OPENAI_MODEL)
$OPENAI_MODEL = OPENAI_MODEL;
if (defined('STORAGE_DIR') && STORAGE_DIR)
$STORAGE_DIR = STORAGE_DIR;
}
@is_dir($STORAGE_DIR) || @mkdir($STORAGE_DIR, 0775, true);
/* ---------------- Helpers ---------------- */
function json_out($data, $code = 200)
{
if (!headers_sent()) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
/** Tenta ler JSON do cookie (URL-encoded) */
function cookie_json($name)
{
if (!isset($_COOKIE[$name]))
return null;
$raw = urldecode($_COOKIE[$name]);
$j = json_decode($raw, true);
return is_array($j) ? $j : null;
}
/** Extrai resumo Lighthouse de arquivo /storage/scan_<id>.json */
function lighthouse_from_scan_file($scanId, $storageDir)
{
if (!$scanId)
return null;
$p = rtrim($storageDir, '/') . '/scan_' . $scanId . '.json';
if (!is_file($p))
return null;
$raw = json_decode(@file_get_contents($p), true);
if (!is_array($raw) || !isset($raw['lighthouseResult']))
return null;
$lh = $raw['lighthouseResult'];
$aud = $lh['audits'] ?? [];
$cats = $lh['categories'] ?? [];
$score = (int) round((($cats['performance']['score'] ?? 0) * 100));
$disp = function ($id) use ($aud) {
return $aud[$id]['displayValue'] ?? '';
};
$opps = [];
foreach ($aud as $id => $a) {
if (isset($a['details']['type']) && $a['details']['type'] === 'opportunity') {
$opps[] = [
'id' => $id,
'title' => $a['title'] ?? $id,
'savingsMs' => $a['details']['overallSavingsMs'] ?? 0
];
}
}
usort($opps, fn($A, $B) => ($B['savingsMs'] ?? 0) <=> ($A['savingsMs'] ?? 0));
$opps = array_slice($opps, 0, 10);
return [
'score' => max(0, min(100, $score)),
'metrics' => [
'lcp' => $disp('largest-contentful-paint'),
'tbt' => $disp('total-blocking-time'),
'cls' => $disp('cumulative-layout-shift'),
'interactive' => $disp('interactive'),
],
'oportunidades' => $opps
];
}
/** Chamada simples à OpenAI (opcional) */
function openai_chat($apiKey, $model, $system, $user, $temperature = 0.7, $timeout = 45)
{
$ch = curl_init('https://api.openai.com/v1/chat/completions');
$payload = [
'model' => $model,
'temperature' => $temperature,
'messages' => [
['role' => 'system', 'content' => $system],
['role' => 'user', 'content' => $user],
]
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => $timeout,
]);
$body = curl_exec($ch);
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 400 || !$body) {
throw new Exception('OpenAI HTTP ' . $code . ': ' . ($body ?: $err ?: 'erro'));
}
$j = json_decode($body, true);
$txt = trim($j['choices'][0]['message']['content'] ?? '');
if ($txt === '')
throw new Exception('OpenAI: resposta vazia');
return $txt;
}
/** Gera explicação curta sobre a maturidade (com/sem OpenAI) */
function build_explanation($maturidade, $answers, $apiKey, $model)
{
$map = [
'basico' => 'Classificada como Básico: respostas indicam processos iniciais e pouco sistematizados, com baixa padronização e sinais limitados de preparo para a Black Friday.',
'intermediario' => 'Classificada como Intermediário: já há práticas consistentes e alguns rituais, mas com lacunas de governança e priorização para maximizar a Black Friday.',
'avancado' => 'Classificada como Avançado: práticas consolidadas e visão orientada a dados; foco em orquestração de canais e eficiência de margem para a Black Friday.'
];
$key = strtolower($maturidade);
$default = $map[$key] ?? $map['basico'];
if (!$apiKey)
return $default;
// Prompt mínimo (barato)
try {
$pairs = [];
foreach ((array) $answers as $a) {
$q = trim((string) ($a['pergunta'] ?? ''));
$r = trim((string) ($a['resposta'] ?? ''));
if ($q !== '' || $r !== '')
$pairs[] = "• {$q} → {$r}";
if (count($pairs) >= 8)
break;
}
$system = "Você é um avaliador de maturidade de e-commerce para Black Friday. Responda 1 frase curta, clara e específica.";
$user = "Maturidade: {$maturidade}\nBaseie-se nos sinais abaixo para justificar em 1 frase por que a loja está nesse nível (sem jargão, sem citar plataformas):\n" . implode("\n", $pairs);
$txt = openai_chat($apiKey, $model, $system, $user, 0.1, 20);
// saneia para 1 frase curta
$txt = preg_replace('/\s+/', ' ', trim($txt));
return mb_substr($txt, 0, 260, 'UTF-8');
} catch (Throwable $e) {
return $default;
}
}
/** Gera roadmap_ai (array) com OpenAI; fallback determinístico por maturidade */
function build_roadmap_ai($maturidade, $answers, $lighthouse, $apiKey, $model, $maxItems = 18)
{
// se não tiver OpenAI, cai no determinístico
if (!$apiKey)
return roadmap_deterministico($maturidade, $maxItems);
try {
// compacta
$pairs = [];
foreach ((array) $answers as $a) {
$q = trim((string) ($a['pergunta'] ?? ''));
$r = trim((string) ($a['resposta'] ?? ''));
if ($q !== '' || $r !== '')
$pairs[] = "• {$q} → {$r}";
if (count($pairs) >= 16)
break;
}
$lh = '';
if (is_array($lighthouse) && $lighthouse) {
$ops = array_map(function ($o) {
return '- ' . $o['title'] . ' (~' . (int) ($o['savingsMs'] ?? 0) . 'ms)';
}, array_slice(($lighthouse['oportunidades'] ?? []), 0, 8));
$lh = "Score: " . ($lighthouse['score'] ?? 'n/a')
. "; LCP: " . (($lighthouse['metrics']['lcp'] ?? 'n/a'))
. "; TBT: " . (($lighthouse['metrics']['tbt'] ?? 'n/a'))
. "; CLS: " . (($lighthouse['metrics']['cls'] ?? 'n/a'))
. "; Interactive: " . (($lighthouse['metrics']['interactive'] ?? 'n/a'))
. "\nOportunidades:\n" . implode("\n", $ops);
}
$system = <<<SYS
Você é estrategista sênior de e-commerce para Black Friday que consegue ser didático e especialista em comunicação e seu papel é criar um roadmap e briefing de uma maneira inteligente e especifica, você precisa criar estratégias capazes de gerar mais conversões para um ecommerce. Eu preciso que você desenvolva a ação estratégia (acao_estrategica) com pelo menos 50 palavras, sendo estrategico, pensando como um especialista senior de ecommerce capaz de elevar o faturamento de qualquer ecommerce.
Gere um array JSON (sem texto fora do JSON) com ATÉ {$maxItems}/18 objetos com pelo menos 3 itens por area.
Cada objeto:
- "titulo": um hook de introdução para a ação estratégica
- "acao_estrategica": passos de gestão de uma maneira especifica, levando em conta a {$maturidade} do cliente, eu não quero que sugira nada amplo e ou básico, o foco aqui é trazer resultados especificos e inteligentes, com uma estratégia de ponta a ponta que pode ser executada de acordo com a {$maturidade} realmente(sem citar plataformas/plug-ins) + precisa ter pelo menos 50 palavras, e precisa ser estratégico realmente.
- "kpi": 1 KPI
- "area": CRM|UI/UX|SEO|Performance|Dados
SYS;
$user = "Maturidade: {$maturidade}\nSinais:\n" . implode("\n", $pairs) . "\n\nLighthouse (resumo):\n{$lh}\n\nResponda somente com JSON do array.";
$txt = openai_chat($apiKey, $model, $system, $user, 0.7, 45);
$arr = json_decode($txt, true);
if (!is_array($arr)) {
if (preg_match('/\[[\s\S]*\]/', $txt, $m)) {
$arr = json_decode($m[0], true);
}
}
if (!is_array($arr))
throw new Exception('IA não retornou JSON válido');
// saneia e limita
$okAreas = ['CRM', 'UI/UX', 'SEO', 'Performance', 'Dados'];
$out = [];
foreach ($arr as $it) {
$item = [
'titulo' => trim((string) ($it['titulo'] ?? '')),
'por_que' => trim((string) ($it['por_que'] ?? '')),
'acao_estrategica' => trim((string) ($it['acao_estrategica'] ?? '')),
'kpi' => trim((string) ($it['kpi'] ?? '')),
'area' => in_array(($it['area'] ?? ''), $okAreas, true) ? $it['area'] : 'Operacao',
];
if ($item['titulo'] !== '')
$out[] = $item;
if (count($out) >= $maxItems)
break;
}
if (!$out)
throw new Exception('IA retornou lista vazia');
return $out;
} catch (Throwable $e) {
return roadmap_deterministico($maturidade, $maxItems);
}
}
/** Fallback determinístico simples (usa “melhorias” como título) */
function roadmap_deterministico($maturidade, $maxItems = 10)
{
$packs = [
'basico' => [
['area' => 'Performance', 'titulo' => 'Fundamentos de performance em BF', 'por_que' => 'Reduzir fricção e tempo de resposta aumenta conversão sob pico de tráfego.', 'acao_estrategica' => 'Definir política de cache, compressão e peso por página; revisar banners e assets da campanha.', 'kpi' => 'LCP p75', 'impacto' => 'alto', 'prazo' => 'antes da BF'],
['area' => 'CRM', 'titulo' => 'Cadência básica de CRM', 'por_que' => 'Aumenta o alcance qualificado com baixo custo.', 'acao_estrategica' => 'Higienizar base, definir sequência de pré-BF, BF e pós-BF com metas.', 'kpi' => 'Receita por envio', 'impacto' => 'alto', 'prazo' => 'antes da BF'],
['area' => 'UI/UX', 'titulo' => 'Página de BF clara', 'por_que' => 'Facilita descoberta rápida e reduz abandono.', 'acao_estrategica' => 'Criar landing de ofertas com filtros, contagem regressiva e regras de frete.', 'kpi' => 'CVR BF', 'impacto' => 'alto', 'prazo' => 'antes da BF'],
],
'intermediario' => [
['area' => 'Dados', 'titulo' => 'Métricas de coorte', 'por_que' => 'Foco em público com maior retorno.', 'acao_estrategica' => 'Definir coortes VIP/reativação e metas por coorte durante a BF.', 'kpi' => 'LTV 90d', 'impacto' => 'medio', 'prazo' => 'H1'],
['area' => 'SEO', 'titulo' => 'Arquitetura sazonal', 'por_que' => 'Captura demanda orgânica de BF.', 'acao_estrategica' => 'Landing evergreen de BF + clusters por categoria, com interligação.', 'kpi' => 'Impressões orgânicas', 'impacto' => 'medio', 'prazo' => 'H1'],
],
'avancado' => [
['area' => 'Operacao', 'titulo' => 'Orquestração omnichannel', 'por_que' => 'Maximiza margem e disponibilidade.', 'acao_estrategica' => 'Regras de roteamento/estoque e preços por canal em janela de BF.', 'kpi' => 'Margem líquida', 'impacto' => 'alto', 'prazo' => 'antes da BF'],
['area' => 'Performance', 'titulo' => 'Estratégia de peso de campanha', 'por_que' => 'Criativos pesados degradam UX sob pico.', 'acao_estrategica' => 'Diretrizes de tamanho/formatos para assets de BF e rollout controlado.', 'kpi' => 'TBT p75', 'impacto' => 'medio', 'prazo' => 'antes da BF'],
]
];
$k = in_array(strtolower($maturidade), ['basico', 'intermediario', 'avancado'], true) ? strtolower($maturidade) : 'basico';
$base = $packs[$k];
return array_slice($base, 0, $maxItems);
}
function normalize_maturity_label($m)
{
$m = mb_strtolower(trim((string) $m), 'UTF-8');
if (strpos($m, 'av') === 0)
return 'Avançado';
if (strpos($m, 'inter') === 0)
return 'Intermediário';
return 'Básico';
}
function strategy_html($item)
{
$t = htmlspecialchars($item['titulo'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$pq = htmlspecialchars($item['por_que'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$ac = htmlspecialchars($item['acao_estrategica'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
if ($t === '' && $pq === '' && $ac === '')
return '';
return "<p><b>{$t}</b></p><p>{$pq}</p><p>{$ac}</p>";
}
function gerarAnaliseLighthouseIA(array $lighthouse): array {
if (empty($lighthouse)) return [];
// Prepara o prompt com os dados técnicos
$prompt = "Você é um analista de performance web.
Analise os dados abaixo do relatório Lighthouse e produza 10 slides em português-BR.
Evite uma linguagem muito técnica, prefira o didatismo e a explicação técnica.
Cada slide deve ter este formato em JSON, apenas o JSON abaixo, nenhuma informação adicional:
{\"headline\": \"texto curto\", \"conteudo\": \"texto explicativo no estilo dos exemplos, sendo didático e sem parecer um relatório genérico\"}
Dados:
" . json_encode($lighthouse, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// Chamada para API OpenAI
$ch = curl_init("https://api.openai.com/v1/chat/completions");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . OPENAI_API_KEY,
"Content-Type: application/json"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"model" => OPENAI_MODEL,
"messages" => [
["role" => "system", "content" => "Você é um especialista em Web Performance e UX."],
["role" => "user", "content" => $prompt]
],
"temperature" => 0.7,
"max_tokens" => 800
])
]);
$resp = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err || !$resp) {
error_log("Erro IA Lighthouse: " . $err);
return [];
}
$data = json_decode($resp, true);
$text = $data['choices'][0]['message']['content'] ?? '';
// Tenta decodificar a resposta como JSON
$analises = json_decode($text, true);
if (!is_array($analises)) {
// fallback: embrulha em array de texto
$analises = [["headline" => "Análise indisponível", "conteudo" => $text]];
}
return $analises;
}
try {
$data = Utils::requirePostJSON();
$email = trim((string) ($data['email'] ?? ''));
$site = trim((string) ($data['site'] ?? ''));
$maturidade = trim((string) ($data['maturidade'] ?? ''));
$answers = is_array($data['answers'] ?? null) ? $data['answers'] : [];
$scanId = isset($data['scan_id']) ? trim((string) $data['scan_id']) : '';
if ($email === '' || $site === '' || $maturidade === '') {
json_out(['ok' => false, 'error' => 'email, site e maturidade são obrigatórios'], 400);
}
$lighthouse = is_array($data['lighthouse'] ?? null) ? $data['lighthouse'] : null;
if (!$lighthouse || empty($lighthouse['metrics'])) {
$ck = cookie_json('fridayup_lighthouse');
if ($ck && (isset($ck['score']) || isset($ck['metrics']))) {
$lighthouse = $ck;
}
}
if ((!$lighthouse || empty($lighthouse['metrics'])) && $scanId) {
$lighthouse = lighthouse_from_scan_file($scanId, $STORAGE_DIR);
}
if (!$lighthouse)
$lighthouse = ['score' => null, 'metrics' => [], 'oportunidades' => []];
$roadmapAI = is_array($data['roadmap_ai'] ?? null) ? $data['roadmap_ai'] : null;
if (!$roadmapAI || !count($roadmapAI)) {
$roadmapAI = build_roadmap_ai($maturidade, $answers, $lighthouse, $OPENAI_API_KEY, $OPENAI_MODEL, 18);
}
$items = [];
if (is_array($roadmapAI)) {
$items = array_slice($roadmapAI, 0, 6);
}
$strategies = array_fill(0, 10, '');
foreach (array_slice($roadmapAI, 0, 10) as $i => $it) {
$strategies[$i] = strategy_html($it);
}
$maturidadeNorm = normalize_maturity_label($maturidade);
$explanation = trim((string) ($data['explanation'] ?? ''));
if ($explanation === '') {
$explanation = build_explanation($maturidade, $answers, $OPENAI_API_KEY, $OPENAI_MODEL);
}
$analiseIA = gerarAnaliseLighthouseIA($lighthouse);
$payload = [
'email' => $email,
'site' => $site,
'maturidade' => $maturidadeNorm,
'explanation' => $explanation,
'questions' => json_encode($answers, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'lighthouse_score' => $lighthouse['score'] ?? null,
'lighthouse_sugestoes' => json_encode($analiseIA, JSON_UNESCAPED_UNICODE),
'roadmap' => json_encode($data['roadmap'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'roadmap_ai' => json_encode($roadmapAI, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'created_at' => date('c')
];
$mkStrategyText = function (array $it) {
$titulo = trim((string) ($it['titulo'] ?? ''));
$pq = trim((string) ($it['por_que'] ?? ''));
$acao = trim((string) ($it['acao_estrategica'] ?? ''));
$txt = '';
if ($pq !== '') {
$txt .= "Por quê: " . $pq . "\n\n";
}
if ($acao !== '') {
$txt .= " Ação estratégica: " . $acao;
}
return trim($txt);
};
for ($i = 0; $i < 6; $i++) {
$idx = $i + 1;
$titulo = '';
$content = '';
if (isset($items[$i]) && is_array($items[$i])) {
$titulo = trim((string) ($items[$i]['titulo'] ?? ''));
$content = $mkStrategyText($items[$i]);
}
$payload['strategy_' . $idx . '_title'] = $titulo;
$payload['strategy_' . $idx] = $content;
}
$vtex = new VTEXClient();
$res = $vtex->saveBF($payload);
json_out(['ok' => true, 'vtex' => $res, 'saved' => $payload]);
} catch (Throwable $e) {
json_out(['ok' => false, 'error' => 'submit: ' . $e->getMessage()], 500);
}