<?php
// api/google-ads.php — Google Ads completo: campañas, grupos, anuncios, keywords, términos, competencia, billing, negativos IA
header('Content-Type: application/json');
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../db.php';
require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../credentials.php';
require_once __DIR__ . '/../helpers/http.php';
require_once __DIR__ . '/../helpers/json.php';
dm_require_login();

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

require_once __DIR__ . '/google-auth.php';

$creds       = dm_get_credentials($client_id, 'google');
$dev_token   = defined('GOOGLE_DEV_TOKEN') ? GOOGLE_DEV_TOKEN : '';
$customer_id = $creds['customer_id'] ?? (defined('GOOGLE_CUSTOMER_ID') ? GOOGLE_CUSTOMER_ID : '');
$mcc_id      = $creds['mcc_id']      ?? (defined('GOOGLE_MCC_ID')      ? GOOGLE_MCC_ID      : '');

if (!$customer_id) { echo json_encode(['ok'=>false,'error'=>'Customer ID no configurado para este cliente']); exit; }

$access_token = dm_google_access_token();
if (!$access_token) { echo json_encode(['ok'=>false,'error'=>'No se pudo obtener access token de Google.']); exit; }

$customer_id = preg_replace('/[^0-9]/', '', $customer_id);
$mcc_id      = preg_replace('/[^0-9]/', '', $mcc_id);

// FASE 2: SSL on, retry para 429/5xx, error mapping específico Google Ads
function _gads_headers(string $dev, string $at, string $cid, string $mcc): array {
    $h = ['Authorization: Bearer ' . $at, 'developer-token: ' . $dev];
    if ($mcc && $mcc !== $cid) $h[] = 'login-customer-id: ' . $mcc;
    return $h;
}

function gads_err(int $http, $raw_or_data): string {
    $e = is_array($raw_or_data) ? $raw_or_data : (dm_json_decode_or($raw_or_data, []));
    $base = dm_json_path($e, 'error.message', "HTTP $http");
    // Extraer detalle específico de GoogleAdsFailure
    foreach (dm_json_path($e, 'error.details', []) ?: [] as $d) {
        foreach ($d['errors'] ?? [] as $ge) {
            if (!empty($ge['message'])) { $base .= ': ' . $ge['message']; break 2; }
        }
        if (!empty($d['fieldViolations'][0]['description'])) {
            $base .= ': ' . $d['fieldViolations'][0]['description']; break;
        }
    }
    if ($http === 401 || $http === 403) $base .= ' — Reconecta la cuenta Google Ads';
    if ($http === 404) $base .= ' — Customer ID no encontrado, verifica el ID de Google Ads';
    return $base;
}

function gads(string $cid, string $q, string $dev, string $at, string $mcc): array {
    $url = "https://googleads.googleapis.com/v22/customers/{$cid}/googleAds:search";
    $h   = _gads_headers($dev, $at, $cid, $mcc);
    $r   = dm_http_post($url, ['query' => $q], $h, ['timeout' => 30, 'retries' => 2]);
    if (!$r['ok']) {
        return ['_err' => gads_err($r['status'], $r['data'] ?: $r['error'])];
    }
    return $r['data'] ?? [];
}

// Igual que gads() pero pagina automáticamente hasta traer TODOS los resultados
function gads_all(string $cid, string $q, string $dev, string $at, string $mcc): array {
    $url = "https://googleads.googleapis.com/v22/customers/{$cid}/googleAds:search";
    $h   = _gads_headers($dev, $at, $cid, $mcc);
    $all = []; $token = null; $pages = 0;
    do {
        $body = ['query' => $q];
        if ($token) $body['pageToken'] = $token;
        $r = dm_http_post($url, $body, $h, ['timeout' => 30, 'retries' => 2]);
        if (!$r['ok']) {
            return ['_err' => gads_err($r['status'], $r['data'] ?: $r['error'])];
        }
        $all   = array_merge($all, $r['data']['results'] ?? []);
        $token = $r['data']['nextPageToken'] ?? null;
        $pages++;
    } while ($token && $pages < 20);
    return ['results' => $all];
}

$period   = $_GET['period'] ?? '30d';
// Fechas personalizadas tienen prioridad sobre el período predefinido
if (!empty($_GET['date_from']) && !empty($_GET['date_to']) &&
    preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['date_from']) &&
    preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['date_to'])) {
    $df = $_GET['date_from'];
    $dt = $_GET['date_to'];
} else {
    $days_map = ['7d'=>7,'14d'=>14,'30d'=>30,'60d'=>60,'90d'=>90];
    $days = $days_map[$period] ?? 30;
    $df   = date('Y-m-d', strtotime("-{$days} days"));
    $dt   = date('Y-m-d');
}
$only_active = ($_GET['only_active'] ?? '0') === '1';

switch ($action) {

    // ── CAMPAÑAS ─────────────────────────────────────────────────────────────────
    case 'campaigns':
        $oa_key    = $only_active ? '1' : '0';
        $cache_key = "gads_camps_{$client_id}_{$df}_{$dt}_{$oa_key}";
        if ($c = dm_cache_get($cache_key)) { echo json_encode(['ok'=>true,'campaigns'=>$c,'cached'=>true]); exit; }

        $status_filter = $only_active ? "AND campaign.status = 'ENABLED'" : "AND campaign.status IN ('ENABLED','PAUSED')";
        $q = "SELECT campaign.id,campaign.name,campaign.status,campaign.resource_name,
                campaign.advertising_channel_type,campaign.bidding_strategy_type,
                campaign_budget.resource_name,campaign_budget.amount_micros,
                metrics.cost_micros,metrics.impressions,metrics.clicks,metrics.ctr,
                metrics.average_cpc,metrics.conversions,metrics.conversions_value,
                metrics.search_impression_share,metrics.search_budget_lost_impression_share,
                metrics.search_rank_lost_impression_share
              FROM campaign
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                {$status_filter}
              ORDER BY metrics.cost_micros DESC LIMIT 50";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $list = [];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[]; $c=$r['campaign']??[]; $b=$r['campaignBudget']??[];
            $sp=round(($m['costMicros']??0)/1e6,2); $cv=round((float)($m['conversions']??0),1); $vl=round((float)($m['conversionsValue']??0),2);
            $list[]=['id'=>$c['id']??'','name'=>$c['name']??'','status'=>strtolower($c['status']??''),
                'resource_name'=>$c['resourceName']??'','budget_resource_name'=>$b['resourceName']??'',
                'type'=>$c['advertisingChannelType']??'','bidding'=>$c['biddingStrategyType']??'',
                'budget'=>round(($b['amountMicros']??0)/1e6,2),'spend'=>$sp,'impressions'=>(int)($m['impressions']??0),
                'clicks'=>(int)($m['clicks']??0),'ctr'=>round((float)($m['ctr']??0)*100,2),
                'cpc'=>round(($m['averageCpc']??0)/1e6,2),'conversions'=>$cv,'conv_value'=>$vl,
                'roas'=>$sp>0?round($vl/$sp,2):0,'cpa'=>$cv>0?round($sp/$cv,2):0,
                'imp_share'=>round((float)($m['searchImpressionShare']??0)*100,1),
                'lost_budget'=>round((float)($m['searchBudgetLostImpressionShare']??0)*100,1),
                'lost_rank'=>round((float)($m['searchRankLostImpressionShare']??0)*100,1)];
        }
        dm_cache_set($cache_key,$list,20);
        echo json_encode(['ok'=>true,'campaigns'=>$list,'period'=>$period]);
        break;

    // ── GRUPOS DE ANUNCIOS ───────────────────────────────────────────────────────
    case 'adgroups':
        $camp_filter = (int)($_GET['campaign_id']??0);
        $where = $camp_filter ? "AND campaign.id = $camp_filter" : '';
        $camp_status = $only_active ? "AND campaign.status = 'ENABLED'" : "AND campaign.status IN ('ENABLED','PAUSED')";
        $q = "SELECT ad_group.id,ad_group.name,ad_group.status,campaign.id,campaign.name,
                metrics.cost_micros,metrics.impressions,metrics.clicks,metrics.ctr,
                metrics.average_cpc,metrics.conversions,metrics.conversions_value
              FROM ad_group
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                AND ad_group.status IN ('ENABLED','PAUSED')
                {$camp_status} {$where}
              ORDER BY metrics.cost_micros DESC LIMIT 100";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $list=[];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[];$ag=$r['adGroup']??[];$c=$r['campaign']??[];
            $sp=round(($m['costMicros']??0)/1e6,2); $cv=round((float)($m['conversions']??0),1);
            $list[]=['id'=>$ag['id']??'','name'=>$ag['name']??'','status'=>strtolower($ag['status']??''),
                'campaign_id'=>$c['id']??'','campaign'=>$c['name']??'',
                'spend'=>$sp,'impressions'=>(int)($m['impressions']??0),'clicks'=>(int)($m['clicks']??0),
                'ctr'=>round((float)($m['ctr']??0)*100,2),'cpc'=>round(($m['averageCpc']??0)/1e6,2),
                'conversions'=>$cv,'roas'=>$sp>0?round((float)($m['conversionsValue']??0)/$sp,2):0];
        }
        echo json_encode(['ok'=>true,'adgroups'=>$list,'period'=>$period]);
        break;

    // ── ANUNCIOS ─────────────────────────────────────────────────────────────────
    case 'ads':
        $camp_filter = (int)($_GET['campaign_id']??0);
        $ag_filter   = (int)($_GET['adgroup_id']??0);
        $where = ''; if($camp_filter) $where.=" AND campaign.id=$camp_filter"; if($ag_filter) $where.=" AND ad_group.id=$ag_filter";
        $camp_status = $only_active ? "AND campaign.status = 'ENABLED'" : "AND campaign.status IN ('ENABLED','PAUSED')";
        $q = "SELECT ad_group_ad.ad.id,ad_group_ad.ad.type,
                ad_group_ad.ad.responsive_search_ad.headlines,
                ad_group_ad.ad.responsive_search_ad.descriptions,
                ad_group_ad.ad.final_urls,ad_group_ad.status,
                ad_group_ad.policy_summary.approval_status,
                ad_group_ad.ad_strength,
                ad_group.id,ad_group.name,campaign.id,campaign.name,
                metrics.cost_micros,metrics.impressions,metrics.clicks,metrics.ctr,
                metrics.average_cpc,metrics.conversions
              FROM ad_group_ad
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                AND ad_group_ad.status IN ('ENABLED','PAUSED')
                {$camp_status} {$where}
              ORDER BY metrics.cost_micros DESC LIMIT 100";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $list=[];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[];$aga=$r['adGroupAd']??[];$ad=$aga['ad']??[];$rsa=$ad['responsiveSearchAd']??[];
            $heads=array_values(array_filter(array_map(fn($h)=>$h['text']??'',$rsa['headlines']??[])));
            $descs=array_values(array_filter(array_map(fn($d)=>$d['text']??'',$rsa['descriptions']??[])));
            $sp=round(($m['costMicros']??0)/1e6,2);
            $list[]=['id'=>$ad['id']??'','type'=>$ad['type']??'','status'=>strtolower($aga['status']??''),
                'approval'=>$aga['policySummary']['approvalStatus']??'',
                'ad_strength'=>$aga['adStrength']??'',
                'headlines'=>$heads,'descriptions'=>$descs,'final_url'=>$ad['finalUrls'][0]??'',
                'adgroup_id'=>$r['adGroup']['id']??'','adgroup'=>$r['adGroup']['name']??'',
                'campaign_id'=>$r['campaign']['id']??'','campaign'=>$r['campaign']['name']??'',
                'spend'=>$sp,'impressions'=>(int)($m['impressions']??0),'clicks'=>(int)($m['clicks']??0),
                'ctr'=>round((float)($m['ctr']??0)*100,2),'cpc'=>round(($m['averageCpc']??0)/1e6,2),
                'conversions'=>round((float)($m['conversions']??0),1)];
        }
        echo json_encode(['ok'=>true,'ads'=>$list,'period'=>$period]);
        break;

    // ── KEYWORDS ─────────────────────────────────────────────────────────────────
    case 'keywords':
        $camp_filter = (int)($_GET['campaign_id']??0);
        $where = $camp_filter ? "AND campaign.id = $camp_filter" : '';
        $camp_status = $only_active ? "AND campaign.status = 'ENABLED'" : "AND campaign.status IN ('ENABLED','PAUSED')";
        $q = "SELECT ad_group_criterion.criterion_id,ad_group_criterion.keyword.text,
                ad_group_criterion.keyword.match_type,ad_group_criterion.status,
                ad_group_criterion.quality_info.quality_score,
                ad_group_criterion.quality_info.creative_quality_score,
                ad_group_criterion.quality_info.post_click_quality_score,
                ad_group_criterion.quality_info.search_predicted_ctr,
                ad_group_criterion.cpc_bid_micros,
                ad_group.id,ad_group.name,campaign.id,campaign.name,
                metrics.cost_micros,metrics.impressions,metrics.clicks,metrics.ctr,
                metrics.average_cpc,metrics.conversions,metrics.conversions_value,
                metrics.search_impression_share,
                metrics.search_top_impression_share,
                metrics.search_absolute_top_impression_share
              FROM keyword_view
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                {$camp_status}
                AND ad_group.status IN ('ENABLED','PAUSED')
                AND ad_group_criterion.status IN ('ENABLED','PAUSED') {$where}
              ORDER BY metrics.impressions DESC LIMIT 200";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $list=[];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[];$agc=$r['adGroupCriterion']??[];$kw=$agc['keyword']??[];$qi=$agc['qualityInfo']??[];
            $sp=round(($m['costMicros']??0)/1e6,2); $cv=round((float)($m['conversions']??0),1);
            $is_raw=$m['searchImpressionShare']??null;
            $top_raw=$m['searchTopImpressionShare']??null;
            $atop_raw=$m['searchAbsoluteTopImpressionShare']??null;
            $list[]=['id'=>$agc['criterionId']??'','keyword'=>$kw['text']??'',
                'match_type'=>strtolower($kw['matchType']??''),'status'=>strtolower($agc['status']??''),
                'qs'=>(int)($qi['qualityScore']??0),'qs_ad'=>$qi['creativeQualityScore']??'',
                'qs_landing'=>$qi['postClickQualityScore']??'','qs_ctr'=>$qi['searchPredictedCtr']??'',
                'bid'=>round(($agc['cpcBidMicros']??0)/1e6,2),
                'adgroup_id'=>$r['adGroup']['id']??'','adgroup'=>$r['adGroup']['name']??'',
                'campaign_id'=>$r['campaign']['id']??'','campaign'=>$r['campaign']['name']??'',
                'spend'=>$sp,'impressions'=>(int)($m['impressions']??0),'clicks'=>(int)($m['clicks']??0),
                'ctr'=>round((float)($m['ctr']??0)*100,2),'cpc'=>round(($m['averageCpc']??0)/1e6,2),
                'conversions'=>$cv,'conv_value'=>round((float)($m['conversionsValue']??0),2),
                'roas'=>$sp>0?round((float)($m['conversionsValue']??0)/$sp,2):0,
                'imp_share'   =>$is_raw!==null?round((float)$is_raw*100,1):null,
                'top_share'   =>$top_raw!==null?round((float)$top_raw*100,1):null,
                'abs_top'     =>$atop_raw!==null?round((float)$atop_raw*100,1):null,
            ];
        }
        echo json_encode(['ok'=>true,'keywords'=>$list,'period'=>$period]);
        break;

    // ── TÉRMINOS DE BÚSQUEDA ─────────────────────────────────────────────────────
    case 'search_terms':
        $q = "SELECT search_term_view.search_term,search_term_view.status,
                campaign.id,campaign.name,ad_group.name,
                metrics.impressions,metrics.clicks,metrics.cost_micros,
                metrics.conversions,metrics.ctr
              FROM search_term_view
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                AND campaign.status='ENABLED'
              ORDER BY metrics.impressions DESC";
        $res = gads_all($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $list=[];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[];$stv=$r['searchTermView']??[];
            $list[]=['term'=>$stv['searchTerm']??'','status'=>$stv['status']??'',
                'campaign_id'=>$r['campaign']['id']??'','campaign'=>$r['campaign']['name']??'',
                'adgroup'=>$r['adGroup']['name']??'',
                'impressions'=>(int)($m['impressions']??0),'clicks'=>(int)($m['clicks']??0),
                'ctr'=>round((float)($m['ctr']??0)*100,2),'spend'=>round(($m['costMicros']??0)/1e6,2),
                'conversions'=>round((float)($m['conversions']??0),1)];
        }
        echo json_encode(['ok'=>true,'search_terms'=>$list,'total'=>count($list),'date_from'=>$df,'date_to'=>$dt]);
        break;

    // ── COMPETENCIA: IMPRESSION SHARE POR CAMPAÑA ───────────────────────────────
    case 'auction':
        $camp_filter = (int)($_GET['campaign_id']??0);
        $where = $camp_filter ? "AND campaign.id = $camp_filter" : '';
        $q = "SELECT campaign.id,campaign.name,campaign.status,
                metrics.search_impression_share,
                metrics.search_rank_lost_impression_share,
                metrics.search_budget_lost_impression_share,
                metrics.search_absolute_top_impression_share,
                metrics.search_top_impression_share,
                metrics.cost_micros,metrics.clicks,metrics.impressions
              FROM campaign
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                AND campaign.status IN ('ENABLED','PAUSED') {$where}
              ORDER BY metrics.cost_micros DESC LIMIT 50";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $agg=[];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[]; $c=$r['campaign']??[];
            $cid=$c['id']??''; if(!$cid) continue;
            if(!isset($agg[$cid])) $agg[$cid]=['id'=>$cid,'name'=>$c['name']??'','status'=>strtolower($c['status']??''),
                'is'=>0,'lost_rank'=>0,'lost_budget'=>0,'abs_top'=>0,'top_is'=>0,'spend'=>0,'clicks'=>0,'impressions'=>0,'n'=>0];
            $agg[$cid]['is']+=(float)($m['searchImpressionShare']??0);
            $agg[$cid]['lost_rank']+=(float)($m['searchRankLostImpressionShare']??0);
            $agg[$cid]['lost_budget']+=(float)($m['searchBudgetLostImpressionShare']??0);
            $agg[$cid]['abs_top']+=(float)($m['searchAbsoluteTopImpressionShare']??0);
            $agg[$cid]['top_is']+=(float)($m['searchTopImpressionShare']??0);
            $agg[$cid]['spend']+=($m['costMicros']??0)/1e6;
            $agg[$cid]['clicks']+=(int)($m['clicks']??0);
            $agg[$cid]['impressions']+=(int)($m['impressions']??0);
            $agg[$cid]['n']++;
        }
        $result=[];
        foreach ($agg as $row) {
            $n=max(1,$row['n']);
            $result[]=['id'=>$row['id'],'name'=>$row['name'],'status'=>$row['status'],
                'is'=>round($row['is']/$n*100,1),
                'lost_rank'=>round($row['lost_rank']/$n*100,1),
                'lost_budget'=>round($row['lost_budget']/$n*100,1),
                'abs_top'=>round($row['abs_top']/$n*100,1),
                'top_is'=>round($row['top_is']/$n*100,1),
                'spend'=>round($row['spend'],2),
                'clicks'=>$row['clicks'],'impressions'=>$row['impressions']];
        }
        usort($result,fn($a,$b)=>$b['spend']<=>$a['spend']);
        echo json_encode(['ok'=>true,'campaigns'=>$result,'period'=>$period]);
        break;

    // ── FACTURACIÓN / ALERTAS DE PAGO ───────────────────────────────────────────
    case 'billing':
        $q = "SELECT account_budget.status,account_budget.amount_served_micros,
                account_budget.approved_spending_limit_micros,
                account_budget.purchase_order_number
              FROM account_budget LIMIT 10";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        $budgets=[];$alerts=[];
        foreach ($res['results']??[] as $r) {
            $b=$r['accountBudget']??[];
            $status=$b['status']??'';
            $served=round(($b['amountServedMicros']??0)/1e6,2);
            $limit=round(($b['approvedSpendingLimitMicros']??0)/1e6,2);
            $budgets[]=['status'=>$status,'served'=>$served,'limit'=>$limit,'po'=>$b['purchaseOrderNumber']??''];
            if(in_array($status,['CANCELLED','PENDING','UNDER_REVIEW'])) $alerts[]="Budget con estado: $status";
            if($limit>0 && $served>0 && ($served/$limit)>0.9) $alerts[]="Budget al ".round($served/$limit*100)."% de su límite";
        }
        // Check campaigns with payment issues
        $q2 = "SELECT campaign.id,campaign.name,campaign.status,campaign.serving_status FROM campaign WHERE campaign.status='ENABLED' LIMIT 50";
        $res2 = gads($customer_id,$q2,$dev_token,$access_token,$mcc_id);
        $paused_billing=[];
        foreach ($res2['results']??[] as $r) {
            $c=$r['campaign']??[];
            if(($c['servingStatus']??'')!=='SERVING' && ($c['status']??'')==='ENABLED') {
                $paused_billing[]=$c['name']??'';
            }
        }
        if(!empty($paused_billing)) $alerts[]="Campañas activas sin servir: ".implode(', ',$paused_billing);
        echo json_encode(['ok'=>true,'budgets'=>$budgets,'alerts'=>$alerts,'has_issue'=>!empty($alerts)]);
        break;

    // ── SEGMENTOS: DISPOSITIVOS / UBICACIONES / EDADES ───────────────────────────
    case 'segments':
        $seg_type = $_GET['seg_type'] ?? 'device';
        $status_filter = $only_active ? "AND campaign.status='ENABLED'" : "AND campaign.status IN ('ENABLED','PAUSED')";
        $result = [];

        if ($seg_type === 'device') {
            $q = "SELECT segments.device,
                    metrics.impressions,metrics.clicks,metrics.cost_micros,metrics.ctr,
                    metrics.average_cpc,metrics.conversions,metrics.conversions_value,metrics.cost_per_conversion
                  FROM campaign
                  WHERE segments.date BETWEEN '{$df}' AND '{$dt}' {$status_filter}";
            $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
            if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
            $agg=[];
            foreach ($res['results']??[] as $r) {
                $dev=strtolower($r['segments']['device']??'unknown');
                $m=$r['metrics']??[];
                if(!isset($agg[$dev])) $agg[$dev]=['device'=>$dev,'impressions'=>0,'clicks'=>0,'spend'=>0,'conversions'=>0,'conv_value'=>0];
                $agg[$dev]['impressions']+=(int)($m['impressions']??0);
                $agg[$dev]['clicks']+=(int)($m['clicks']??0);
                $agg[$dev]['spend']+=($m['costMicros']??0)/1e6;
                $agg[$dev]['conversions']+=(float)($m['conversions']??0);
                $agg[$dev]['conv_value']+=(float)($m['conversionsValue']??0);
            }
            foreach($agg as &$d) {
                $d['spend']=round($d['spend'],2); $d['conversions']=round($d['conversions'],1); $d['conv_value']=round($d['conv_value'],2);
                $d['ctr']=$d['impressions']>0?round($d['clicks']/$d['impressions']*100,2):0;
                $d['cpc']=$d['clicks']>0?round($d['spend']/$d['clicks'],2):0;
                $d['cpa']=$d['conversions']>0?round($d['spend']/$d['conversions'],2):0;
                $d['roas']=$d['spend']>0?round($d['conv_value']/$d['spend'],2):0;
            }
            usort($agg,fn($a,$b)=>$b['impressions']<=>$a['impressions']);
            $result=array_values($agg);
        }

        elseif ($seg_type === 'location') {
            // geographic_view requiere campaign.status en SELECT cuando se filtra por él
            $loc_status = $only_active ? "AND campaign.status = 'ENABLED'" : "AND campaign.status IN ('ENABLED','PAUSED')";
            $q = "SELECT campaign.status,geographic_view.location_type,
                    geographic_view.country_criterion_id,
                    metrics.impressions,metrics.clicks,metrics.cost_micros,
                    metrics.conversions,metrics.conversions_value
                  FROM geographic_view
                  WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                    {$loc_status}
                  ORDER BY metrics.impressions DESC LIMIT 100";
            $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
            if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
            // Mapa de IDs de criterio de país a nombre
            $country_map=['2170'=>'Colombia','2840'=>'Estados Unidos','2484'=>'México','2076'=>'Brasil',
                '2032'=>'Argentina','2152'=>'Chile','2604'=>'Perú','2218'=>'Ecuador','2862'=>'Venezuela',
                '2724'=>'España','2858'=>'Uruguay','2591'=>'Panamá','2188'=>'Costa Rica','2320'=>'Guatemala'];
            $agg=[];
            foreach ($res['results']??[] as $r) {
                $gv=$r['geographicView']??[]; $m=$r['metrics']??[];
                $cid_geo=$gv['countryCriterionId']??'';
                $loc_name=$country_map[$cid_geo]??'País '.$cid_geo;
                $type=strtolower($gv['locationType']??''); if($type==='location_of_presence') $type='presencia'; else if($type==='area_of_interest') $type='interés';
                $key=$cid_geo.'_'.$type;
                if(!isset($agg[$key])) $agg[$key]=['location'=>$loc_name.' ('.$type.')','impressions'=>0,'clicks'=>0,'spend'=>0,'conversions'=>0,'conv_value'=>0];
                $agg[$key]['impressions']+=(int)($m['impressions']??0);
                $agg[$key]['clicks']+=(int)($m['clicks']??0);
                $agg[$key]['spend']+=($m['costMicros']??0)/1e6;
                $agg[$key]['conversions']+=(float)($m['conversions']??0);
                $agg[$key]['conv_value']+=(float)($m['conversionsValue']??0);
            }
            foreach($agg as &$d) {
                $d['spend']=round($d['spend'],2); $d['conversions']=round($d['conversions'],1); $d['conv_value']=round($d['conv_value'],2);
                $d['ctr']=$d['impressions']>0?round($d['clicks']/$d['impressions']*100,2):0;
                $d['cpc']=$d['clicks']>0?round($d['spend']/$d['clicks'],2):0;
                $d['cpa']=$d['conversions']>0?round($d['spend']/$d['conversions'],2):0;
                $d['roas']=$d['spend']>0?round($d['conv_value']/$d['spend'],2):0;
            }
            usort($agg,fn($a,$b)=>$b['impressions']<=>$a['impressions']);
            $result=array_values($agg);
        }

        elseif ($seg_type === 'age') {
            $q = "SELECT ad_group_criterion.age_range.type,
                    metrics.impressions,metrics.clicks,metrics.cost_micros,
                    metrics.conversions,metrics.conversions_value,metrics.cost_per_conversion
                  FROM age_range_view
                  WHERE segments.date BETWEEN '{$df}' AND '{$dt}' {$status_filter}";
            $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
            if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
            $labels=['AGE_RANGE_18_24'=>'18-24','AGE_RANGE_25_34'=>'25-34','AGE_RANGE_35_44'=>'35-44',
                'AGE_RANGE_45_54'=>'45-54','AGE_RANGE_55_64'=>'55-64','AGE_RANGE_65_UP'=>'65+','AGE_RANGE_UNDETERMINED'=>'Sin determinar'];
            $agg=[];
            foreach ($res['results']??[] as $r) {
                $agc=$r['adGroupCriterion']??[]; $ar=$agc['ageRange']??[]; $m=$r['metrics']??[];
                $key=$ar['type']??'AGE_RANGE_UNDETERMINED';
                $label=$labels[$key]??$key;
                if(!isset($agg[$key])) $agg[$key]=['age'=>$label,'impressions'=>0,'clicks'=>0,'spend'=>0,'conversions'=>0,'conv_value'=>0];
                $agg[$key]['impressions']+=(int)($m['impressions']??0);
                $agg[$key]['clicks']+=(int)($m['clicks']??0);
                $agg[$key]['spend']+=($m['costMicros']??0)/1e6;
                $agg[$key]['conversions']+=(float)($m['conversions']??0);
                $agg[$key]['conv_value']+=(float)($m['conversionsValue']??0);
            }
            foreach($agg as &$d) {
                $d['spend']=round($d['spend'],2); $d['conversions']=round($d['conversions'],1); $d['conv_value']=round($d['conv_value'],2);
                $d['ctr']=$d['impressions']>0?round($d['clicks']/$d['impressions']*100,2):0;
                $d['cpc']=$d['clicks']>0?round($d['spend']/$d['clicks'],2):0;
                $d['cpa']=$d['conversions']>0?round($d['spend']/$d['conversions'],2):0;
                $d['roas']=$d['spend']>0?round($d['conv_value']/$d['spend'],2):0;
            }
            $order=['AGE_RANGE_18_24','AGE_RANGE_25_34','AGE_RANGE_35_44','AGE_RANGE_45_54','AGE_RANGE_55_64','AGE_RANGE_65_UP','AGE_RANGE_UNDETERMINED'];
            $sorted=[];
            foreach($order as $k) if(isset($agg[$k])) $sorted[]=$agg[$k];
            $result=$sorted;
        }

        echo json_encode(['ok'=>true,'data'=>$result,'seg_type'=>$seg_type,'date_from'=>$df,'date_to'=>$dt]);
        break;

    // ── CONTEXTO DE NEGATIVOS: LEER ──────────────────────────────────────────────
    case 'get_neg_context':
        $raw_ctx = dm_get_credential($client_id, 'google', 'neg_context');
        $ctx = $raw_ctx ? json_decode($raw_ctx, true) : [];
        echo json_encode(['ok'=>true,
            'permanent' => $ctx['permanent'] ?? '',
            'daily'     => $ctx['daily']     ?? '',
            'updated_at'=> $ctx['updated_at'] ?? ''
        ]);
        break;

    // ── CONTEXTO DE NEGATIVOS: GUARDAR ───────────────────────────────────────────
    case 'save_neg_context':
        dm_csrf_verify();
        $input     = json_decode(file_get_contents('php://input'), true) ?? [];
        $permanent = trim($input['permanent'] ?? '');
        $daily     = trim($input['daily']     ?? '');
        $ctx = json_encode(['permanent'=>$permanent,'daily'=>$daily,'updated_at'=>date('Y-m-d H:i')]);
        dm_set_credential($client_id, 'google', 'neg_context', $ctx);
        echo json_encode(['ok'=>true]);
        break;

    // ── NEGATIVOS IA ─────────────────────────────────────────────────────────────
    case 'ai_negatives':
        // Cache 2h — evita llamadas repetidas por el mismo período
        $cache_key_neg = "neg_ai_{$client_id}_{$df}_{$dt}";
        if ($cached_neg = dm_cache_get($cache_key_neg)) {
            echo json_encode($cached_neg); exit;
        }
        // Cargar contexto del cliente
        $raw_ctx  = dm_get_credential($client_id, 'google', 'neg_context');
        $neg_ctx  = $raw_ctx ? json_decode($raw_ctx, true) : [];
        $permanent_neg = array_filter(array_map('trim', explode("\n", $neg_ctx['permanent'] ?? '')));
        $daily_filters = array_filter(array_map('trim', explode("\n", $neg_ctx['daily']     ?? '')));

        // Traer términos de búsqueda sin conversión
        $q = "SELECT search_term_view.search_term,metrics.impressions,metrics.clicks,
                metrics.cost_micros,metrics.conversions,campaign.name
              FROM search_term_view
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                AND campaign.status='ENABLED'
                AND metrics.impressions > 5
              ORDER BY metrics.cost_micros DESC LIMIT 200";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }

        $terms=[];
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[];$stv=$r['searchTermView']??[];
            $sp=round(($m['costMicros']??0)/1e6,2); $cv=(float)($m['conversions']??0);
            $imp=(int)($m['impressions']??0); $term=strtolower(trim($stv['searchTerm']??''));
            if($cv==0 && ($sp>0||$imp>20) && $term) {
                $terms[]=['term'=>$term,'impressions'=>$imp,'clicks'=>(int)($m['clicks']??0),'spend'=>$sp,'conversions'=>0,'campaign'=>$r['campaign']['name']??''];
            }
        }

        if(empty($terms)) { echo json_encode(['ok'=>true,'auto'=>[],'review'=>[],'terms_analyzed'=>0,'message'=>'No hay términos sin conversión para analizar']); exit; }

        // Filtrar términos que ya están en la lista de permanentes (ya son negativos conocidos)
        $perm_lower = array_map('strtolower', $permanent_neg);
        $terms = array_filter($terms, function($t) use ($perm_lower) {
            foreach($perm_lower as $p) { if($p && strpos($t['term'],$p)!==false) return false; }
            return true;
        });

        $str = implode("\n", array_map(fn($t)=>$t['term'].'|'.$t['impressions'].'imp|'.$t['clicks'].'clics|$'.$t['spend'].'|'.$t['campaign'], array_slice(array_values($terms),0,60)));

        $perm_str   = !empty($permanent_neg) ? implode(', ',$permanent_neg) : 'ninguna definida aún';
        $daily_str  = !empty($daily_filters) ? implode(', ',$daily_filters) : 'ninguna definida aún';

        $ck = defined('CLAUDE_API_KEY') ? CLAUDE_API_KEY : '';
        $prompt = "Eres el experto en Google Ads del equipo SuperCerebro. Analiza estos términos de búsqueda con CERO conversiones.\n\n".
            "CONTEXTO DE LA CUENTA:\n".
            "- Palabras siempre negativas (permanentes): $perm_str\n".
            "- Filtros diarios aplicados: $daily_str\n\n".
            "TÉRMINOS A ANALIZAR (formato: término|impresiones|clics|gasto|campaña):\n$str\n\n".
            "INSTRUCCIONES:\n".
            "Clasifica CADA término en uno de dos grupos:\n".
            "1. AUTO: Definitivamente negativo. Intención incorrecta, irrelevante, competidor, informacional puro. Sin duda.\n".
            "2. REVIEW: Dudoso. Puede ser relevante en algunos contextos, tiene potencial o necesita criterio humano.\n\n".
            "NO incluyas términos que ya están en la lista de permanentes ni en filtros diarios.\n".
            "Responde SOLO con JSON válido (sin markdown, sin texto extra):\n".
            "{\"auto\":[{\"keyword\":\"term\",\"reason\":\"motivo corto\",\"match_type\":\"exact|phrase|broad\"}],".
            "\"review\":[{\"keyword\":\"term\",\"reason\":\"por qué genera duda\",\"match_type\":\"exact|phrase|broad\"}]}\n\n".
            "Máximo 20 en auto, 15 en review. Solo los más claros y relevantes.";

        $ch=curl_init('https://api.anthropic.com/v1/messages');
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>60,
            CURLOPT_HTTPHEADER=>['Content-Type: application/json','x-api-key: '.$ck,'anthropic-version: 2023-06-01'],
            CURLOPT_POSTFIELDS=>json_encode(['model'=>'claude-haiku-4-5-20251001','max_tokens'=>3000,'messages'=>[['role'=>'user','content'=>$prompt]]])]);
        $raw=curl_exec($ch); curl_close($ch);
        $resp=json_decode($raw,true);
        $text=$resp['content'][0]['text']??'{}';
        preg_match('/\{.*\}/s',$text,$m2);
        $result=json_decode($m2[0]??'{}',true)??[];
        $neg_response = ['ok'=>true,'auto'=>$result['auto']??[],'review'=>$result['review']??[],'terms_analyzed'=>count($terms)];
        dm_cache_set($cache_key_neg, $neg_response, 120); // 2h
        echo json_encode($neg_response);
        break;

    // ── REVISIÓN DIARIA ──────────────────────────────────────────────────────────
    case 'daily_review':
        $yesterday = date('Y-m-d', strtotime('-1 day'));
        // Cache 24h — un análisis por cliente por día
        $cache_key_dr = "daily_review_{$client_id}_{$yesterday}";
        if ($cached_dr = dm_cache_get($cache_key_dr)) {
            echo json_encode($cached_dr); exit;
        }

        $raw_ctx      = dm_get_credential($client_id, 'google', 'neg_context');
        $neg_ctx      = $raw_ctx ? json_decode($raw_ctx, true) : [];
        $permanent_neg = array_filter(array_map('trim', explode("\n", $neg_ctx['permanent'] ?? '')));

        // Nombres de campañas activas para contexto
        $camp_r = gads($customer_id, "SELECT campaign.name FROM campaign WHERE campaign.status='ENABLED' LIMIT 10", $dev_token, $access_token, $mcc_id);
        $camp_names = array_filter(array_map(fn($r) => $r['campaign']['name'] ?? '', $camp_r['results'] ?? []));
        $camp_context = $camp_names ? implode(', ', $camp_names) : 'No disponible';

        // Términos de ayer
        $q = "SELECT search_term_view.search_term, metrics.impressions, metrics.clicks,
                metrics.cost_micros, metrics.conversions, campaign.name
              FROM search_term_view
              WHERE segments.date BETWEEN '{$yesterday}' AND '{$yesterday}'
                AND campaign.status='ENABLED'
              ORDER BY metrics.cost_micros DESC LIMIT 300";
        $res = gads($customer_id, $q, $dev_token, $access_token, $mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }

        $terms = [];
        foreach ($res['results'] ?? [] as $r) {
            $m = $r['metrics'] ?? []; $stv = $r['searchTermView'] ?? [];
            $term = strtolower(trim($stv['searchTerm'] ?? ''));
            if (!$term) continue;
            $terms[] = [
                'term'        => $term,
                'impressions' => (int)($m['impressions'] ?? 0),
                'clicks'      => (int)($m['clicks'] ?? 0),
                'spend'       => round(($m['costMicros'] ?? 0) / 1e6, 2),
                'conversions' => (float)($m['conversions'] ?? 0),
                'campaign'    => $r['campaign']['name'] ?? '',
            ];
        }

        if (empty($terms)) {
            echo json_encode(['ok'=>true,'date'=>$yesterday,'total_terms'=>0,'summary'=>'Sin términos de búsqueda registrados para ayer.','negativas'=>[],'conservar'=>[]]);
            exit;
        }

        // Filtrar permanentes ya conocidos
        $perm_lower = array_map('strtolower', $permanent_neg);
        $terms = array_values(array_filter($terms, function($t) use ($perm_lower) {
            foreach ($perm_lower as $p) { if ($p && strpos($t['term'], $p) !== false) return false; }
            return true;
        }));

        if (empty($terms)) {
            echo json_encode(['ok'=>true,'date'=>$yesterday,'total_terms'=>0,'summary'=>'Todos los términos de ayer ya están en la lista de negativos permanentes.','negativas'=>[],'conservar'=>[]]);
            exit;
        }

        $total_terms = count($terms);
        $perm_str = !empty($permanent_neg) ? implode(', ', $permanent_neg) : 'ninguna definida';

        $str = implode("\n", array_map(
            fn($t) => $t['term'].'|'.$t['impressions'].'imp|'.$t['clicks'].'clics|$'.$t['spend'].'|'.($t['conversions']>0?'✓conv':'sin conv').'|'.$t['campaign'],
            array_slice($terms, 0, 60)
        ));

        $ck = defined('CLAUDE_API_KEY') ? CLAUDE_API_KEY : '';
        $prompt = "Eres el experto en Google Ads de SuperCerebro. Analiza los términos de búsqueda de AYER ({$yesterday}) para este cliente.\n\n".
            "CAMPAÑAS ACTIVAS: {$camp_context}\n".
            "PALABRAS SIEMPRE NEGATIVAS (ignorar en clasificación): {$perm_str}\n\n".
            "TÉRMINOS DE AYER (formato: término|impresiones|clics|gasto|conversiones|campaña):\n{$str}\n\n".
            "TAREA: Analiza el rendimiento de ayer y clasifica cada término.\n".
            "Responde SOLO con este JSON (sin markdown, sin texto extra):\n".
            "{\"summary\":\"Análisis experto de 2-3 frases: patrones detectados, oportunidades y riesgos del día.\",".
            "\"negativas\":[{\"keyword\":\"term\",\"match_type\":\"exact|phrase|broad\",\"reason\":\"motivo corto\"}],".
            "\"conservar\":[{\"keyword\":\"term\",\"reason\":\"por qué conservar\"}]}\n\n".
            "REGLAS:\n".
            "- negativas: intención incorrecta, irrelevante, informacional puro, sin conv + gasto alto\n".
            "- conservar: tiene conversiones, relevante o con potencial real\n".
            "- Máximo 20 negativas y 10 conservar, solo los más claros\n".
            "- NO incluir términos que ya están en negativos permanentes";

        $ch = curl_init('https://api.anthropic.com/v1/messages');
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>60,
            CURLOPT_HTTPHEADER=>['Content-Type: application/json','x-api-key: '.$ck,'anthropic-version: 2023-06-01'],
            CURLOPT_POSTFIELDS=>json_encode(['model'=>'claude-haiku-4-5-20251001','max_tokens'=>3000,'messages'=>[['role'=>'user','content'=>$prompt]]])]);
        $raw = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
        $resp = json_decode($raw, true);

        if ($http_code !== 200 || isset($resp['error'])) {
            echo json_encode(['ok'=>false,'error'=>'Error IA: '.($resp['error']['message']??$resp['message']??"HTTP $http_code")]); exit;
        }

        $text = $resp['content'][0]['text'] ?? '{}';
        preg_match('/\{.*\}/s', $text, $m2);
        $result = json_decode($m2[0] ?? '{}', true) ?? [];

        $dr_response = [
            'ok'          => true,
            'date'        => $yesterday,
            'total_terms' => $total_terms,
            'summary'     => $result['summary'] ?? 'Análisis completado.',
            'negativas'   => $result['negativas'] ?? [],
            'conservar'   => $result['conservar'] ?? [],
        ];
        dm_cache_set($cache_key_dr, $dr_response, 1440); // 24h
        echo json_encode($dr_response);
        break;

    // ── CONFIANZA / MODO AUTOMÁTICO: LEER ───────────────────────────────────────
    case 'get_trust':
        $raw_ctx = dm_get_credential($client_id,'google','neg_context');
        $ctx     = $raw_ctx ? json_decode($raw_ctx,true) : [];
        $trust   = $ctx['trust'] ?? ['approved'=>0,'auto_mode'=>false,'auto_since'=>null,'last_auto_run'=>null,'last_summary'=>null];
        $threshold = 30;
        $approved  = (int)($trust['approved'] ?? 0);
        $progress  = min(100, round($approved / $threshold * 100));
        echo json_encode(['ok'=>true,'trust'=>$trust,'threshold'=>$threshold,'progress'=>$progress,
            'auto_mode'=>(bool)($trust['auto_mode']??false),'approved'=>$approved]);
        break;

    // ── CONFIANZA / MODO AUTOMÁTICO: CAMBIAR ────────────────────────────────────
    case 'set_trust_mode':
        dm_csrf_verify();
        dm_require_admin();
        $input = json_decode(file_get_contents('php://input'),true)??[];
        $mode  = (bool)($input['auto_mode']??false);
        $raw_ctx = dm_get_credential($client_id,'google','neg_context');
        $ctx     = $raw_ctx ? json_decode($raw_ctx,true) : [];
        if (!isset($ctx['trust'])) $ctx['trust'] = ['approved'=>0];
        $ctx['trust']['auto_mode']  = $mode;
        $ctx['trust']['auto_since'] = $mode ? date('Y-m-d') : null;
        dm_set_credential($client_id,'google','neg_context',json_encode($ctx));
        echo json_encode(['ok'=>true,'auto_mode'=>$mode]);
        break;

    // ── EJECUCIÓN AUTOMÁTICA (llamado por cron) ──────────────────────────────────
    case 'auto_run':
        // Acepta solo CLI o token de cron
        $cron_token = defined('CRON_SECRET') ? CRON_SECRET : '';
        $is_cli     = php_sapi_name() === 'cli';
        $req_token  = $_GET['cron_token'] ?? ($_SERVER['HTTP_X_CRON_TOKEN'] ?? '');
        if (!$is_cli && (!$cron_token || $req_token !== $cron_token)) {
            http_response_code(403); echo json_encode(['ok'=>false,'error'=>'Forbidden']); exit;
        }

        $raw_ctx = dm_get_credential($client_id,'google','neg_context');
        $ctx     = $raw_ctx ? json_decode($raw_ctx,true) : [];
        if (!($ctx['trust']['auto_mode']??false)) { echo json_encode(['ok'=>false,'error'=>'Auto mode no activo']); exit; }

        $permanent_neg = array_filter(array_map('trim', explode("\n", $ctx['permanent']??'')));
        $run_df = date('Y-m-d', strtotime('-7 days'));
        $run_dt = date('Y-m-d');

        $q = "SELECT search_term_view.search_term,metrics.impressions,metrics.clicks,
                metrics.cost_micros,metrics.conversions,campaign.name
              FROM search_term_view
              WHERE segments.date BETWEEN '{$run_df}' AND '{$run_dt}'
                AND campaign.status='ENABLED' AND metrics.impressions > 5
              ORDER BY metrics.cost_micros DESC LIMIT 200";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }

        $terms=[];
        $perm_lower = array_map('strtolower',$permanent_neg);
        foreach ($res['results']??[] as $r) {
            $m=$r['metrics']??[];$stv=$r['searchTermView']??[];
            $sp=round(($m['costMicros']??0)/1e6,2); $cv=(float)($m['conversions']??0);
            $imp=(int)($m['impressions']??0); $term=strtolower(trim($stv['searchTerm']??''));
            if($cv==0 && ($sp>0||$imp>20) && $term) {
                $skip=false;
                foreach($perm_lower as $p){if($p&&strpos($term,$p)!==false){$skip=true;break;}}
                if(!$skip) $terms[]=['term'=>$term,'impressions'=>$imp,'clicks'=>(int)($m['clicks']??0),'spend'=>$sp,'campaign'=>$r['campaign']['name']??''];
            }
        }

        if (empty($terms)) {
            $ctx['trust']['last_auto_run']  = date('Y-m-d H:i');
            $ctx['trust']['last_summary']   = ['applied'=>0,'terms_analyzed'=>0,'date'=>date('Y-m-d H:i'),'keywords'=>[]];
            dm_set_credential($client_id,'google','neg_context',json_encode($ctx));
            echo json_encode(['ok'=>true,'applied'=>0,'terms_analyzed'=>0,'message'=>'Sin términos nuevos']);
            exit;
        }

        $str      = implode("\n", array_map(fn($t)=>$t['term'].'|'.$t['impressions'].'imp|'.$t['clicks'].'clics|$'.$t['spend'].'|'.$t['campaign'], array_slice($terms,0,120)));
        $perm_str = !empty($permanent_neg) ? implode(', ',$permanent_neg) : 'ninguna';
        $ck       = defined('CLAUDE_API_KEY') ? CLAUDE_API_KEY : '';

        $prompt = "Eres el experto en Google Ads del SuperCerebro. MODO AUTOMÁTICO — el sistema confía plenamente en tu criterio.\n\n".
            "Contexto cuenta — palabras siempre negativas: $perm_str\n\n".
            "Términos sin conversión (formato: término|imp|clics|gasto|campaña):\n$str\n\n".
            "Selecciona SOLO los que definitivamente no deben activar anuncios (intención incorrecta, informacional puro, competidor, empleo, etc.).\n".
            "Sé conservador: si hay duda razonable de que pueda convertir, NO lo incluyas.\n".
            "Responde SOLO JSON válido:\n".
            "{\"to_apply\":[{\"keyword\":\"term\",\"reason\":\"motivo\",\"match_type\":\"exact|phrase|broad\"}]}\n".
            "Máximo 25 términos.";

        $ch=curl_init('https://api.anthropic.com/v1/messages');
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>60,
            CURLOPT_HTTPHEADER=>['Content-Type: application/json','x-api-key: '.$ck,'anthropic-version: 2023-06-01'],
            CURLOPT_POSTFIELDS=>json_encode(['model'=>'claude-haiku-4-5-20251001','max_tokens'=>2000,'messages'=>[['role'=>'user','content'=>$prompt]]])]);
        $raw=curl_exec($ch); curl_close($ch);
        $resp=json_decode($raw,true);
        $text=$resp['content'][0]['text']??'{}';
        preg_match('/\{.*\}/s',$text,$m2);
        $ai_result = json_decode($m2[0]??'{}',true)??[];
        $to_apply  = $ai_result['to_apply']??[];

        $applied = 0;
        if (!empty($to_apply)) {
            $ops=[];
            foreach ($to_apply as $kw) {
                $t=trim($kw['keyword']??''); $mt=strtoupper($kw['match_type']??'BROAD');
                if(!in_array($mt,['EXACT','PHRASE','BROAD'])) $mt='BROAD';
                if($t) $ops[]=$t; // solo texto para dedup
            }
            $ops=array_unique($ops);
            if (!empty($ops)) {
                // Obtener campañas activas
                $cr=gads($customer_id,"SELECT campaign.resource_name FROM campaign WHERE campaign.status='ENABLED' LIMIT 100",$dev_token,$access_token,$mcc_id);
                $crns=array_filter(array_map(fn($r)=>$r['campaign']['resourceName']??'',$cr['results']??[]));
                $mutations=[];
                foreach($crns as $crn) foreach($ops as $txt) {
                    $mt_kw=strtoupper($to_apply[array_search($txt,array_column($to_apply,'keyword'))]['match_type']??'BROAD');
                    if(!in_array($mt_kw,['EXACT','PHRASE','BROAD'])) $mt_kw='BROAD';
                    $mutations[]=['create'=>['campaign'=>$crn,'negative'=>true,'type'=>'KEYWORD','keyword'=>['text'=>$txt,'matchType'=>$mt_kw]]];
                }
                $url2="https://googleads.googleapis.com/v22/customers/{$customer_id}/campaignCriteria:mutate";
                $h2=['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
                if($mcc_id&&$mcc_id!==$customer_id) $h2[]='login-customer-id: '.$mcc_id;
                $ch2=curl_init($url2);
                curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>30,
                    CURLOPT_HTTPHEADER=>$h2,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$mutations,'partialFailure'=>true])]);
                $raw2=curl_exec($ch2); curl_close($ch2);
                $res2=json_decode($raw2,true);
                $applied=count($ops);
                // Agregar al contexto permanente
                $existing=array_filter(array_map('trim',explode("\n",$ctx['permanent']??'')));
                $new_kws=array_filter(array_map(fn($k)=>trim($k['keyword']??''),$to_apply));
                $ctx['permanent']=implode("\n",array_unique(array_merge($existing,$new_kws)));
            }
        }

        $ctx['trust']['approved']       = ($ctx['trust']['approved']??0) + $applied;
        $ctx['trust']['last_auto_run']  = date('Y-m-d H:i');
        $ctx['trust']['last_summary']   = ['applied'=>$applied,'terms_analyzed'=>count($terms),'date'=>date('Y-m-d H:i'),'keywords'=>array_map(fn($k)=>$k['keyword'],$to_apply)];
        $ctx['updated_at'] = date('Y-m-d H:i');
        dm_set_credential($client_id,'google','neg_context',json_encode($ctx));
        dm_activity($client_id,'auto_negatives',"Cron: {$applied} negativos aplicados de ".count($terms)." términos analizados");
        echo json_encode(['ok'=>true,'applied'=>$applied,'terms_analyzed'=>count($terms),'keywords'=>array_map(fn($k)=>$k['keyword'],$to_apply)]);
        break;

    // ── NEGATIVOS ACTUALES EN CAMPAÑAS ──────────────────────────────────────────
    case 'get_account_negatives':
        // Negativos a nivel de campaña
        $q_camp = "SELECT campaign_criterion.criterion_id,campaign_criterion.keyword.text,
                campaign_criterion.keyword.match_type,campaign.id,campaign.name
              FROM campaign_criterion
              WHERE campaign_criterion.negative = TRUE
                AND campaign_criterion.type = KEYWORD
                AND campaign.status IN ('ENABLED','PAUSED')
              ORDER BY campaign_criterion.keyword.text ASC";
        $res_camp = gads_all($customer_id,$q_camp,$dev_token,$access_token,$mcc_id);
        if (isset($res_camp['_err'])) { echo json_encode(['ok'=>false,'error'=>$res_camp['_err']]); exit; }

        // Negativos a nivel de cuenta (shared negative keyword lists)
        $q_shared = "SELECT shared_criterion.keyword.text,shared_criterion.keyword.match_type,shared_set.name
                     FROM shared_criterion
                     WHERE shared_set.type = 'NEGATIVE_KEYWORDS'
                     ORDER BY shared_criterion.keyword.text ASC";
        $res_shared = gads_all($customer_id,$q_shared,$dev_token,$access_token,$mcc_id);

        $seen=[]; $list=[];
        foreach ($res_camp['results']??[] as $r) {
            $cc=$r['campaignCriterion']??[]; $kw=$cc['keyword']??[];
            $text=trim($kw['text']??''); if(!$text) continue;
            $key=strtolower($text).'|'.strtolower($kw['matchType']??'');
            if(isset($seen[$key])) continue; $seen[$key]=true;
            $list[]=['keyword'=>$text,'match_type'=>strtolower($kw['matchType']??'broad'),
                     'campaign'=>$r['campaign']['name']??'','source'=>'campaign'];
        }
        foreach ($res_shared['results']??[] as $r) {
            $sc=$r['sharedCriterion']??[]; $kw=$sc['keyword']??[];
            $text=trim($kw['text']??''); if(!$text) continue;
            $key=strtolower($text).'|'.strtolower($kw['matchType']??'');
            if(isset($seen[$key])) continue; $seen[$key]=true;
            $list[]=['keyword'=>$text,'match_type'=>strtolower($kw['matchType']??'broad'),
                     'campaign'=>'Lista: '.($r['sharedSet']['name']??'cuenta'),'source'=>'shared'];
        }
        usort($list, fn($a,$b)=>strcasecmp($a['keyword'],$b['keyword']));
        echo json_encode(['ok'=>true,'negatives'=>$list,'count'=>count($list)]);
        break;

    // ── APLICAR NEGATIVOS A CAMPAÑAS ACTIVAS ────────────────────────────────────
    case 'apply_negatives':
        dm_csrf_verify();
        $input       = json_decode(file_get_contents('php://input'), true) ?? [];
        $keywords    = $input['keywords']       ?? [];
        $add_context = (bool)($input['add_to_context'] ?? false);

        if (empty($keywords)) { echo json_encode(['ok'=>false,'error'=>'No hay palabras para aplicar']); exit; }

        // Obtener campañas activas
        $camp_res = gads($customer_id,"SELECT campaign.id,campaign.resource_name FROM campaign WHERE campaign.status='ENABLED' LIMIT 100",$dev_token,$access_token,$mcc_id);
        $camp_rns = array_map(fn($r)=>$r['campaign']['resourceName']??'', $camp_res['results']??[]);
        $camp_rns = array_filter($camp_rns);

        if (empty($camp_rns)) { echo json_encode(['ok'=>false,'error'=>'No hay campañas activas']); exit; }

        $operations = [];
        foreach ($camp_rns as $crn) {
            foreach ($keywords as $kw) {
                $text  = trim($kw['keyword'] ?? '');
                $match = strtoupper($kw['match_type'] ?? 'BROAD');
                if (!in_array($match,['EXACT','PHRASE','BROAD'])) $match='BROAD';
                if (!$text) continue;
                $operations[] = ['create'=>['campaign'=>$crn,'negative'=>true,'type'=>'KEYWORD','keyword'=>['text'=>$text,'matchType'=>$match]]];
            }
        }
        if (empty($operations)) { echo json_encode(['ok'=>false,'error'=>'Sin operaciones válidas']); exit; }

        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/campaignCriteria:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>30,
            CURLOPT_HTTPHEADER=>$h,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$operations,'partialFailure'=>true])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);

        if ($http!==200 && empty($res['results'])) {
            echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit;
        }

        if ($add_context) {
            $raw_ctx = dm_get_credential($client_id,'google','neg_context');
            $ctx = $raw_ctx ? json_decode($raw_ctx,true) : [];
            $existing = array_filter(array_map('trim', explode("\n", $ctx['permanent']??'')));
            $new_kws  = array_filter(array_map(fn($k)=>trim($k['keyword']??''), $keywords));
            $merged   = array_unique(array_merge($existing, $new_kws));
            $ctx['permanent']   = implode("\n", $merged);
            $ctx['updated_at']  = date('Y-m-d H:i');
            $ctx['trust']['approved'] = ($ctx['trust']['approved']??0) + count($keywords);
            dm_set_credential($client_id,'google','neg_context',json_encode($ctx));
        }

        $applied = count($keywords);
        $camps   = count($camp_rns);
        echo json_encode(['ok'=>true,'applied'=>$applied,'campaigns'=>$camps,
            'message'=>"{$applied} negativas aplicadas en {$camps} campañas activas"]);
        break;

    // ── PAUSAR CAMPAÑA ───────────────────────────────────────────────────────────
    case 'pause_campaign':
    // ── ACTIVAR CAMPAÑA ──────────────────────────────────────────────────────────
    case 'enable_campaign':
        dm_csrf_verify();
        dm_require_admin();
        require_once __DIR__ . '/../helpers/avi_token.php';
        $input   = json_decode(file_get_contents('php://input'), true) ?? [];
        $camp_id = preg_replace('/[^0-9]/', '', $input['campaign_id'] ?? '');
        if (!$camp_id) { echo json_encode(['ok'=>false,'error'=>'campaign_id requerido']); exit; }
        // FASE 8: validar token AVI si viene (backwards-compat: UI directa sin token sigue funcionando)
        // El action_type que AVI emite es 'pause_campaign' o 'activate_campaign', mientras que
        // google-ads.php usa 'enable_campaign' para activar. Mapear para que el token match.
        $_avi_action = $action === 'enable_campaign' ? 'activate_campaign' : 'pause_campaign';
        if (!dm_avi_check_request_token($input, $_avi_action, $camp_id)) {
            echo json_encode(['ok'=>false,'error'=>'Token AVI inválido o expirado.']); exit;
        }
        $new_status    = $action === 'pause_campaign' ? 'PAUSED' : 'ENABLED';
        $resource_name = "customers/{$customer_id}/campaigns/{$camp_id}";
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/campaigns:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,
            CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>['resourceName'=>$resource_name,'status'=>$new_status],'updateMask'=>'status']]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit; }
        dm_activity($client_id,'campaign_status',"Campaña {$camp_id}: {$new_status}");
        echo json_encode(['ok'=>true,'status'=>strtolower($new_status),'message'=>'Campaña '.($new_status==='PAUSED'?'pausada':'activada').' correctamente']);
        break;

    // ── CAMBIAR PRESUPUESTO ──────────────────────────────────────────────────────
    case 'update_budget':
        $input     = json_decode(file_get_contents('php://input'), true) ?? [];
        $budget_rn = $input['budget_resource_name'] ?? '';
        $camp_id   = preg_replace('/[^0-9]/', '', $input['campaign_id'] ?? '');
        $amount    = (float)($input['amount'] ?? 0);
        if ($amount <= 0) { echo json_encode(['ok'=>false,'error'=>'Monto inválido']); exit; }
        // Si no llega budget_resource_name, buscarlo por campaign_id
        if (!$budget_rn && $camp_id) {
            $res_b = gads($customer_id, "SELECT campaign_budget.resource_name FROM campaign WHERE campaign.id={$camp_id} LIMIT 1", $dev_token, $access_token, $mcc_id);
            $budget_rn = $res_b['results'][0]['campaignBudget']['resourceName'] ?? '';
        }
        if (!$budget_rn) { echo json_encode(['ok'=>false,'error'=>'No se encontró el presupuesto de la campaña']); exit; }
        $amount_micros = (int)round($amount * 1e6);
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/campaignBudgets:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,
            CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>['resourceName'=>$budget_rn,'amountMicros'=>$amount_micros],'updateMask'=>'amount_micros']]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API Google Ads','http'=>$http]); exit; }
        dm_activity($client_id,'campaign_budget',"Presupuesto actualizado: \${$amount}");
        echo json_encode(['ok'=>true,'amount'=>$amount,'message'=>'Presupuesto actualizado a $'.number_format($amount,0)]);
        break;

    // ── CAMBIAR PRESUPUESTO POR CAMPAIGN ID (AVI Agente) ─────────────────────────
    case 'change_budget':
        dm_csrf_verify();
        dm_require_admin();
        require_once __DIR__ . '/../helpers/avi_token.php';
        $input     = json_decode(file_get_contents('php://input'), true) ?? [];
        $camp_id   = preg_replace('/[^0-9]/', '', $input['campaign_id'] ?? '');
        $amount    = (float)($input['amount'] ?? 0);
        if (!$camp_id || $amount <= 0) { echo json_encode(['ok'=>false,'error'=>'campaign_id y amount requeridos']); exit; }
        // FASE 8: token AVI obligatorio si viene (UI directa sin token sigue OK)
        if (!dm_avi_check_request_token($input, 'change_budget', $camp_id)) {
            echo json_encode(['ok'=>false,'error'=>'Token AVI inválido o expirado.']); exit;
        }
        // FASE 8: validar rango razonable para evitar errores grotescos por respuesta IA mal formada
        if ($amount > 5000000) { echo json_encode(['ok'=>false,'error'=>'Presupuesto excede 5M COP/día (límite de seguridad).']); exit; }
        // Buscar budget resource name de la campaña
        $res_b = gads($customer_id, "SELECT campaign.id,campaign_budget.resource_name FROM campaign WHERE campaign.id={$camp_id} LIMIT 1", $dev_token, $access_token, $mcc_id);
        $budget_rn = $res_b['results'][0]['campaignBudget']['resourceName'] ?? '';
        if (!$budget_rn) { echo json_encode(['ok'=>false,'error'=>'No se encontró el presupuesto de la campaña']); exit; }
        $amount_micros = (int)round($amount * 1e6);
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/campaignBudgets:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>['resourceName'=>$budget_rn,'amountMicros'=>$amount_micros],'updateMask'=>'amount_micros']]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit; }
        dm_activity($client_id,'campaign_budget',"Presupuesto campaña {$camp_id}: \${$amount}");
        echo json_encode(['ok'=>true,'amount'=>$amount,'message'=>'Presupuesto actualizado a $'.number_format($amount,0)]);
        break;

    // ── PAUSAR / ACTIVAR GRUPO DE ANUNCIOS ────────────────────────────────────────
    case 'pause_adgroup':
    case 'enable_adgroup':
        dm_csrf_verify();
        dm_require_admin();
        $input   = json_decode(file_get_contents('php://input'), true) ?? [];
        $ag_id   = preg_replace('/[^0-9]/', '', $input['adgroup_id'] ?? '');
        if (!$ag_id) { echo json_encode(['ok'=>false,'error'=>'adgroup_id requerido']); exit; }
        $new_status    = $action === 'pause_adgroup' ? 'PAUSED' : 'ENABLED';
        $resource_name = "customers/{$customer_id}/adGroups/{$ag_id}";
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/adGroups:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,
            CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>['resourceName'=>$resource_name,'status'=>$new_status],'updateMask'=>'status']]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit; }
        dm_activity($client_id,'adgroup_status',"Ad Group {$ag_id}: {$new_status}");
        echo json_encode(['ok'=>true,'status'=>strtolower($new_status),'message'=>'Grupo de anuncios '.($new_status==='PAUSED'?'pausado':'activado').' correctamente']);
        break;

    // ── PAUSAR / ACTIVAR ANUNCIO INDIVIDUAL ──────────────────────────────────────
    case 'pause_ad':
    case 'enable_ad':
        dm_csrf_verify();
        dm_require_admin();
        $input   = json_decode(file_get_contents('php://input'), true) ?? [];
        $ad_id   = preg_replace('/[^0-9]/', '', $input['ad_id'] ?? '');
        $ag_id   = preg_replace('/[^0-9]/', '', $input['adgroup_id'] ?? '');
        if (!$ad_id || !$ag_id) { echo json_encode(['ok'=>false,'error'=>'ad_id y adgroup_id requeridos']); exit; }
        $new_status    = $action === 'pause_ad' ? 'PAUSED' : 'ENABLED';
        $resource_name = "customers/{$customer_id}/adGroupAds/{$ag_id}~{$ad_id}";
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/adGroupAds:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,
            CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>['resourceName'=>$resource_name,'status'=>$new_status],'updateMask'=>'status']]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit; }
        dm_activity($client_id,'ad_status',"Anuncio {$ad_id}: {$new_status}");
        echo json_encode(['ok'=>true,'status'=>strtolower($new_status),'message'=>'Anuncio '.($new_status==='PAUSED'?'pausado':'activado').' correctamente']);
        break;

    // ── AGREGAR KEYWORDS POSITIVAS A UN GRUPO ────────────────────────────────────
    case 'add_keywords':
        dm_csrf_verify();
        dm_require_admin();
        $input    = json_decode(file_get_contents('php://input'), true) ?? [];
        $keywords = $input['keywords'] ?? [];   // [{keyword, match_type}]
        $ag_id    = preg_replace('/[^0-9]/', '', $input['adgroup_id'] ?? '');
        if (empty($keywords) || !$ag_id) { echo json_encode(['ok'=>false,'error'=>'keywords y adgroup_id requeridos']); exit; }
        $ag_rn = "customers/{$customer_id}/adGroups/{$ag_id}";
        $operations = [];
        foreach ($keywords as $kw) {
            $text  = trim($kw['keyword'] ?? $kw['text'] ?? '');
            $match = strtoupper($kw['match_type'] ?? 'BROAD');
            if (!in_array($match,['EXACT','PHRASE','BROAD'])) $match='BROAD';
            if (!$text) continue;
            $operations[] = ['create'=>['adGroup'=>$ag_rn,'type'=>'KEYWORD','keyword'=>['text'=>$text,'matchType'=>$match]]];
        }
        if (empty($operations)) { echo json_encode(['ok'=>false,'error'=>'Sin keywords válidas']); exit; }
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/adGroupCriteria:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>20,
            CURLOPT_HTTPHEADER=>$h,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$operations,'partialFailure'=>true])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200 && empty($res['results'])) {
            echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit;
        }
        $applied = count($operations);
        dm_activity($client_id,'add_keywords',"{$applied} keywords positivas agregadas al grupo {$ag_id}");
        echo json_encode(['ok'=>true,'applied'=>$applied,'message'=>"{$applied} keywords agregadas al grupo de anuncios"]);
        break;

    // ── CAMBIAR ESTRATEGIA DE PUJA ────────────────────────────────────────────────
    case 'change_bidding':
        dm_csrf_verify();
        dm_require_admin();
        $input      = json_decode(file_get_contents('php://input'), true) ?? [];
        $camp_id    = preg_replace('/[^0-9]/', '', $input['campaign_id'] ?? '');
        $strategy   = strtoupper($input['strategy'] ?? '');
        $target_cpa  = (float)($input['target_cpa']  ?? 0);
        $target_roas = (float)($input['target_roas'] ?? 0);
        if (!$camp_id || !$strategy) { echo json_encode(['ok'=>false,'error'=>'campaign_id y strategy requeridos']); exit; }
        $valid = ['TARGET_CPA','TARGET_ROAS','MAXIMIZE_CONVERSIONS','MAXIMIZE_CLICKS','MANUAL_CPC'];
        if (!in_array($strategy,$valid)) { echo json_encode(['ok'=>false,'error'=>'Estrategia inválida. Válidas: '.implode(', ',$valid)]); exit; }
        $resource_name = "customers/{$customer_id}/campaigns/{$camp_id}";
        $update = ['resourceName'=>$resource_name];
        if ($strategy === 'TARGET_CPA' && $target_cpa > 0) {
            $update['targetCpa'] = ['targetCpaMicros'=>(int)round($target_cpa*1e6)];
        } elseif ($strategy === 'TARGET_ROAS' && $target_roas > 0) {
            $update['targetRoas'] = ['targetRoas'=>$target_roas/100];
        } elseif ($strategy === 'MAXIMIZE_CONVERSIONS') {
            $update['maximizeConversions'] = (object)[];
        } elseif ($strategy === 'MAXIMIZE_CLICKS') {
            $update['maximizeClicks'] = (object)[];
        } elseif ($strategy === 'MANUAL_CPC') {
            $update['manualCpc'] = ['enhancedCpcEnabled'=>true];
        }
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/campaigns:mutate";
        $h   = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,
            CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>$update,'updateMask'=>'bidding_strategy_type']]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http]); exit; }
        dm_activity($client_id,'change_bidding',"Campaña {$camp_id}: puja → {$strategy}");
        echo json_encode(['ok'=>true,'strategy'=>$strategy,'message'=>"Estrategia de puja cambiada a {$strategy}"]);
        break;

    // ── CONVERSION TRACKING CHECK ─────────────────────────────────────────────
    case 'conversions_check':
        $cache_key = "gads_conv_check_{$client_id}";
        if ($c = dm_cache_get($cache_key)) { echo json_encode(['ok'=>true,'data'=>$c,'cached'=>true]); exit; }
        $q = "SELECT conversion_action.id,conversion_action.name,conversion_action.status,
                conversion_action.type,conversion_action.include_in_conversions_metric
              FROM conversion_action
              WHERE conversion_action.status != 'REMOVED'
              ORDER BY conversion_action.status ASC LIMIT 20";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $list=[]; $active_count=0;
        foreach ($res['results']??[] as $r) {
            $ca=$r['conversionAction']??[];
            $status=strtolower($ca['status']??'');
            if($status==='enabled') $active_count++;
            $list[]=['name'=>$ca['name']??'','status'=>$status,'type'=>$ca['type']??'','included'=>(bool)($ca['includeInConversionsMetric']??false)];
        }
        $data=['active_count'=>$active_count,'total'=>count($list),'conversions'=>$list];
        dm_cache_set($cache_key,$data,60);
        echo json_encode(['ok'=>true,'data'=>$data]);
        break;

    // ── GEO TOP 5 (para widget en Resumen) ───────────────────────────────────
    case 'geo_top':
        $cache_key = "gads_geo_top_{$client_id}_{$df}_{$dt}";
        if ($c = dm_cache_get($cache_key)) { echo json_encode(['ok'=>true,'geo'=>$c,'cached'=>true]); exit; }
        $q = "SELECT geographic_view.location_type,geographic_view.country_criterion_id,
                metrics.impressions,metrics.clicks,metrics.cost_micros,
                metrics.conversions,metrics.conversions_value
              FROM geographic_view
              WHERE segments.date BETWEEN '{$df}' AND '{$dt}'
                AND campaign.status IN ('ENABLED','PAUSED')
              ORDER BY metrics.conversions DESC LIMIT 50";
        $res = gads($customer_id,$q,$dev_token,$access_token,$mcc_id);
        if (isset($res['_err'])) { echo json_encode(['ok'=>false,'error'=>$res['_err']]); exit; }
        $country_map=['2170'=>'Colombia','2840'=>'EE.UU.','2484'=>'México','2076'=>'Brasil',
            '2032'=>'Argentina','2152'=>'Chile','2604'=>'Perú','2218'=>'Ecuador','2862'=>'Venezuela',
            '2724'=>'España','2858'=>'Uruguay','2591'=>'Panamá','2188'=>'Costa Rica','2320'=>'Guatemala'];
        $agg=[];
        foreach ($res['results']??[] as $r) {
            $gv=$r['geographicView']??[]; $m=$r['metrics']??[];
            $cid_geo=$gv['countryCriterionId']??'';
            $name=$country_map[$cid_geo]??'País '.$cid_geo;
            if(!isset($agg[$cid_geo])) $agg[$cid_geo]=['location'=>$name,'clicks'=>0,'spend'=>0,'conversions'=>0,'conv_value'=>0];
            $agg[$cid_geo]['clicks']+=(int)($m['clicks']??0);
            $agg[$cid_geo]['spend']+=($m['costMicros']??0)/1e6;
            $agg[$cid_geo]['conversions']+=(float)($m['conversions']??0);
            $agg[$cid_geo]['conv_value']+=(float)($m['conversionsValue']??0);
        }
        foreach($agg as &$d) { $d['spend']=round($d['spend'],2); $d['conversions']=round($d['conversions'],1); $d['roas']=$d['spend']>0?round($d['conv_value']/$d['spend'],2):0; }
        usort($agg,fn($a,$b)=>$b['conversions']<=>$a['conversions']);
        $geo=array_slice(array_values($agg),0,5);
        dm_cache_set($cache_key,$geo,30);
        echo json_encode(['ok'=>true,'geo'=>$geo]);
        break;

    // ── CREAR GRUPO DE ANUNCIOS (AVI) ────────────────────────────────────────────
    case 'create_adgroup':
        dm_csrf_verify();
        dm_require_admin();
        $input     = json_decode(file_get_contents('php://input'), true) ?? [];
        $camp_id   = preg_replace('/[^0-9]/', '', $input['campaign_id'] ?? '');
        $ag_name   = trim($input['name'] ?? 'Nuevo grupo');
        $keywords  = $input['keywords']   ?? [];   // [{text, match_type}] o strings separados por coma
        $bid_micro = (int)(((float)($input['cpc_bid'] ?? 0.5)) * 1e6);
        if (!$camp_id) { echo json_encode(['ok'=>false,'error'=>'campaign_id requerido']); exit; }
        $camp_rn = "customers/{$customer_id}/campaigns/{$camp_id}";
        $h = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        // Crear grupo
        $ag_url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/adGroups:mutate";
        $ch = curl_init($ag_url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['create'=>[
                'name'=>$ag_name,'campaign'=>$camp_rn,'status'=>'ENABLED',
                'type'=>'SEARCH_STANDARD','cpcBidMicros'=>$bid_micro,
            ]]]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error al crear grupo','http'=>$http]); exit; }
        $ag_rn = $res['results'][0]['resourceName'] ?? '';
        $ag_id = preg_replace('/[^0-9]/','',$ag_rn);
        // Agregar keywords si vienen
        $kw_added = 0;
        if (!empty($keywords)) {
            // Acepta string separado por comas o array
            if (is_string($keywords)) $keywords = array_map('trim', explode(',', $keywords));
            $kw_ops = [];
            foreach ($keywords as $kw) {
                $text  = is_array($kw) ? trim($kw['text']??'') : trim($kw);
                $match = is_array($kw) ? strtoupper($kw['match_type']??'BROAD') : 'BROAD';
                if (!in_array($match,['EXACT','PHRASE','BROAD'])) $match='BROAD';
                if (!$text) continue;
                $kw_ops[] = ['create'=>['adGroup'=>$ag_rn,'type'=>'KEYWORD','keyword'=>['text'=>$text,'matchType'=>$match]]];
            }
            if ($kw_ops) {
                $kw_url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/adGroupCriteria:mutate";
                $ch=curl_init($kw_url);
                curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
                    CURLOPT_HTTPHEADER=>$h,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$kw_ops,'partialFailure'=>true])]);
                curl_exec($ch); curl_close($ch);
                $kw_added = count($kw_ops);
            }
        }
        dm_activity($client_id,'create_adgroup',"Grupo '{$ag_name}' creado en campaña {$camp_id}");
        echo json_encode(['ok'=>true,'adgroup_id'=>$ag_id,'keywords_added'=>$kw_added,
            'message'=>"Grupo '{$ag_name}' creado".($kw_added?" con {$kw_added} keywords":'')]);
        break;

    // ── EDITAR ANUNCIO RSA (reemplazar headlines / descriptions) ─────────────────
    case 'edit_rsa':
        dm_csrf_verify();
        dm_require_admin();
        $input      = json_decode(file_get_contents('php://input'), true) ?? [];
        $ag_id      = preg_replace('/[^0-9]/', '', $input['adgroup_id'] ?? '');
        $ad_id      = preg_replace('/[^0-9]/', '', $input['ad_id'] ?? '');
        $headlines  = $input['headlines']    ?? [];  // array de strings o string separado por coma
        $descs      = $input['descriptions'] ?? [];
        $final_url  = trim($input['final_url'] ?? '');
        if (!$ag_id || !$ad_id) { echo json_encode(['ok'=>false,'error'=>'adgroup_id y ad_id requeridos']); exit; }
        // Normalizar a arrays
        if (is_string($headlines)) $headlines = array_filter(array_map('trim', explode(',', $headlines)));
        if (is_string($descs))     $descs     = array_filter(array_map('trim', explode(',', $descs)));
        if (empty($headlines) && empty($descs)) { echo json_encode(['ok'=>false,'error'=>'headlines o descriptions requeridos']); exit; }
        if (!empty($headlines) && count($headlines) < 3) { echo json_encode(['ok'=>false,'error'=>'RSA requiere mínimo 3 headlines']); exit; }
        $rn = "customers/{$customer_id}/adGroupAds/{$ag_id}~{$ad_id}";
        $ad_obj = ['resourceName'=>$rn,'status'=>'ENABLED','ad'=>['responsiveSearchAd'=>[]]];
        if (!empty($headlines)) {
            $ad_obj['ad']['responsiveSearchAd']['headlines'] = array_values(array_map(fn($h)=>['text'=>substr(trim($h),0,30)], $headlines));
        }
        if (!empty($descs)) {
            $ad_obj['ad']['responsiveSearchAd']['descriptions'] = array_values(array_map(fn($d)=>['text'=>substr(trim($d),0,90)], $descs));
        }
        if ($final_url) $ad_obj['ad']['finalUrls'] = [$final_url];
        $mask_parts = [];
        if (!empty($headlines)) $mask_parts[] = 'ad.responsive_search_ad.headlines';
        if (!empty($descs))     $mask_parts[] = 'ad.responsive_search_ad.descriptions';
        if ($final_url)         $mask_parts[] = 'ad.final_urls';
        $h = ['Content-Type: application/json','Authorization: Bearer '.$access_token,'developer-token: '.$dev_token];
        if ($mcc_id && $mcc_id !== $customer_id) $h[] = 'login-customer-id: '.$mcc_id;
        $url = "https://googleads.googleapis.com/v22/customers/{$customer_id}/adGroupAds:mutate";
        $ch = curl_init($url);
        curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_TIMEOUT=>15,
            CURLOPT_HTTPHEADER=>$h,CURLOPT_POSTFIELDS=>json_encode(['operations'=>[['update'=>$ad_obj,'updateMask'=>implode(',',$mask_parts)]]])]);
        $raw=curl_exec($ch); $http=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        $res=json_decode($raw,true);
        if ($http!==200) { echo json_encode(['ok'=>false,'error'=>$res['error']['message']??'Error API','http'=>$http,'raw'=>$res]); exit; }
        dm_activity($client_id,'edit_rsa',"RSA {$ad_id} actualizado en grupo {$ag_id}");
        echo json_encode(['ok'=>true,'message'=>'Anuncio RSA actualizado correctamente']);
        break;

    default:
        echo json_encode(['ok'=>false,'error'=>'Acción desconocida']);
}
