yazrkisvs3vvvsv33svrhetrjsvyvsvvyjsvsvs
tavsuysvssvvjvvyjyjkvsvqd
qfogsvvsvsegjdgdfgdskhgdksvqsvshdqsd
<?php
// api/gsc-data.php — Datos reales de Google Search Console por cliente
header('Content-Type: application/json');
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../credentials.php';
require_once __DIR__ . '/google-auth.php';
dm_require_login();
dm_check_module_access('seo');

$client_id = (int)($_GET['client_id'] ?? 0);
$period    = $_GET['period'] ?? '30d';
$action    = $_GET['action'] ?? 'summary';
if (!$client_id) { echo json_encode(['ok'=>false,'error'=>'client_id requerido']); exit; }
dm_require_client_access($client_id);

// ── ACCIÓN: position_history — posición promedio diaria desde dm_metrics_history ──
if ($action === 'position_history') {
    require_once __DIR__ . '/../db.php';
    $days = (int)([ '30d'=>30,'60d'=>60,'90d'=>90 ][$_GET['days'] ?? '30d'] ?? 30);
    $stmt = $pdo->prepare(
        "SELECT metric_date, data FROM dm_metrics_history
         WHERE client_id = ? AND service = 'gsc'
           AND metric_date >= CURDATE() - INTERVAL ? DAY
         ORDER BY metric_date ASC"
    );
    $stmt->execute([$client_id, $days]);
    $history = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
        $d = json_decode($r['data'], true) ?? [];
        $pos = round((float)($d['position'] ?? 0), 1);
        if ($pos > 0) {
            $history[] = ['date' => $r['metric_date'], 'position' => $pos, 'clicks' => (int)($d['clicks'] ?? 0)];
        }
    }
    echo json_encode(['ok' => true, 'data' => $history]);
    exit;
}

$creds        = dm_get_credentials($client_id, 'gsc');
$domain       = $creds['domain'] ?? '';
$access_token = dm_google_access_token('https://www.googleapis.com/auth/webmasters.readonly');

if (!$domain) {
    echo json_encode(['ok'=>false,'error'=>'Configura el dominio de Search Console para este cliente']); exit;
}
if (!$access_token) {
    echo json_encode(['ok'=>false,'error'=>'No se pudo obtener token de Google Search Console']); exit;
}

// Normalizar dominio
if (!str_starts_with($domain, 'sc-domain:') && !str_starts_with($domain, 'https://') && !str_starts_with($domain, 'http://')) {
    $domain = 'https://' . $domain . '/';
} elseif (str_starts_with($domain, 'https://') && !str_ends_with($domain, '/')) {
    $domain = $domain . '/';
}

// ── ACCIÓN: cobertura — indexación desde GSC Sitemaps API ────────────────────
// Debe evaluarse ANTES del cache general (que es para summary)
if ($action === 'cobertura') {
    $sm_url = 'https://www.googleapis.com/webmasters/v3/sites/' . urlencode($domain) . '/sitemaps';
    $ch = curl_init($sm_url);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$access_token], CURLOPT_TIMEOUT=>15]);
    $raw = curl_exec($ch); curl_close($ch);
    $res = json_decode($raw, true) ?? [];
    $sitemaps = $res['sitemap'] ?? [];
    $total_submitted = $total_indexed = $total_errors = $total_warnings = 0;
    $sm_list = [];
    foreach ($sitemaps as $sm) {
        $sub = $idx = $err = $warn = 0;
        foreach ($sm['contents'] ?? [] as $c) {
            $sub  += (int)($c['submitted'] ?? 0);
            $idx  += (int)($c['indexed']   ?? 0);
        }
        $err  = (int)($sm['errors']   ?? 0);
        $warn = (int)($sm['warnings'] ?? 0);
        $total_submitted += $sub; $total_indexed += $idx;
        $total_errors    += $err; $total_warnings += $warn;
        $sm_list[] = [
            'path'      => $sm['path']            ?? '—',
            'submitted' => $sub,
            'indexed'   => $idx,
            'errors'    => $err,
            'warnings'  => $warn,
            'last_dl'   => substr($sm['lastDownloaded'] ?? '', 0, 10),
        ];
    }
    echo json_encode(['ok'=>true,'data'=>[
        'submitted'   => $total_submitted,
        'indexed'     => $total_indexed,
        'not_indexed' => max(0, $total_submitted - $total_indexed),
        'errors'      => $total_errors,
        'warnings'    => $total_warnings,
        'sitemaps'    => $sm_list,
    ]]);
    exit;
}

// ── Cache para summary (no aplica a cobertura ni position_history) ────────────
$cache_key = "gsc_v2_{$client_id}_{$period}";
$cached = dm_cache_get($cache_key);
if ($cached) { echo json_encode(['ok'=>true,'data'=>$cached,'cached'=>true]); exit; }

$days_map = ['7d'=>7,'30d'=>30,'90d'=>90];
$days      = $days_map[$period] ?? 30;
$date_to   = date('Y-m-d', strtotime('-3 days'));
$date_from = date('Y-m-d', strtotime("-{$days} days"));
// Período anterior para deltas
$prev_date_to   = date('Y-m-d', strtotime($date_from . ' -1 day'));
$prev_date_from = date('Y-m-d', strtotime($prev_date_to . " -{$days} days"));

function gsc_query(string $domain, array $body, string $token): array {
    $url = 'https://searchconsole.googleapis.com/webmasters/v3/sites/' . urlencode($domain) . '/searchAnalytics/query';
    $ch  = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode($body),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Authorization: Bearer ' . $token],
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $result = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $result ?? [];
}

// Consultas paralelas a GSC
$summary_res = gsc_query($domain, ['startDate'=>$date_from,'endDate'=>$date_to,'dimensions'=>[],'rowLimit'=>1], $access_token);
if (isset($summary_res['error'])) {
    echo json_encode(['ok'=>false,'error'=>$summary_res['error']['message'] ?? 'Error GSC API']); exit;
}

$prev_res    = gsc_query($domain, ['startDate'=>$prev_date_from,'endDate'=>$prev_date_to,'dimensions'=>[],'rowLimit'=>1], $access_token);
$kw_res      = gsc_query($domain, ['startDate'=>$date_from,'endDate'=>$date_to,'dimensions'=>['query'],'rowLimit'=>50,'orderBy'=>[['fieldName'=>'impressions','sortOrder'=>'DESCENDING']]], $access_token);
$pages_res   = gsc_query($domain, ['startDate'=>$date_from,'endDate'=>$date_to,'dimensions'=>['page'],'rowLimit'=>15,'orderBy'=>[['fieldName'=>'clicks','sortOrder'=>'DESCENDING']]], $access_token);
$trend_res   = gsc_query($domain, ['startDate'=>$date_from,'endDate'=>$date_to,'dimensions'=>['date'],'rowLimit'=>90], $access_token);
$device_res  = gsc_query($domain, ['startDate'=>$date_from,'endDate'=>$date_to,'dimensions'=>['device'],'rowLimit'=>5], $access_token);
$country_res = gsc_query($domain, ['startDate'=>$date_from,'endDate'=>$date_to,'dimensions'=>['country'],'rowLimit'=>10,'orderBy'=>[['fieldName'=>'clicks','sortOrder'=>'DESCENDING']]], $access_token);

// Métricas principales
$s_row             = $summary_res['rows'][0] ?? [];
$total_clicks      = (int)($s_row['clicks'] ?? 0);
$total_impressions = (int)($s_row['impressions'] ?? 0);
$avg_ctr           = round((float)($s_row['ctr'] ?? 0) * 100, 1);
$avg_position      = round((float)($s_row['position'] ?? 0), 1);

// Deltas vs período anterior
$p_row    = $prev_res['rows'][0] ?? [];
$p_clicks = (int)($p_row['clicks'] ?? 0);
$p_impr   = (int)($p_row['impressions'] ?? 0);
$p_ctr    = round((float)($p_row['ctr'] ?? 0) * 100, 1);
$p_pos    = round((float)($p_row['position'] ?? 0), 1);

function pct_delta(float $now, float $prev): ?float {
    if ($prev == 0) return null;
    return round(($now - $prev) / $prev * 100, 1);
}

$deltas = [
    'clicks'      => pct_delta($total_clicks, $p_clicks),
    'impressions' => pct_delta($total_impressions, $p_impr),
    'ctr'         => $p_ctr > 0 ? round($avg_ctr - $p_ctr, 1) : null,
    'position'    => $p_pos > 0 ? round($p_pos - $avg_position, 1) : null, // positivo = mejoró
];

// Keywords
$kw_list = [];
foreach (($kw_res['rows'] ?? []) as $r) {
    $pos = round((float)($r['position'] ?? 0), 1);
    $ctr = round((float)($r['ctr'] ?? 0) * 100, 1);
    $imp = (int)($r['impressions'] ?? 0);
    $clk = (int)($r['clicks'] ?? 0);
    $kw_list[] = [
        'keyword'     => $r['keys'][0] ?? '',
        'clicks'      => $clk,
        'impressions' => $imp,
        'ctr'         => $ctr,
        'position'    => $pos,
        'pos_label'   => $pos <= 3 ? 'top3' : ($pos <= 10 ? 'top10' : 'other'),
    ];
}

// Oportunidades: posición 4-20 con impresiones > 30
$quick_wins = array_values(array_filter($kw_list, fn($k) => $k['position'] >= 4 && $k['position'] <= 20 && $k['impressions'] >= 30));
usort($quick_wins, fn($a,$b) => $b['impressions'] <=> $a['impressions']);
$quick_wins = array_slice($quick_wins, 0, 15);

// CTR bajas: impresiones > 50 y CTR < 3%
$low_ctr = array_values(array_filter($kw_list, fn($k) => $k['impressions'] >= 50 && $k['ctr'] < 3.0));
usort($low_ctr, fn($a,$b) => $b['impressions'] <=> $a['impressions']);
$low_ctr = array_slice($low_ctr, 0, 15);

// Sin clics: impresiones pero 0 clics
$no_clicks = array_values(array_filter($kw_list, fn($k) => $k['clicks'] === 0 && $k['impressions'] >= 20));
usort($no_clicks, fn($a,$b) => $b['impressions'] <=> $a['impressions']);
$no_clicks = array_slice($no_clicks, 0, 10);

// Páginas
$pages_list = [];
foreach (($pages_res['rows'] ?? []) as $r) {
    $pages_list[] = [
        'page'        => $r['keys'][0] ?? '',
        'clicks'      => (int)($r['clicks'] ?? 0),
        'impressions' => (int)($r['impressions'] ?? 0),
        'ctr'         => round((float)($r['ctr'] ?? 0) * 100, 1),
        'position'    => round((float)($r['position'] ?? 0), 1),
    ];
}

// Tendencia diaria
$trend = [];
foreach (($trend_res['rows'] ?? []) as $r) {
    $trend[] = ['date'=>$r['keys'][0],'clicks'=>(int)($r['clicks'] ?? 0),'impressions'=>(int)($r['impressions'] ?? 0)];
}

// Dispositivos
$devices = [];
foreach (($device_res['rows'] ?? []) as $r) {
    $devices[strtolower($r['keys'][0] ?? '')] = [
        'clicks'      => (int)($r['clicks'] ?? 0),
        'impressions' => (int)($r['impressions'] ?? 0),
        'ctr'         => round((float)($r['ctr'] ?? 0) * 100, 1),
        'position'    => round((float)($r['position'] ?? 0), 1),
    ];
}

// Países (normalizar código ISO a nombre)
$country_names = ['usa'=>'Estados Unidos','col'=>'Colombia','mex'=>'México','arg'=>'Argentina','esp'=>'España','chl'=>'Chile','per'=>'Perú','ecu'=>'Ecuador','ven'=>'Venezuela','gtm'=>'Guatemala','cri'=>'Costa Rica','pan'=>'Panamá','dom'=>'Rep. Dominicana','bra'=>'Brasil','gbr'=>'Reino Unido','deu'=>'Alemania','fra'=>'Francia','ita'=>'Italia','can'=>'Canadá','aus'=>'Australia'];
$countries = [];
foreach (($country_res['rows'] ?? []) as $r) {
    $code = strtolower($r['keys'][0] ?? '');
    $countries[] = [
        'code'        => $code,
        'name'        => $country_names[$code] ?? strtoupper($code),
        'clicks'      => (int)($r['clicks'] ?? 0),
        'impressions' => (int)($r['impressions'] ?? 0),
        'ctr'         => round((float)($r['ctr'] ?? 0) * 100, 1),
    ];
}

$top3  = count(array_filter($kw_list, fn($k) => $k['position'] <= 3));
$top10 = count(array_filter($kw_list, fn($k) => $k['position'] <= 10));

$data = [
    'metrics' => [
        'clicks'      => $total_clicks,
        'impressions' => $total_impressions,
        'ctr'         => $avg_ctr,
        'avg_position'=> $avg_position,
        'top3'        => $top3,
        'top10'       => $top10,
    ],
    'deltas'     => $deltas,
    'keywords'   => $kw_list,
    'pages'      => $pages_list,
    'trend'      => $trend,
    'devices'    => $devices,
    'countries'  => $countries,
    'quick_wins' => $quick_wins,
    'low_ctr'    => $low_ctr,
    'no_clicks'  => $no_clicks,
    'period'     => ['from'=>$date_from,'to'=>$date_to,'prev_from'=>$prev_date_from,'prev_to'=>$prev_date_to],
];

dm_cache_set($cache_key, $data, 60);
echo json_encode(['ok'=>true,'data'=>$data]);
