* ============================================================================
* 🛡️ CENTINELA SQR - VERSIÓN 0.6 (White List Support)
* ============================================================================
* DESCRIPCIÓN:
* Analiza términos de búsqueda (SQR).
* Filtra:
* 1. Keywords exactas ya existentes en la cuenta.
* 2. Términos presentes en la pestaña "WHITE_LIST" del Spreadsheet.
* ============================================================================
*/
const CONFIG = {
// 👇👇👇 PEGA AQUÍ EL ENLACE DE TU GOOGLE SHEET 👇👇👇
SPREADSHEET_URL: "https://docs.google.com/spreadsheets/d/1bP-nYgCZdYDIn8Cn_BbXfEUhMzX-kzl3lCGlzJXT-Ic/edit?gid=0#gid=0",
/* OPCIONES DE FECHA:
1. Predefinidas: "TODAY", "YESTERDAY", "LAST_7_DAYS", "LAST_30_DAYS", "LAST_MONTH".
2. Personalizado: Usa el formato "YYYYMMDD,YYYYMMDD" (Ej: "20240101,20240131")
*/
DATE_RANGE: "20260220,20260223",
COLORS: {
HEADER_SQR: "#0d47a1",
HEADER_KW: "#444444",
HEADER_WL: "#2e7d32", // Color Verde para la White List
TEXT: "#FFFFFF"
}
};
function main() {
Logger.log("⚙️ INICIANDO PROCESO CENTINELA V0.6...");
// --- VALIDACIÓN DE SEGURIDAD ---
if (!CONFIG.SPREADSHEET_URL || CONFIG.SPREADSHEET_URL.indexOf("docs.google.com") === -1) {
throw new Error("❌ ERROR FATAL: No has puesto un enlace válido en CONFIG.SPREADSHEET_URL.");
}
Logger.log("📅 Rango de fechas seleccionado: " + CONFIG.DATE_RANGE);
// Abrimos la hoja manualmente
const ss = openSpreadsheetFromUrl();
// PASO 1: Generar el "Mapa de Exclusión" (Keywords existentes)
const existingKeywordsSet = exportKeywordsAndBuildCache(ss);
// PASO 2: Cargar la "White List" (Términos aprobados/ignorados)
const whiteListSet = loadWhiteList(ss);
// PASO 3: Procesar el SQR aplicando AMBOS filtros
exportSQR(ss, existingKeywordsSet, whiteListSet);
Logger.log("✅ PROCESO FINALIZADO CON ÉXITO.");
Logger.log("🔗 Datos volcados en: " + ss.getUrl());
}
/**
* Carga la lista blanca desde la pestaña WHITE_LIST
*/
function loadWhiteList(ss) {
Logger.log("... Paso 2: Cargando White List...");
let sheet = ss.getSheetByName("WHITE_LIST");
const whiteListSet = new Set();
// Si no existe la hoja, la creamos para que el usuario sepa donde poner los datos
if (!sheet) {
sheet = ss.insertSheet("WHITE_LIST");
sheet.appendRow(["TERMINOS IGNORADOS (WHITE LIST)"]);
applyHeaderStyle(sheet.getRange("A1"), CONFIG.COLORS.HEADER_WL);
Logger.log(" -> ⚠️ Pestaña WHITE_LIST no existía. Se ha creado vacía.");
return whiteListSet;
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
Logger.log(" -> White List vacía (solo cabecera o nada).");
return whiteListSet;
}
// Leemos desde A2 hasta la última fila
const data = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
for (let i = 0; i < data.length; i++) {
const term = data[i][0];
if (term) {
// Normalizamos a minúsculas y quitamos espacios extra para asegurar coincidencia
whiteListSet.add(String(term).toLowerCase().trim());
}
}
Logger.log( -> 🛡️ White List cargada: ${whiteListSet.size} términos.);
return whiteListSet;
}
/**
* Descarga Keywords Exactas para referencia y construye el filtro de memoria.
*/
function exportKeywordsAndBuildCache(ss) {
Logger.log("... Paso 1: Indexando Keywords Exactas Activas...");
let sheet = ss.getSheetByName("KEYWORDS_DATA");
if (!sheet) {
sheet = ss.insertSheet("KEYWORDS_DATA");
sheet.appendRow(["ADGROUP_ID", "KEYWORD", "MATCH_TYPE", "ESTADO"]);
applyHeaderStyle(sheet.getRange("A1:D1"), CONFIG.COLORS.HEADER_KW);
} else {
const lastRow = sheet.getLastRow();
if (lastRow > 1) sheet.getRange(2, 1, lastRow - 1, 4).clearContent();
}
const kwIterator = AdsApp.keywords()
.withCondition("Status = ENABLED")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.withCondition("KeywordMatchType = EXACT")
.get();
const kwData = [];
const cacheSet = new Set();
while (kwIterator.hasNext()) {
const kw = kwIterator.next();
const adGroupId = kw.getAdGroup().getId();
const text = kw.getText().toLowerCase();
kwData.push([adGroupId, kw.getText(), kw.getMatchType(), "Activa"]);
cacheSet.add(${adGroupId}|${text});
}
if (kwData.length > 0) {
sheet.getRange(2, 1, kwData.length, 4).setValues(kwData);
Logger.log( -> Indexadas ${kwData.length} Keywords Exactas.);
}
return cacheSet;
}
/**
* Descarga y filtra el reporte de términos de búsqueda.
* AHORA ACEPTA DOS FILTROS: Keywords existentes y White List
*/
function exportSQR(ss, exclusionSet, whiteListSet) {
Logger.log("... Paso 3: Analizando Términos de Búsqueda (SQR)...");
let sheet = ss.getSheetByName("SQR_DATA");
if (!sheet) {
sheet = ss.insertSheet("SQR_DATA");
sheet.appendRow(["FECHA_REPORTE", "CAMPAÑA", "ADGROUP", "TERMINO DE BUSQUEDA", "IMPRESIONES", "CLICS", "CPC", "COSTO", "CTR", "CONVERSIONES", "ADGROUP_ID"]);
applyHeaderStyle(sheet.getRange("A1:K1"), CONFIG.COLORS.HEADER_SQR);
sheet.setFrozenRows(1);
}
const executionDate = Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), "yyyy-MM-dd");
const lastRow = sheet.getLastRow();
if (lastRow > 1) sheet.getRange(2, 1, lastRow - 1, 11).clearContent();
const report = AdsApp.report(
`SELECT CampaignName, AdGroupName, Query, Impressions, Clicks, AverageCpc, Cost, Ctr, Conversions, AdGroupId
FROM SEARCH_QUERY_PERFORMANCE_REPORT
WHERE Impressions > 0
DURING ${CONFIG.DATE_RANGE}`
);
const rows = report.rows();
const validRows = [];
// Contadores para el log
let skippedExisting = 0;
let skippedWhiteList = 0;
while (rows.hasNext()) {
const row = rows.next();
const queryClean = row["Query"].toLowerCase().trim();
const adGroupId = row["AdGroupId"];
const currentKey = ${adGroupId}|${queryClean};
// 1. FILTRO DE KEYWORDS EXISTENTES
if (exclusionSet.has(currentKey)) {
skippedExisting++;
continue;
}
// 2. FILTRO DE WHITE LIST (NUEVO)
if (whiteListSet.has(queryClean)) {
skippedWhiteList++;
continue;
}
validRows.push([
executionDate,
row["CampaignName"],
row["AdGroupName"],
row["Query"],
row["Impressions"],
row["Clicks"],
row["AverageCpc"],
row["Cost"],
row["Ctr"],
row["Conversions"],
row["AdGroupId"]
]);
}
if (validRows.length > 0) {
sheet.getRange(2, 1, validRows.length, 11).setValues(validRows);
Logger.log( -> 🛡️ SQR Exportado: ${validRows.length} filas.);
Logger.log( -> 👻 Filtrados (Ya existen): ${skippedExisting});
Logger.log( -> 🦢 Filtrados (White List): ${skippedWhiteList});
} else {
Logger.log(" -> No se encontraron términos nuevos en ese rango de fechas.");
}
}
// --- HELPERS ---
function openSpreadsheetFromUrl() {
Logger.log("📂 Intentando abrir Spreadsheet...");
try {
const ss = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
return ss;
} catch (e) {
throw new Error("❌ ERROR: Verifica URL y Permisos. " + e.message);
}
}
function applyHeaderStyle(range, color) {
range.setFontWeight("bold").setBackground(color).setFontColor(CONFIG.COLORS.TEXT);
}
}