Gratis · ohne Anmeldung

Werkzeuge, die Ihr Konto selbst durchleuchten.

Fertige Google-Ads-Skripte zum Kopieren. Sie laufen in Ihrem eigenen Konto, ändern nichts und zeigen in Minuten, wo Budget verpufft. Kein Abo, kein Login, keine E-Mail nötig.

✓ Read-only — reines Anschauen✓ Copy & Paste✓ läuft in Ihrem Konto

Anrufe-Verpufft-Detektor

Findet Kampagnen, die klicken und Geld kosten — aber 0 Conversions bringen. In einer Minute wissen Sie, wo Budget ohne Wirkung verpufft.

Google Ads ScriptRead-only — ändert nichtsErgebnis im Log~1 Min.
Was es Ihnen zeigt
  • Alle aktiven Kampagnen mit Klicks, aber 0 Conversions — teuerste zuerst
  • Klartext-Warnung, falls das Konto gar keine Conversions meldet (Tracking defekt)
  • Die Summe des Budgets, das ohne eine einzige Conversion verbrennt
1

Code unten kopieren

2

In Google Ads: „Tools → Bulk-Aktionen → Skripts → +"

3

Einfügen, „Vorschau" — das Ergebnis steht im Protokoll

anrufe-detektor.js
// ============================================================
//  ANRUFE-VERPUFFT-DETEKTOR  ·  prompted audits  ·  READ-ONLY
//  Findet Kampagnen mit Klicks/Kosten, aber 0 Conversions.
//  Ändert NICHTS am Konto – reines Reporting.
// ============================================================

// ----------------------------- KONFIG -----------------------------
var KONFIG = {
  ZEITRAUM_TAGE: 30,           // Betrachtungszeitraum in Tagen
  NUR_AKTIVE_KAMPAGNEN: true,  // true = nur ENABLED-Kampagnen
  MIN_KLICKS: 1,               // ab so vielen Klicks gilt eine Kampagne als "auffällig"
  MIN_KOSTEN: 0                // Mindestkosten (Kontowährung), 0 = egal
};
// ------------------------------------------------------------------

function main() {
  var z = zeitraum(KONFIG.ZEITRAUM_TAGE);
  var waehrung = AdsApp.currentAccount().getCurrencyCode();

  // Nur aktive Kampagnen? -> Filter dynamisch anhängen
  var statusFilter = KONFIG.NUR_AKTIVE_KAMPAGNEN ? " AND campaign.status = 'ENABLED'" : "";

  var query =
    "SELECT campaign.name, campaign.status, " +
    "metrics.clicks, metrics.cost_micros, metrics.conversions " +
    "FROM campaign " +
    "WHERE segments.date BETWEEN '" + z.von + "' AND '" + z.bis + "'" +
    statusFilter;

  var verdaechtige = [];
  var kontoConversions = 0; // Summe aller Conversions -> liefert Tracking Daten?

  try {
    var it = AdsApp.search(query);
    while (it.hasNext()) {
      var r = it.next();
      var klicks = Number(r.metrics.clicks) || 0;
      var kosten = (Number(r.metrics.costMicros) || 0) / 1e6; // Micros -> Währung
      var conv   = Number(r.metrics.conversions) || 0;

      kontoConversions += conv;

      // Klicks/Kosten vorhanden, aber 0 Conversions = Geld verpufft
      if (klicks >= KONFIG.MIN_KLICKS && kosten >= KONFIG.MIN_KOSTEN && conv === 0) {
        verdaechtige.push({ name: r.campaign.name, klicks: klicks, kosten: kosten, conv: conv });
      }
    }
  } catch (e) {
    Logger.log('FEHLER bei der Abfrage: ' + e);
    return;
  }

  var trackingAktiv = kontoConversions > 0;
  verdaechtige.sort(function (a, b) { return b.kosten - a.kosten; }); // teuerste zuerst

  // ------------------------- AUSGABE -------------------------
  Logger.log('=== ANRUFE-VERPUFFT-DETEKTOR (' + z.von + ' bis ' + z.bis + ') ===');
  Logger.log('Conversion-Tracking: ' + (trackingAktiv
      ? 'liefert Daten (' + kontoConversions.toFixed(1) + ' Conv. gesamt)'
      : 'KEINE Conversions im Konto – Tracking evtl. defekt oder nicht eingerichtet!'));
  Logger.log('');
  Logger.log('Kampagne | Klicks | Kosten (' + waehrung + ') | Conversions | Warnung');
  Logger.log('----------------------------------------------------------------');

  if (verdaechtige.length === 0) {
    Logger.log('Keine Kampagne mit Klicks aber 0 Conversions gefunden.');
  } else {
    for (var i = 0; i < verdaechtige.length; i++) {
      var v = verdaechtige[i];
      var warn = trackingAktiv ? 'Geld ohne Wirkung' : 'evtl. Tracking-Problem';
      Logger.log(v.name + ' | ' + v.klicks + ' | ' + v.kosten.toFixed(2) + ' | ' + v.conv + ' | ' + warn);
    }
  }

  Logger.log('----------------------------------------------------------------');
  Logger.log('FAZIT: ' + fazit(verdaechtige, trackingAktiv, waehrung));
  Logger.log('Rote Ampeln? Kostenlose Ersteinschaetzung: hallo@promptedaudits.de - kein Abo.');
}

// 1 Satz Fazit je nach Lage
function fazit(verdaechtige, trackingAktiv, waehrung) {
  if (!trackingAktiv) {
    return 'Das Konto meldet 0 Conversions - vermutlich fehlt/streikt das Tracking; Zahlen erst fixen, dann bewerten.';
  }
  if (verdaechtige.length === 0) {
    return 'Alle aktiven Kampagnen mit Klicks liefern auch Conversions - kein akuter Geld-Verpuffer.';
  }
  var summe = 0;
  for (var i = 0; i < verdaechtige.length; i++) summe += verdaechtige[i].kosten;
  return verdaechtige.length + ' Kampagne(n) verbrennen rund ' + summe.toFixed(2) + ' ' +
         waehrung + ' ohne eine einzige Conversion - hier lohnt der genaue Blick zuerst.';
}

// Zeitraum als ISO-Datum (von/bis) in Kontozeitzone
function zeitraum(tage) {
  var tz = AdsApp.currentAccount().getTimeZone();
  var ende = new Date();
  var start = new Date();
  start.setDate(ende.getDate() - (tage - 1));
  return {
    von: Utilities.formatDate(start, tz, 'yyyy-MM-dd'),
    bis: Utilities.formatDate(ende, tz, 'yyyy-MM-dd')
  };
}

N-Gramm-Geldverbrenner

Zerlegt Ihre Suchbegriffe in einzelne Wörter und findet die Wortmuster, die viel kosten und nie zu einer Conversion führen — die klassischen Ausschluss-Kandidaten.

Google Ads ScriptRead-only — ändert nichtsErgebnis als Google Sheet2–3 Min.
Was es Ihnen zeigt
  • Die teuersten Wörter & Wortpaare ohne eine einzige Conversion
  • Kosten, Klicks und CPC pro Wortmuster — sauber aggregiert
  • Fertiges Google Sheet, das Sie direkt als Ausschluss-Liste nutzen können
1

Code unten kopieren

2

In Google Ads unter „Skripts" einfügen und autorisieren

3

Ausführen — die Sheet-URL steht am Ende im Protokoll

ngram-geldverbrenner.js
// ============================================================
//  N-GRAMM-GELDVERBRENNER  ·  prompted audits  ·  READ-ONLY
//  Zerlegt Suchbegriffe in 1-/2-Wort-N-Gramme, aggregiert
//  Kosten & Conversions, listet die teuersten OHNE Conversion.
//  Ergebnis -> neues Google Sheet (URL wird geloggt).
// ============================================================

// ----------------------------- KONFIG -----------------------------
var KONFIG = {
  ZEITRAUM_TAGE: 60,          // 30–90 empfohlen
  MAX_NGRAMM: 2,              // 1- und 2-Wort-N-Gramme
  MIN_KOSTEN_ANZEIGEN: 1,     // N-Gramme unter diesen Kosten ignorieren (Rauschen)
  MIN_VORKOMMEN: 2,           // N-Gramm muss in mind. so vielen Suchbegriffen vorkommen
  TOP_N: 50,                  // wie viele teuerste N-Gramme ins Sheet
  // Fuellwoerter, die keine sinnvollen N-Gramme bilden:
  STOPWOERTER: ['und','oder','der','die','das','fuer','mit','von','in','im','zu','den','ein','eine','am','an','auf']
};
// ------------------------------------------------------------------

function main() {
  var z = zeitraum(KONFIG.ZEITRAUM_TAGE);
  var waehrung = AdsApp.currentAccount().getCurrencyCode();

  var query =
    "SELECT search_term_view.search_term, metrics.clicks, " +
    "metrics.cost_micros, metrics.conversions " +
    "FROM search_term_view " +
    "WHERE segments.date BETWEEN '" + z.von + "' AND '" + z.bis + "' " +
    "AND metrics.clicks > 0";

  // Stopwort-Lookup fuer O(1)-Zugriff
  var stop = {};
  for (var s = 0; s < KONFIG.STOPWOERTER.length; s++) stop[KONFIG.STOPWOERTER[s]] = true;

  // key (N-Gramm) -> aggregierte Kennzahlen
  var ngramme = {};
  var zeilen = 0;

  try {
    var it = AdsApp.search(query);
    while (it.hasNext()) {
      var r = it.next();
      zeilen++;

      var term   = (r.searchTermView.searchTerm || '').toLowerCase();
      var kosten = (Number(r.metrics.costMicros) || 0) / 1e6;
      var conv   = Number(r.metrics.conversions) || 0;
      var klicks = Number(r.metrics.clicks) || 0;

      // in Woerter zerlegen, Stopwoerter/1-Zeichen-Tokens raus
      var woerter = term.split(/\s+/).filter(function (w) { return w.length > 1 && !stop[w]; });
      var grams = ngrammeAusWoertern(woerter, KONFIG.MAX_NGRAMM);

      // Hinweis: Jedes N-Gramm bekommt die vollen Kennzahlen des Suchbegriffs.
      // Das ist der Standard bei N-Gramm-Analysen - Summen != Kontosumme (gewollt).
      var gesehen = {}; // pro Suchbegriff jedes N-Gramm nur 1x zaehlen
      for (var g = 0; g < grams.length; g++) {
        var key = grams[g];
        if (gesehen[key]) continue;
        gesehen[key] = true;

        if (!ngramme[key]) ngramme[key] = { kosten: 0, conv: 0, klicks: 0, vorkommen: 0 };
        ngramme[key].kosten    += kosten;
        ngramme[key].conv      += conv;
        ngramme[key].klicks    += klicks;
        ngramme[key].vorkommen += 1;
      }
    }
  } catch (e) {
    Logger.log('FEHLER bei der Suchbegriffs-Abfrage: ' + e);
    return;
  }

  // Kandidaten: 0 Conversions + genug Kosten + genug Vorkommen
  var kandidaten = [];
  for (var key in ngramme) {
    var d = ngramme[key];
    if (d.conv === 0 && d.kosten >= KONFIG.MIN_KOSTEN_ANZEIGEN && d.vorkommen >= KONFIG.MIN_VORKOMMEN) {
      kandidaten.push({
        ngram: key,
        woerter: key.split(' ').length,
        kosten: d.kosten,
        klicks: d.klicks,
        vorkommen: d.vorkommen,
        cpc: d.klicks > 0 ? d.kosten / d.klicks : 0  // Division-durch-Null abgefangen
      });
    }
  }
  kandidaten.sort(function (a, b) { return b.kosten - a.kosten; });
  kandidaten = kandidaten.slice(0, KONFIG.TOP_N);

  // ------------------- Google Sheet schreiben -------------------
  var ss = SpreadsheetApp.create('N-Gramm-Geldverbrenner ' + z.von + ' bis ' + z.bis);
  var sheet = ss.getActiveSheet();
  sheet.setName('N-Gramme ohne Conversion');

  var kopf = ['N-Gramm', 'Woerter', 'Kosten (' + waehrung + ')', 'Klicks', 'CPC', 'Vorkommen', 'Conversions'];
  var daten = [kopf];

  for (var i = 0; i < kandidaten.length; i++) {
    var k = kandidaten[i];
    daten.push([k.ngram, k.woerter, round2(k.kosten), k.klicks, round2(k.cpc), k.vorkommen, 0]);
  }
  if (daten.length === 1) {
    daten.push(['- keine N-Gramme ohne Conversion gefunden -', '', '', '', '', '', '']);
  }

  // In einem Rutsch schreiben (schnell + robust)
  sheet.getRange(1, 1, daten.length, kopf.length).setValues(daten);
  sheet.getRange(1, 1, 1, kopf.length).setFontWeight('bold');
  sheet.setFrozenRows(1);
  sheet.autoResizeColumns(1, kopf.length);

  var foot = daten.length + 2;
  sheet.getRange(foot, 1).setValue(
    'Analysierte Suchbegriffe: ' + zeilen + ' · Zeitraum: ' + z.von + ' bis ' + z.bis);
  sheet.getRange(foot + 1, 1).setValue(
    'Rote Ampeln? Kostenlose Ersteinschaetzung: hallo@promptedaudits.de - kein Abo.');

  Logger.log('=== N-GRAMM-GELDVERBRENNER fertig ===');
  Logger.log('Suchbegriffe verarbeitet: ' + zeilen + ' · Kandidaten im Sheet: ' + kandidaten.length);
  Logger.log('SHEET-URL: ' + ss.getUrl());
}

// Erzeugt alle 1..maxN-Wort-N-Gramme aus einer Wortliste
function ngrammeAusWoertern(woerter, maxN) {
  var out = [];
  for (var n = 1; n <= maxN; n++) {
    for (var i = 0; i + n <= woerter.length; i++) {
      out.push(woerter.slice(i, i + n).join(' '));
    }
  }
  return out;
}

function round2(x) { return Math.round(x * 100) / 100; }

function zeitraum(tage) {
  var tz = AdsApp.currentAccount().getTimeZone();
  var ende = new Date();
  var start = new Date();
  start.setDate(ende.getDate() - (tage - 1));
  return {
    von: Utilities.formatDate(start, tz, 'yyyy-MM-dd'),
    bis: Utilities.formatDate(ende, tz, 'yyyy-MM-dd')
  };
}

Konto-Schnellaudit (Ampel-Sheet)

Zehn Gesundheits-Checks auf einen Blick: von Conversion-Tracking über Ausschluss-Keywords bis Impression-Share — als grün/gelb/rot eingefärbtes Sheet.

Google Ads ScriptRead-only — ändert nichtsErgebnis als Google Sheet2–3 Min.
Was es Ihnen zeigt
  • 10 zentrale Konto-Checks mit Ampel und Kurzbefund in Klartext
  • Sofort sichtbar, was rot ist und zuerst Aufmerksamkeit braucht
  • Ein Sheet zum Behalten, Weitergeben oder Nachverfolgen
1

Code unten kopieren

2

In Google Ads unter „Skripts" einfügen und autorisieren

3

Ausführen — die Sheet-URL steht am Ende im Protokoll

schnellaudit-sheet.js
// ============================================================
//  KONTO-SCHNELLAUDIT-ALS-SHEET  ·  prompted audits  ·  READ-ONLY
//  10 Health-Checks -> Ampel-Sheet (gruen/gelb/rot + Kurzbefund).
//  Aendert NICHTS am Konto. URL wird geloggt.
// ============================================================

// ----------------------------- KONFIG -----------------------------
var KONFIG = {
  ZEITRAUM_TAGE: 30,          // Zeitraum fuer metrische Checks
  // Schwellen fuer Ampeln (Anteile in %):
  ST_KOSTEN_OHNE_CONV_GELB: 25,  ST_KOSTEN_OHNE_CONV_ROT: 50, // Suchbegriffs-Kostenanteil ohne Conv.
  IS_VERLUST_GELB: 10,           IS_VERLUST_ROT: 20           // Impression-Share-Verlust in %
};
// ------------------------------------------------------------------

function main() {
  var z = zeitraum(KONFIG.ZEITRAUM_TAGE);
  var waehrung = AdsApp.currentAccount().getCurrencyCode();

  // Kampagnen-Metriken einmal laden (mehrere Checks nutzen sie)
  var metriken = null;
  try { metriken = ladeKampagnenMetriken(z); } catch (e) { metriken = null; }

  var checks = [];

  // 1) Conversion-Tracking liefert Daten?
  checks.push(pruefe('Conversion-Tracking aktiv', function () {
    if (!metriken) throw 'Kampagnendaten fehlen';
    var conv = 0;
    for (var i = 0; i < metriken.length; i++) conv += metriken[i].conv;
    if (conv > 0) return ampel('gruen', conv.toFixed(1) + ' Conversions im Zeitraum - Tracking liefert Daten.');
    return ampel('rot', '0 Conversions im Konto - Tracking fehlt oder ist defekt. Zuerst fixen!');
  }));

  // 2) Aktive vs. pausierte Kampagnen
  checks.push(pruefe('Aktive vs. pausierte Kampagnen', function () {
    var c = zaehleKampagnenStatus();
    var txt = c.aktiv + ' aktiv, ' + c.pausiert + ' pausiert.';
    if (c.aktiv === 0) return ampel('rot', 'Keine aktive Kampagne - es laeuft nichts.');
    if (c.pausiert > c.aktiv) return ampel('gelb', txt + ' Auffaellig viele pausierte Kampagnen - aufraeumen?');
    return ampel('gruen', txt);
  }));

  // 3) Kampagnen mit Ausgaben, aber 0 Conversions
  checks.push(pruefe('Kampagnen mit Ausgaben ohne Conversion', function () {
    if (!metriken) throw 'Kampagnendaten fehlen';
    var betroffen = 0, verbrannt = 0, gesamtKosten = 0;
    for (var i = 0; i < metriken.length; i++) {
      var m = metriken[i];
      gesamtKosten += m.kosten;
      if (m.kosten > 0 && m.conv === 0) { betroffen++; verbrannt += m.kosten; }
    }
    if (betroffen === 0) return ampel('gruen', 'Alle Kampagnen mit Ausgaben liefern auch Conversions.');
    var anteil = gesamtKosten > 0 ? (verbrannt / gesamtKosten * 100) : 0;
    var txt = betroffen + ' Kampagne(n) ohne Conversion, ' + verbrannt.toFixed(2) + ' ' + waehrung +
              ' (' + anteil.toFixed(0) + '% der Ausgaben).';
    return ampel(anteil >= KONFIG.ST_KOSTEN_OHNE_CONV_ROT ? 'rot' : 'gelb', txt);
  }));

  // 4) Suchbegriffs-Kostenanteil ohne Conversion
  checks.push(pruefe('Suchbegriffe: Kostenanteil ohne Conversion', function () {
    var a = suchbegriffAnteilOhneConv(z);
    if (a.gesamt === 0) return ampel('gelb', 'Keine Suchbegriffs-Kosten im Zeitraum.');
    var pct = a.ohneConv / a.gesamt * 100;
    var txt = pct.toFixed(0) + '% der Suchbegriffs-Kosten ohne Conversion (' +
              a.ohneConv.toFixed(2) + ' / ' + a.gesamt.toFixed(2) + ' ' + waehrung + ').';
    if (pct >= KONFIG.ST_KOSTEN_OHNE_CONV_ROT) return ampel('rot', txt + ' Ausschluss-Listen pruefen.');
    if (pct >= KONFIG.ST_KOSTEN_OHNE_CONV_GELB) return ampel('gelb', txt);
    return ampel('gruen', txt);
  }));

  // 5) Negative Keywords im Einsatz
  checks.push(pruefe('Auszuschliessende (Negative) Keywords', function () {
    var n = zaehleNegativeKeywords();
    if (n === 0) return ampel('rot', 'Keine auszuschliessenden Keywords gefunden - hohes Streuverlust-Risiko.');
    if (n < 10) return ampel('gelb', 'Nur ' + n + ' Ausschluss-Kriterien - fuer die meisten Konten zu wenig.');
    return ampel('gruen', n + ' Ausschluss-Kriterien im Einsatz.');
  }));

  // 6) Sitelinks / Assets vorhanden
  checks.push(pruefe('Anzeigenerweiterungen (Sitelinks)', function () {
    var c = zaehleKampagnenStatus();
    var mitSitelink = zaehleKampagnenMitSitelinks();
    if (c.aktiv === 0) return ampel('gelb', 'Keine aktive Kampagne zum Pruefen.');
    var pct = mitSitelink / c.aktiv * 100;
    var txt = mitSitelink + ' von ' + c.aktiv + ' aktiven Kampagnen mit Sitelinks (' + pct.toFixed(0) + '%).';
    if (mitSitelink === 0) return ampel('rot', 'Keine Sitelinks - wertvolle Anzeigenflaeche verschenkt.');
    if (pct < 100) return ampel('gelb', txt);
    return ampel('gruen', txt);
  }));

  // 7) Gebotsstrategie-Uebersicht (informativ)
  checks.push(pruefe('Gebotsstrategien-Mix', function () {
    if (!metriken) throw 'Kampagnendaten fehlen';
    var mix = {};
    for (var i = 0; i < metriken.length; i++) {
      var s = metriken[i].strategie || 'UNBEKANNT';
      mix[s] = (mix[s] || 0) + 1;
    }
    var teile = [];
    for (var k in mix) teile.push(k + ': ' + mix[k]);
    if (teile.length === 0) return ampel('gelb', 'Keine aktiven Kampagnen im Zeitraum.');
    return ampel('gruen', teile.join(' · '));
  }));

  // 8) Impression-Share-Verlust durch Budget
  checks.push(pruefe('Impression-Share-Verlust (Budget)', function () {
    return isVerlustCheck(metriken, 'budgetLostIS', 'Budget zu knapp');
  }));

  // 9) Impression-Share-Verlust durch Rang/Qualitaet
  checks.push(pruefe('Impression-Share-Verlust (Rang/Qualitaet)', function () {
    return isVerlustCheck(metriken, 'rankLostIS', 'Gebote/Qualitaet zu niedrig');
  }));

  // 10) Anzeigengruppen ohne aktive Anzeige
  checks.push(pruefe('Anzeigengruppen ohne aktive Anzeige', function () {
    var a = anzeigengruppenOhneAnzeige();
    if (a.gesamt === 0) return ampel('gelb', 'Keine aktiven Anzeigengruppen gefunden.');
    if (a.ohne === 0) return ampel('gruen', 'Alle ' + a.gesamt + ' aktiven Anzeigengruppen haben eine aktive Anzeige.');
    return ampel('rot', a.ohne + ' von ' + a.gesamt + ' aktiven Anzeigengruppen ohne aktive Anzeige - schalten nicht aus.');
  }));

  schreibeSheet(checks, z, waehrung);
}

// ------------------------- Helfer: Ampel-Handling -------------------------
function ampel(farbe, befund) { return { ampel: farbe, befund: befund }; }

function pruefe(name, fn) {
  try {
    var r = fn();
    return { name: name, ampel: r.ampel, befund: r.befund };
  } catch (e) {
    return { name: name, ampel: 'gelb', befund: 'Nicht pruefbar: ' + e };
  }
}

// ------------------------- Datenlader -------------------------
function ladeKampagnenMetriken(z) {
  var q =
    "SELECT campaign.name, metrics.clicks, metrics.cost_micros, metrics.conversions, " +
    "metrics.search_budget_lost_impression_share, metrics.search_rank_lost_impression_share, " +
    "campaign.bidding_strategy_type, campaign.advertising_channel_type " +
    "FROM campaign WHERE campaign.status = 'ENABLED' " +
    "AND segments.date BETWEEN '" + z.von + "' AND '" + z.bis + "'";
  var rows = [];
  var it = AdsApp.search(q);
  while (it.hasNext()) {
    var r = it.next();
    rows.push({
      name: r.campaign.name,
      klicks: Number(r.metrics.clicks) || 0,
      kosten: (Number(r.metrics.costMicros) || 0) / 1e6,
      conv: Number(r.metrics.conversions) || 0,
      // IS-Kennzahlen sind Anteile 0..1 und koennen fehlen (undefined) -> so lassen
      budgetLostIS: r.metrics.searchBudgetLostImpressionShare,
      rankLostIS: r.metrics.searchRankLostImpressionShare,
      strategie: r.campaign.biddingStrategyType,
      kanal: r.campaign.advertisingChannelType
    });
  }
  return rows;
}

function zaehleKampagnenStatus() {
  var aktiv = 0, pausiert = 0;
  var it = AdsApp.search("SELECT campaign.status FROM campaign WHERE campaign.status IN ('ENABLED','PAUSED')");
  while (it.hasNext()) {
    var s = it.next().campaign.status;
    if (s === 'ENABLED') aktiv++; else if (s === 'PAUSED') pausiert++;
  }
  return { aktiv: aktiv, pausiert: pausiert };
}

function suchbegriffAnteilOhneConv(z) {
  var q =
    "SELECT metrics.cost_micros, metrics.conversions FROM search_term_view " +
    "WHERE segments.date BETWEEN '" + z.von + "' AND '" + z.bis + "' AND metrics.clicks > 0";
  var gesamt = 0, ohneConv = 0;
  var it = AdsApp.search(q);
  while (it.hasNext()) {
    var r = it.next();
    var k = (Number(r.metrics.costMicros) || 0) / 1e6;
    var c = Number(r.metrics.conversions) || 0;
    gesamt += k;
    if (c === 0) ohneConv += k;
  }
  return { gesamt: gesamt, ohneConv: ohneConv };
}

function zaehleNegativeKeywords() {
  var q = "SELECT campaign.id FROM campaign_criterion " +
          "WHERE campaign_criterion.type = 'KEYWORD' AND campaign_criterion.negative = true";
  var n = 0;
  var it = AdsApp.search(q);
  while (it.hasNext()) { it.next(); n++; }
  return n;
}

function zaehleKampagnenMitSitelinks() {
  var q = "SELECT campaign.id FROM campaign_asset WHERE campaign_asset.field_type = 'SITELINK'";
  var set = {};
  var it = AdsApp.search(q);
  while (it.hasNext()) { set[it.next().campaign.id] = true; }
  return Object.keys(set).length;
}

function anzeigengruppenOhneAnzeige() {
  // Alle aktiven Anzeigengruppen (in aktiven Kampagnen)
  var alle = {};
  var it1 = AdsApp.search(
    "SELECT ad_group.id FROM ad_group " +
    "WHERE ad_group.status = 'ENABLED' AND campaign.status = 'ENABLED'");
  while (it1.hasNext()) alle[it1.next().adGroup.id] = true;

  // Anzeigengruppen, die mind. eine aktive Anzeige haben
  var mitAnzeige = {};
  var it2 = AdsApp.search(
    "SELECT ad_group.id FROM ad_group_ad " +
    "WHERE ad_group_ad.status = 'ENABLED' AND ad_group.status = 'ENABLED' AND campaign.status = 'ENABLED'");
  while (it2.hasNext()) mitAnzeige[it2.next().adGroup.id] = true;

  var gesamt = Object.keys(alle).length, ohne = 0;
  for (var id in alle) if (!mitAnzeige[id]) ohne++;
  return { gesamt: gesamt, ohne: ohne };
}

// Impression-Share-Verlust auswerten (Durchschnitt ueber Kampagnen mit Wert)
function isVerlustCheck(metriken, feld, ursache) {
  if (!metriken) throw 'Kampagnendaten fehlen';
  var summe = 0, anzahl = 0;
  for (var i = 0; i < metriken.length; i++) {
    var v = metriken[i][feld];
    if (typeof v === 'number' && !isNaN(v)) { summe += v; anzahl++; } // fehlende Werte ueberspringen
  }
  if (anzahl === 0) return ampel('gelb', 'Kein Impression-Share-Wert verfuegbar (z. B. keine Suchkampagnen).');
  var pct = (summe / anzahl) * 100; // Anteil -> Prozent
  var txt = 'Durchschnitt ' + pct.toFixed(0) + '% verlorene Impressionen (' + ursache + '), ueber ' + anzahl + ' Kampagne(n).';
  if (pct >= KONFIG.IS_VERLUST_ROT) return ampel('rot', txt);
  if (pct >= KONFIG.IS_VERLUST_GELB) return ampel('gelb', txt);
  return ampel('gruen', txt);
}

// ------------------------- Sheet schreiben -------------------------
function schreibeSheet(checks, z, waehrung) {
  var ss = SpreadsheetApp.create('Konto-Schnellaudit ' + z.von + ' bis ' + z.bis);
  var sh = ss.getActiveSheet();
  sh.setName('Ampel-Audit');

  var kopf = ['Ampel', 'Check', 'Kurzbefund'];
  var daten = [kopf];
  var rot = 0, gelb = 0, gruen = 0;
  for (var i = 0; i < checks.length; i++) {
    var c = checks[i];
    daten.push([symbol(c.ampel), c.name, c.befund]);
    if (c.ampel === 'rot') rot++; else if (c.ampel === 'gelb') gelb++; else gruen++;
  }

  sh.getRange(1, 1, daten.length, 3).setValues(daten);
  sh.getRange(1, 1, 1, 3).setFontWeight('bold');
  sh.setFrozenRows(1);

  // Zeilen einfaerben nach Ampel
  for (var j = 0; j < checks.length; j++) {
    sh.getRange(j + 2, 1, 1, 3).setBackground(farbe(checks[j].ampel));
  }
  sh.setColumnWidth(1, 60);
  sh.setColumnWidth(2, 280);
  sh.setColumnWidth(3, 560);

  var foot = daten.length + 2;
  sh.getRange(foot, 1).setValue(
    'Konto: ' + AdsApp.currentAccount().getCustomerId() + ' · Zeitraum: ' + z.von + ' bis ' + z.bis +
    ' · Waehrung: ' + waehrung + ' · Ergebnis: gruen ' + gruen + ' / gelb ' + gelb + ' / rot ' + rot);
  sh.getRange(foot + 1, 1).setValue(
    'Rote Ampeln? Kostenlose Ersteinschaetzung: hallo@promptedaudits.de - kein Abo.');

  Logger.log('=== KONTO-SCHNELLAUDIT fertig ===');
  Logger.log('Ampeln: gruen ' + gruen + ' · gelb ' + gelb + ' · rot ' + rot);
  Logger.log('SHEET-URL: ' + ss.getUrl());
}

function farbe(a) { return a === 'gruen' ? '#d9ead3' : (a === 'gelb' ? '#fff2cc' : '#f4cccc'); }
function symbol(a) { return a === 'gruen' ? 'GRUEN' : (a === 'gelb' ? 'GELB' : 'ROT'); }

function zeitraum(tage) {
  var tz = AdsApp.currentAccount().getTimeZone();
  var ende = new Date();
  var start = new Date();
  start.setDate(ende.getDate() - (tage - 1));
  return {
    von: Utilities.formatDate(start, tz, 'yyyy-MM-dd'),
    bis: Utilities.formatDate(ende, tz, 'yyyy-MM-dd')
  };
}

Neue Skripte zuerst bekommen

Wir bauen laufend neue Gratis-Tools — für Google Ads, GA4 und mehr. Tragen Sie Ihre E-Mail ein, und Sie hören von uns, sobald das nächste fertig ist. Nicht öfter, kein Abo, jederzeit abbestellbar.

Nur Benachrichtigung über neue Tools. Details in der Datenschutzerklärung.
✓ Notiert — wir melden uns, sobald es Neues gibt.

Alle Skripte sind reines Reporting — sie lesen nur und verändern nichts an Ihrem Konto. Ohne Gewähr; prüfen Sie Skripte vor dem Ausführen immer selbst.

Keine Zeit zum Selbermachen?

Wir finden Ihre erste Geldverschwendung — kostenlos.

Die Skripte zeigen die roten Ampeln. Wenn Sie wollen, schauen wir mit echten Daten genauer hin und sagen Ihnen in Klartext, was zuerst zu tun ist.

Kostet Sie:0 € — wirklich kostenlos
Dauert:2 Minuten Zugang freigeben
Danach:kein Verkaufsdruck, kein Abo
Analyse anfordern
Mit dem Absenden stimmen Sie zu, dass wir Sie zu Ihrer Anfrage kontaktieren. Keine Weitergabe an Dritte. Details: Datenschutzerklärung.
✓

Anfrage ist raus.

Wir melden uns innerhalb von 1–2 Werktagen mit Ihrer ersten konkreten Geldverschwendung.