File: /home/storage/5/78/dd/wicomm2/public_html/clientes/eucatex/showcase/region.php
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
set_time_limit(300000);
ini_set('memory_limit', '928M');
$debug = false;
$appKey = 'vtexappkey-lojaeucatex-CZXEWO';
$appToken = 'TOBKCMNYXCXZWZCNXTZAAHHUSVWPOGNJGDWHAIWNOTGKGUWHRFHRYRLFHRYRLFJSEOFPDPNEWZWDIEWXNWNLWSXVPODRWJDDQSPLQGGCYQLKSAVHBVEINUONIAEPNOSUCTYVOX';
$accountName = 'lojaeucatex';
$environment = 'vtexcommercestable';
$EXT_EMAIL = 'jefferson@wicomm.com.br';
$EXT_API_KEY = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm92aWRlcl9pZCI6MSwiZW1haWwiOiJqZWZmZXJzb25Ad2ljb21tLmNvbS5iciJ9.zprWd1UZ9hv_5fjmy3TO63kE4f27vBFKHw-kW8ieEAg';
$action = isset($_GET['action']) ? $_GET['action'] : null;
/**
* Busca se existe um ranking cacheado na entidade MC para o SKU e CEP informado.
* O CEP pode ser um range, então verifica se está dentro do intervalo.
*
* @param int $skuId
* @param string $cep
* @return array|null
*/
function getCachedRankFromMC($skuId, $cep)
{
global $accountName, $environment;
$entity = 'MC';
$cepNum = intval(preg_replace('/[^0-9]/', '', $cep));
// Considera que MC pode ter campos: skuId, cepStart, cepEnd, ou simplesmente cep
// Busca por registros onde skuId bate e o cep está dentro do range (ou igual)
$where = "(skuId=$skuId AND ((cepStart<=$cepNum AND cepEnd>=$cepNum) OR cep=\"$cepNum\"))";
$url = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?_where=" . urlencode($where) . "&_fields=payload,cep,cepStart,cepEnd";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$docs = json_decode($result, true);
if ($docs && is_array($docs) && count($docs) > 0 && isset($docs[0]['payload'])) {
$payload = json_decode($docs[0]['payload'], true);
return is_array($payload) ? $payload : null;
}
return null;
}
/**
* Salva ou atualiza o ranking de sellers para SKU + CEP na entidade MC (Master Cache).
*
* @param int $skuId
* @param string $cep
* @param array $payload
*/
function saveRankToMC($skuId, $cep, $payload)
{
global $accountName, $environment;
$entity = 'MC';
$cepNum = intval(preg_replace('/[^0-9]/', '', $cep));
// Busca simples: apenas por skuId e cep exato (não usa intervalos)
$where = "_where=skuId=$skuId AND cep=\"$cepNum\"";
$searchUrl = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?$where&_fields=id";
error_log("[saveRankToMC] Buscando registro MC: $searchUrl");
$ch = curl_init($searchUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$docs = json_decode($result, true);
if ($docs === null) {
error_log("[saveRankToMC] ERRO ao decodificar resposta de busca MC: " . var_export($result, true));
}
// Payload JSON corretamente formatado, inclui campos obrigatórios
$data = [
'skuId' => $skuId,
'cep' => (string) $cepNum,
'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE),
'cepStart' => null,
'cepEnd' => null
];
// Atualiza se existe, senão cria novo
if ($docs && is_array($docs) && count($docs) > 0 && isset($docs[0]['id'])) {
$docId = $docs[0]['id'];
$updateUrl = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents/$docId";
error_log("[saveRankToMC] Atualizando registro existente MC id=$docId");
$ch = curl_init($updateUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
$res = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$updateResult = json_decode($res, true);
if ($httpCode >= 200 && $httpCode < 300) {
error_log("[saveRankToMC] Atualização MC OK: id=$docId");
} else {
error_log("[saveRankToMC] ERRO ao atualizar MC id=$docId: HTTP $httpCode - $res");
}
return [
'type' => 'update',
'id' => $docId,
'httpCode' => $httpCode,
'result' => $updateResult
];
} else {
$url = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents";
error_log("[saveRankToMC] Criando novo registro MC (skuId=$skuId, cep=$cepNum)");
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
$res = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$insertResult = json_decode($res, true);
if ($httpCode >= 200 && $httpCode < 300) {
error_log("[saveRankToMC] Inserção MC OK: skuId=$skuId, cep=$cepNum");
} else {
error_log("[saveRankToMC] ERRO ao inserir MC: HTTP $httpCode - $res");
}
return [
'type' => 'insert',
'httpCode' => $httpCode,
'result' => $insertResult
];
}
}
/**
* Set VTEX headers
*/
function vtexHeaders()
{
global $appKey, $appToken;
return [
"X-VTEX-API-AppKey: $appKey",
"X-VTEX-API-AppToken: $appToken",
"Content-Type: application/json"
];
}
/**
* Busca EANs da vitrine via API externa
*
* @param string $cep CEP
* @return array Lista de EANs
*/
function getEansFromShowcase($cep)
{
$url = "https://lojaeucatexapi.conectala.com.br/app/Api/V1/SellerRank/showcase";
$headers = [
"Content-Type: application/json",
"accept: application/json;charset=UTF-8",
"x-email: jefferson@wicomm.com.br",
"x-api-key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm92aWRlcl9pZCI6MSwiZW1haWwiOiJqZWZmZXJzb25Ad2ljb21tLmNvbS5iciJ9.zprWd1UZ9hv_5fjmy3TO63kE4f27vBFKHw-kW8ieEAg",
"x-provider-key: 5"
];
$payload = [
"marketplace" => "Wicommpartnerbr",
"cep" => preg_replace('/[^0-9]/', '', $cep)
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$result = curl_exec($ch);
curl_close($ch);
$data = json_decode($result, true);
$eans = [];
if (is_array($data)) {
$isFlatArray = count($data) > 0 && is_string(reset($data));
if ($isFlatArray) {
foreach ($data as $ean) {
if (is_string($ean) && $ean !== '')
$eans[] = $ean;
}
} elseif (isset($data['data']) && is_array($data['data'])) {
foreach ($data['data'] as $ean) {
if (is_string($ean) && $ean !== '')
$eans[] = $ean;
}
} elseif (isset($data['products']) && is_array($data['products'])) {
foreach ($data['products'] as $product) {
if (isset($product['ean']) && is_string($product['ean']) && $product['ean'] !== '') {
$eans[] = $product['ean'];
}
}
}
}
if (empty($eans)) {
error_log("SHOWCASE: nenhum EAN retornado");
} else {
error_log("SHOWCASE: EANs recebidos");
}
return $eans;
}
/**
* Busca SKUs na VTEX por uma lista de EANs
*
* @param array $eans Lista de EANs
* @return array Lista de SKUs encontrados
*/
function getSkusByEans($eans)
{
global $accountName, $environment, $debug;
$skus = [];
$totalFound = 0;
$eans = array_slice($eans, 0, 50); // segurança para evitar overload
foreach ($eans as $ean) {
error_log("PROCESSANDO EAN: " . $ean);
$url = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=alternateIds_Ean:$ean";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$data = json_decode($result, true);
if (!$data || !is_array($data) || count($data) === 0) {
$url = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=referenceId:$ean";
$ch2 = curl_init($url);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch2, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch2);
curl_close($ch2);
$data = json_decode($result, true);
}
if ($data && is_array($data)) {
$eanCount = 0;
foreach ($data as $product) {
if (isset($product['items']) && is_array($product['items']) && count($product['items']) > 0) {
foreach ($product['items'] as $item) {
$skus[] = $item;
$eanCount++;
}
}
}
$totalFound += $eanCount;
error_log("EAN {$ean}: {$eanCount} SKUs encontrados");
}
usleep(150000); // delay leve entre chamadas
unset($data);
gc_collect_cycles();
}
error_log("SEARCH BY EAN: consultas concluídas (" . count($eans) . "), SKUs encontrados: {$totalFound}");
error_log("SEARCH BY EAN: total de SKUs salvos após simulação: " . count($skus));
return $skus;
}
/**
* Busca SKUs do catálogo VTEX (nova versão usando endpoint /api/catalog_system/pub/products/search)
*
* @param int $batchSize Número de produtos por página
* @param int $maxBatches Número máximo de páginas a buscar
* @return array Lista de SKUs
*/
function getSkus($batchSize = 50, $maxBatches = 200)
{
global $accountName, $environment, $debug;
$skus = [];
$allIds = [];
$from = 0;
$loopCount = 0;
while ($loopCount < $maxBatches) {
$to = $from + $batchSize - 1;
$url = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?from=$from&to=$to";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$data = json_decode($result, true);
if (!$data || !is_array($data) || count($data) === 0) {
break;
}
foreach ($data as $product) {
if (isset($product['items']) && is_array($product['items'])) {
foreach ($product['items'] as $item) {
if (isset($item['itemId'])) {
$allIds[] = $item['itemId'];
}
}
}
}
$loopCount++;
$from += $batchSize;
if (count($data) < $batchSize) {
break;
}
}
error_log("CATALOG SEARCH: concluído");
foreach ($allIds as $skuId) {
$skuUrl = "https://$accountName.$environment.com.br/api/catalog_system/pvt/sku/stockkeepingunitbyid/$skuId";
$ch3 = curl_init($skuUrl);
curl_setopt($ch3, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch3, CURLOPT_HTTPHEADER, vtexHeaders());
$skuResult = curl_exec($ch3);
curl_close($ch3);
if ($skuResult)
$skus[] = json_decode($skuResult, true);
}
return $skus;
}
/**
* Função para simular entrega de um SKU por Seller e CEP
* Nova versão: re-simula se não houver SLA, usa salesChannel=1 como fallback, logs detalhados.
*/
function simulateDelivery($skuId, $sellerId, $cep)
{
global $accountName, $environment;
error_log("SIMULATION: testando SKU {$skuId}, seller {$sellerId}");
$salesChannel = isset($_GET['sc']) && $_GET['sc'] !== '' ? (string) $_GET['sc'] : '1';
$url = "https://$accountName.$environment.com.br/api/checkout/pub/orderForms/simulation";
$payload = [
"items" => [
[
"id" => (string) $skuId,
"quantity" => 1,
"seller" => (string) $sellerId
]
],
"postalCode" => preg_replace('/[^0-9]/', '', $cep),
"country" => "BRA",
"salesChannel" => $salesChannel
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$result = curl_exec($ch);
curl_close($ch);
if (!$result) {
error_log("SIMULATION FAIL: sem retorno para SKU {$skuId}, seller {$sellerId}");
return null;
}
$data = json_decode($result, true);
if (!$data || !isset($data['items'][0])) {
error_log("SIMULATION FAIL: retorno inválido para SKU {$skuId}, seller {$sellerId}");
return null;
}
$availability = $data['items'][0]['availability'] ?? 'unknown';
$sellingPrice = $data['items'][0]['sellingPrice'] ?? null;
// NOVA LÓGICA DE SLAS
$slas = [];
if (isset($data['shippingData']['logisticsInfo'][0]['slas'])) {
$slas = $data['shippingData']['logisticsInfo'][0]['slas'];
} elseif (isset($data['logisticsInfo'][0]['slas'])) {
// fallback: alguns ambientes VTEX retornam direto em logisticsInfo
$slas = $data['logisticsInfo'][0]['slas'];
} elseif (isset($data['purchaseConditions']['itemPurchaseConditions'][0]['slas'])) {
// fallback adicional baseado em purchaseConditions
$slas = $data['purchaseConditions']['itemPurchaseConditions'][0]['slas'];
}
$best = null;
// Se não houver SLAs, tenta novamente com salesChannel=1 e loga
if (empty($slas)) {
error_log("SIMULATION WARNING: nenhum SLA retornado para seller {$sellerId}, tentando re-simulação com salesChannel=1");
$payload['salesChannel'] = "1";
$ch2 = curl_init($url);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch2, CURLOPT_HTTPHEADER, vtexHeaders());
curl_setopt($ch2, CURLOPT_POST, true);
curl_setopt($ch2, CURLOPT_POSTFIELDS, json_encode($payload));
$retryResult = curl_exec($ch2);
curl_close($ch2);
if ($retryResult) {
$retryData = json_decode($retryResult, true);
// Repete a lógica de fallback de SLAs após re-simulação
if (isset($retryData['shippingData']['logisticsInfo'][0]['slas'])) {
$slas = $retryData['shippingData']['logisticsInfo'][0]['slas'];
} elseif (isset($retryData['logisticsInfo'][0]['slas'])) {
$slas = $retryData['logisticsInfo'][0]['slas'];
} elseif (isset($retryData['purchaseConditions']['itemPurchaseConditions'][0]['slas'])) {
$slas = $retryData['purchaseConditions']['itemPurchaseConditions'][0]['slas'];
}
if (!empty($slas)) {
$data = $retryData;
error_log("SIMULATION RECOVERED: SLA encontrado após re-simulação para seller {$sellerId}");
}
}
}
if (is_array($slas) && count($slas) > 0) {
foreach ($slas as $sla) {
$candidate = [
'name' => $sla['name'],
'price' => $sla['price'] ?? $sellingPrice,
'shippingEstimate' => $sla['shippingEstimate'] ?? 'indefinido',
'deliveryIds' => $sla['deliveryIds'] ?? []
];
if (
$best === null
|| $candidate['price'] < $best['price']
|| ($candidate['price'] == $best['price']
&& estimateToMinutes($candidate['shippingEstimate']) < estimateToMinutes($best['shippingEstimate']))
) {
$best = $candidate;
}
}
} elseif ($sellingPrice !== null) {
$best = [
'name' => 'Sem cálculo de frete',
'price' => $sellingPrice,
'shippingEstimate' => 'indefinido',
'deliveryIds' => []
];
}
// Se ainda não achou SLAs mas o produto tem entrega (purchaseConditions), tenta recuperar manualmente
if ((!$best || empty($best['shippingEstimate']) || strtolower($best['shippingEstimate']) === 'indefinido') && isset($data['purchaseConditions']['itemPurchaseConditions'][0]['slas'])) {
$manualSlas = $data['purchaseConditions']['itemPurchaseConditions'][0]['slas'];
if (is_array($manualSlas) && count($manualSlas) > 0) {
$firstSla = $manualSlas[0];
$best = [
'name' => $firstSla['name'] ?? 'Entrega própria',
'price' => $sellingPrice,
'shippingEstimate' => $firstSla['shippingEstimate'] ?? '3bd',
'deliveryIds' => $firstSla['deliveryIds'] ?? [],
'availability' => 'available'
];
error_log("SIMULATION RECOVERED (purchaseConditions): SKU {$skuId}, seller {$sellerId}, shipping={$best['shippingEstimate']}");
}
}
// Parcelamento
$installments = $data['paymentData']['installmentOptions'][0]['installments'][0]['count'] ?? null;
$installmentValue = $data['paymentData']['installmentOptions'][0]['installments'][0]['value'] ?? null;
$paymentName = $data['paymentData']['installmentOptions'][0]['paymentName'] ?? null;
if ($best) {
$best['installments'] = $installments;
$best['installmentValue'] = $installmentValue;
$best['paymentName'] = $paymentName;
$best['availability'] = $availability;
error_log("SIMULATION OK: SKU {$skuId}, seller {$sellerId}, price={$best['price']}, shipping={$best['shippingEstimate']}");
} else {
error_log("SIMULATION FAIL FINAL: SKU {$skuId}, seller {$sellerId}, sem opções de entrega");
}
return $best;
}
/**
* Função para salvar ou atualizar dados no Master Data v1 (entidade 'RA')
*/
function saveMasterData($data)
{
global $accountName, $environment;
$entity = 'RA';
$url = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents";
$where = "skuId={$data['skuId']} AND sellerId=\"{$data['sellerId']}\" AND cep=\"{$data['cep']}\"";
$searchUrl = $url . "?_where=" . urlencode($where) . "&_fields=id";
$ch = curl_init($searchUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$docs = json_decode($result, true);
if ($docs && count($docs) > 0 && isset($docs[0]['id'])) {
$docId = $docs[0]['id'];
$updateUrl = "https://$accountName.$environment.com.br/api/dataentities/$entity/documents/$docId";
$ch = curl_init($updateUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$res = curl_exec($ch);
curl_close($ch);
return json_decode($res, true);
} else {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$res = curl_exec($ch);
curl_close($ch);
return json_decode($res, true);
}
}
/**
* Função para buscar o melhor seller para um SKU e CEP
*/
function getBestSeller($skuId, $cep)
{
global $accountName, $environment;
$entity = 'RA';
$url = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?_where=skuId=$skuId AND cep=\"$cep\"&_fields=sellerId,price,shippingEstimate";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$docs = json_decode($result, true);
$best = null;
if ($docs && count($docs) > 0) {
foreach ($docs as $doc) {
if (!isset($doc['price']))
continue;
if ($best === null || $doc['price'] < $best['price'] || ($doc['price'] == $best['price'] && $doc['shippingEstimate'] < $best['shippingEstimate'])) {
$best = $doc;
}
}
}
return $best;
}
/**
* Converte estimativa VTEX (ex: "2bd", "3d", "48h") para minutos comparáveis
*
* @param string $estimate
* @return int
*/
function estimateToMinutes($estimate)
{
if (!$estimate || !is_string($estimate))
return PHP_INT_MAX;
if (trim(strtolower($estimate)) === 'indefinido')
return PHP_INT_MAX - 1;
if (!preg_match('/^(\d+)\s*([a-zA-Z]+)/', $estimate, $m))
return PHP_INT_MAX;
$n = intval($m[1]);
$u = strtolower($m[2]);
if ($u === 'bd')
return $n * 1440; // business day ~ 1 dia
if ($u === 'd')
return $n * 1440;
if ($u === 'h')
return $n * 60;
if ($u === 'm')
return $n;
return PHP_INT_MAX;
}
/**
* Busca sellers elegíveis a partir da entidade DS pelo CEP (intervalo postalCodeStart..postalCodeEnd)
*
* @param string $cep
* @return array
*/
function getSellersFromDS($cep)
{
global $accountName, $environment;
$entity = 'DS';
$cepNum = intval(preg_replace('/[^0-9]/', '', $cep));
$where = "postalCodeStart<=$cepNum AND postalCodeEnd>=$cepNum";
$url = "https://$accountName.$environment.com.br/api/dataentities/$entity/search?_where=" . urlencode($where) . "&_fields=sellerId,sellerName,score&_sort=score ASC&_size=200";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$result = curl_exec($ch);
curl_close($ch);
$docs = json_decode($result, true);
if (!$docs || !is_array($docs))
return [];
$out = [];
foreach ($docs as $d) {
$out[] = [
'sellerId' => $d['sellerId'] ?? null,
'sellerName' => $d['sellerName'] ?? null,
'score' => isset($d['score']) ? intval($d['score']) : PHP_INT_MAX,
];
}
return $out;
}
/**
* Ranqueia sellers (DS) para um SKU com base em score, tempo de entrega e preço
* Retorna todos os sellers válidos, adiciona availability e source, salva no Master Data sellers disponíveis e loga progresso.
* O retorno agora inclui um array 'meta' com informações extras.
*
* @param int $skuId
* @param string $cep
* @return array
*/
function rankSellersForSku($skuId, $cep)
{
global $accountName, $environment;
$candidates = getSellersFromDS($cep);
$source = 'ds';
if (empty($candidates)) {
$fallbackUrl = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=skuId:$skuId";
$ch = curl_init($fallbackUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, vtexHeaders());
$fallbackResult = curl_exec($ch);
curl_close($ch);
$fallbackData = json_decode($fallbackResult, true);
if ($fallbackData && isset($fallbackData[0]['items'][0]['sellers'])) {
foreach ($fallbackData[0]['items'][0]['sellers'] as $seller) {
$candidates[] = [
'sellerId' => $seller['sellerId'] ?? null,
'sellerName' => $seller['sellerName'] ?? ($seller['SellerName'] ?? 'Sem nome'),
'score' => 999
];
}
$source = 'catalog_fallback';
error_log("RANK FALLBACK: sellers carregados do catálogo VTEX");
}
}
$ranked = [];
foreach ($candidates as $c) {
if (empty($c['sellerId']))
continue;
$sim = simulateDelivery($skuId, $c['sellerId'], $cep);
if ($sim) {
$isDeliverable = isset($sim['shippingEstimate'])
&& strtolower($sim['shippingEstimate']) !== 'indefinido'
&& strtolower($sim['availability']) === 'available'
&& !empty($sim['name'])
&& strtolower($sim['name']) !== 'sem cálculo de frete';
if (!$isDeliverable) {
error_log("SKIP SELLER: {$c['sellerId']} sem entrega válida (estimate={$sim['shippingEstimate']}, sla={$sim['name']})");
continue;
}
$ranked[] = [
'sellerId' => $c['sellerId'],
'sellerName' => $c['sellerName'] ?? 'Sem nome',
'score' => $c['score'] ?? 999,
'price' => $sim['price'],
'shippingEstimate' => $sim['shippingEstimate'],
'slaName' => $sim['name'],
'availability' => 'available',
'source' => $source,
'estimateMinutes' => estimateToMinutes($sim['shippingEstimate'])
];
saveMasterData([
'skuId' => $skuId,
'sellerId' => $c['sellerId'],
'cep' => $cep,
'price' => $sim['price'],
'shippingEstimate' => $sim['shippingEstimate'],
'slaName' => $sim['name']
]);
}
}
usort($ranked, function ($a, $b) {
if ($a['score'] !== $b['score'])
return $a['score'] <=> $b['score'];
$aIndef = ($a['estimateMinutes'] >= (PHP_INT_MAX - 2));
$bIndef = ($b['estimateMinutes'] >= (PHP_INT_MAX - 2));
if ($aIndef && !$bIndef)
return 1;
if (!$aIndef && $bIndef)
return -1;
if ($a['estimateMinutes'] !== $b['estimateMinutes'])
return $a['estimateMinutes'] <=> $b['estimateMinutes'];
return $a['price'] <=> $b['price'];
});
error_log("RANK: {$skuId} total " . count($ranked) . " sellers ranqueados");
return [
'ranked' => $ranked,
'meta' => [
'sourceUsed' => $source,
'totalCandidates' => count($candidates),
'totalRanked' => count($ranked),
'fallbackTriggered' => ($source === 'catalog_fallback')
]
];
}
switch ($action) {
case 'cron':
$cep = isset($_GET['cep']) ? $_GET['cep'] : '01001000';
error_log("CRON: iniciado");
$eans = getEansFromShowcase($cep);
$eans = array_slice($eans, 50, 80);
$skus = getSkusByEans($eans);
$skus = array_slice($skus, 0, 10);
$results = [];
$totalSkus = count($skus);
$totalSellers = 0;
$totalSimulations = 0;
foreach ($skus as $sku) {
$skuId = $sku['itemId'] ?? $sku['Id'] ?? null;
if (!$skuId)
continue;
$skuDetailUrl = "https://$accountName.$environment.com.br/api/catalog_system/pvt/sku/stockkeepingunitbyid/$skuId";
$chDetail = curl_init($skuDetailUrl);
curl_setopt($chDetail, CURLOPT_RETURNTRANSFER, true);
curl_setopt($chDetail, CURLOPT_HTTPHEADER, vtexHeaders());
$skuDetailResult = curl_exec($chDetail);
curl_close($chDetail);
$skuDetail = json_decode($skuDetailResult, true);
// if (!$skuDetail || !isset($skuDetail['IsActive']) || !$skuDetail['IsActive'] || !$skuDetail['ProductIsVisible']) continue;
$skuSellers = [];
if (isset($skuDetail['SkuSellers']) && is_array($skuDetail['SkuSellers'])) {
$skuSellers = $skuDetail['SkuSellers'];
} elseif (isset($skuDetail['Sellers']) && is_array($skuDetail['Sellers'])) {
$skuSellers = $skuDetail['Sellers'];
}
if (count($skuSellers) === 0) {
// Tenta buscar sellers pelo endpoint público
$fallbackUrl = "https://$accountName.$environment.com.br/api/catalog_system/pub/products/search?fq=skuId:$skuId";
$chFallback = curl_init($fallbackUrl);
curl_setopt($chFallback, CURLOPT_RETURNTRANSFER, true);
curl_setopt($chFallback, CURLOPT_HTTPHEADER, vtexHeaders());
$fallbackResult = curl_exec($chFallback);
curl_close($chFallback);
$fallbackData = json_decode($fallbackResult, true);
if ($fallbackData && isset($fallbackData[0]['items'][0]['sellers'])) {
$skuSellers = $fallbackData[0]['items'][0]['sellers'];
error_log("FALLBACK: sellers encontrados via search para SKU {$skuId}");
} else {
// Força seller padrão 1 para teste de simulação
$skuSellers = [['SellerId' => '1']];
error_log("FALLBACK: nenhum seller encontrado, forçando sellerId=1 para SKU {$skuId}");
}
}
// Força sellerId=1 para teste completo de simulação e salvamento
foreach ($skuSellers as $seller) {
$sellerId = $seller['SellerId'] ?? '1';
$totalSellers++;
$sim = simulateDelivery($skuId, $sellerId, $cep);
$totalSimulations++;
if ($sim) {
$data = [
'skuId' => $skuId,
'sellerId' => $sellerId,
'cep' => $cep,
'price' => $sim['price'],
'shippingEstimate' => $sim['shippingEstimate'],
'slaName' => $sim['name']
];
saveMasterData($data);
$results[] = $data;
}
}
}
error_log("CRON: finalizado processed=" . count($results) . " skus=" . $totalSkus . " sellers=" . $totalSellers . " simulations=" . $totalSimulations);
header('Content-Type: application/json');
$output = [
'processed' => count($results),
'totalSkus' => $totalSkus,
'totalSellers' => $totalSellers,
'totalSimulations' => $totalSimulations,
'results' => $results
];
echo json_encode($output, JSON_PRETTY_PRINT);
break;
case 'seller':
$skuId = isset($_GET['skuId']) ? intval($_GET['skuId']) : null;
$cep = isset($_GET['cep']) ? $_GET['cep'] : null;
if (!$skuId || !$cep) {
http_response_code(400);
echo json_encode(['error' => 'skuId and cep are required']);
exit;
}
$best = getBestSeller($skuId, $cep);
header('Content-Type: application/json');
if ($best) {
echo json_encode($best);
} else {
echo json_encode(['error' => 'No seller found']);
}
break;
case 'rank':
$skuId = isset($_GET['skuId']) ? intval($_GET['skuId']) : null;
$cep = isset($_GET['cep']) ? $_GET['cep'] : null;
if (!$skuId || !$cep) {
http_response_code(400);
echo json_encode(['error' => 'skuId and cep are required']);
break;
}
// 1. Tenta cache MC
$cached = getCachedRankFromMC($skuId, $cep);
if (is_array($cached) && isset($cached['sellers']) && is_array($cached['sellers'])) {
header('Content-Type: application/json');
echo json_encode([
'skuId' => $skuId,
'cep' => preg_replace('/[^0-9]/', '', $cep),
'count' => isset($cached['count']) ? $cached['count'] : count($cached['sellers']),
'sellers' => $cached['sellers'],
'explanation' => [
'sourceUsed' => 'cache_MC'
]
], JSON_PRETTY_PRINT);
break;
}
// 2. Não tem cache: executa ranking e salva no MC
$rankInfo = rankSellersForSku($skuId, $cep);
$ranked = $rankInfo['ranked'];
$meta = $rankInfo['meta'];
// Formata sellers (igual ao retorno atual)
$sellersArr = array_map(function ($r) {
return [
'sellerId' => $r['sellerId'],
'sellerName' => $r['sellerName'],
'score' => $r['score'],
'price' => $r['price'],
'shippingEstimate' => $r['shippingEstimate'],
'slaName' => $r['slaName'],
'availability' => $r['availability'],
'source' => $r['source']
];
}, $ranked);
$payloadToSave = [
'count' => count($sellersArr),
'sellers' => $sellersArr,
'meta' => $meta
];
saveRankToMC($skuId, $cep, $payloadToSave);
header('Content-Type: application/json');
echo json_encode([
'skuId' => $skuId,
'cep' => preg_replace('/[^0-9]/', '', $cep),
'count' => count($sellersArr),
'sellers' => $sellersArr,
'explanation' => [
'sourceUsed' => $meta['sourceUsed'],
'totalCandidates' => $meta['totalCandidates'],
'totalRanked' => $meta['totalRanked'],
'fallbackTriggered' => $meta['fallbackTriggered']
]
], JSON_PRETTY_PRINT);
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Invalid or missing action parameter']);
break;
}
// FIM DO ARQUIVO