# Auditoría técnica de cuentas Google Ads

## Checklist técnico de auditoría

Una auditoría técnica verifica que la infraestructura de medición está
correctamente configurada — no evalúa la estrategia de campañas.

### Conversiones

- [ ] **Conversiones primarias configuradas**: al menos una acción de conversión
  marcada como "Primaria" en la cuenta. Las conversiones secundarias no se usan
  en las estrategias de puja automática.
- [ ] **Enhanced Conversions activadas**: en Configuración de conversiones,
  "Enhanced conversions for web" debe estar habilitado con hash SHA-256.
- [ ] **Auto-tagging habilitado**: en Configuración de la cuenta, permite que
  Google Ads agregue `gclid` a las URLs de destino para importación a GA4.
- [ ] **Sin conversiones duplicadas**: verificar que el mismo evento de compra
  no esté configurado dos veces (una en GA4-importada y otra en etiqueta directa).
- [ ] **Ventana de conversión correcta**: campañas de búsqueda usan 30 días;
  campañas de display o video pueden requerir ventanas más largas (90 días).

### Audiencias y remarketing

- [ ] **Etiqueta de remarketing activa**: Google Ads Tag o GA4 Audience deben
  estar recibiendo datos (verificar en Audience Manager > Fuentes de audiencia).
- [ ] **Audiencias de remarketing creadas**: al menos "Todos los visitantes",
  "Visitantes de producto" y "Compradores anteriores" para campañas RLSA.
- [ ] **Customer Match habilitado**: si se usa lista de clientes, la cuenta debe
  tener historial suficiente y cumplimiento de políticas.

### Shopping (si aplica)

- [ ] **Feed de productos aprobado** en Merchant Center: sin errores de
  disponibilidad, precio o imagen. Los items rechazados no aparecen en anuncios.
- [ ] **Vinculación Merchant Center — Google Ads**: confirmada en ambas cuentas.
- [ ] **Etiquetas personalizadas configuradas**: para segmentación por margen,
  temporada o prioridad de inventario.

### Estructura técnica de la cuenta

- [ ] **Naming convention consistente**: campañas, ad groups y keywords siguen
  una nomenclatura que permite filtrar y reportar programáticamente.
- [ ] **UTM parameters en URLs finales**: `utm_source=google&utm_medium=cpc&utm_campaign={campaignid}`
  para atribución correcta en GA4 sin depender solo del auto-tagging.

---

## Métricas de calidad de cuenta

Estas métricas indican la salud técnica de la implementación, no el
desempeño de negocio:

| Métrica | Objetivo | Señal de problema |
|---------|----------|------------------|
| Quality Score promedio (búsqueda) | ≥ 7/10 | < 5 indica relevancia baja o landing page deficiente |
| Impression Share de marca | ≥ 85% | < 70% indica competencia capturando tráfico de marca |
| Invalid click rate | < 2% | > 5% indica fraude de clics o tráfico de baja calidad |
| Discrepancia GA4 vs Google Ads | < 15% | > 15% indica problema de tracking o atribución |
| Conversiones con valor cero | 0% | Cualquier conversión primaria sin valor = ROAS incalculable |

### Query para Quality Score promedio

```python
def obtener_quality_score_promedio(customer_id: str) -> dict:
    """Obtiene Quality Score promedio de keywords activas."""
    ga_service = client.get_service("GoogleAdsService")

    query = """
        SELECT
            ad_group_criterion.keyword.text,
            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,
            metrics.impressions
        FROM keyword_view
        WHERE campaign.status = 'ENABLED'
            AND ad_group.status = 'ENABLED'
            AND ad_group_criterion.status = 'ENABLED'
            AND ad_group_criterion.quality_info.quality_score > 0
            AND segments.date DURING LAST_30_DAYS
    """

    response = ga_service.search(customer_id=customer_id, query=query)

    scores = []
    impresiones_total = 0
    suma_ponderada = 0

    for row in response:
        qs = row.ad_group_criterion.quality_info.quality_score
        imp = row.metrics.impressions
        scores.append(qs)
        suma_ponderada += qs * imp
        impresiones_total += imp

    if not scores:
        return {"error": "sin_keywords_con_quality_score"}

    return {
        "promedio_simple": round(sum(scores) / len(scores), 1),
        "promedio_ponderado_impresiones": round(
            suma_ponderada / impresiones_total, 1
        ) if impresiones_total > 0 else 0,
        "total_keywords": len(scores),
        "distribucion": {
            "excelente_8_10": sum(1 for s in scores if s >= 8),
            "bueno_6_7": sum(1 for s in scores if 6 <= s <= 7),
            "bajo_1_5": sum(1 for s in scores if s <= 5),
        },
    }
```

---

## Herramientas de diagnóstico

### Google Tag Assistant

Extensión de Chrome para verificar que la etiqueta de Google Ads está
disparando correctamente en el sitio:

1. Activar grabación en la página objetivo
2. Completar el flujo de conversión (compra, registro, etc.)
3. Verificar que el evento aparece en el historial con los parámetros correctos
4. Confirmar que no hay errores de "Tag not found" o "Tag fired multiple times"

### Google Ads Diagnostics Tool

En la consola de Google Ads: Herramientas > Diagnóstico y vista previa
de anuncios. Permite verificar por qué un anuncio no aparece para una
búsqueda específica (presupuesto agotado, keyword pausada, policy violation).

---

## Reporte automático de auditoría con Python

```python
import json
from dataclasses import dataclass, field

@dataclass
class ResultadoAuditoria:
    customer_id: str
    quality_score: dict = field(default_factory=dict)
    discrepancia_conversiones: dict = field(default_factory=dict)
    candidatos_negative: list = field(default_factory=list)
    alertas: list[str] = field(default_factory=list)

def ejecutar_auditoria_completa(
    customer_id: str,
    ga4_conversiones: float,
    campaign_ids: list[str],
) -> ResultadoAuditoria:
    """
    Ejecuta auditoría técnica completa de una cuenta Google Ads.

    Retorna un ResultadoAuditoria con métricas de calidad,
    discrepancias de conversiones y candidatos a negative keywords.
    """
    resultado = ResultadoAuditoria(customer_id=customer_id)

    # 1. Quality Score
    resultado.quality_score = obtener_quality_score_promedio(customer_id)
    qs_prom = resultado.quality_score.get("promedio_simple", 0)
    if qs_prom < 7:
        resultado.alertas.append(
            f"Quality Score promedio bajo: {qs_prom}/10 (objetivo ≥ 7)"
        )

    # 2. Rendimiento de campañas y discrepancia de conversiones
    campanas = obtener_rendimiento_campanas(customer_id)
    google_ads_conv = sum(c["conversiones"] for c in campanas)
    resultado.discrepancia_conversiones = detectar_discrepancia_conversiones(
        google_ads_conv, ga4_conversiones
    )  # Nota: esta función es async en SKILL.md, adaptar según contexto

    if resultado.discrepancia_conversiones.get("estado") != "ok":
        pct = resultado.discrepancia_conversiones.get("discrepancia_porcentual", 0)
        resultado.alertas.append(
            f"Discrepancia de conversiones: {pct}% (umbral: 15%)"
        )

    # 3. Search terms con desperdicio
    analisis = analizar_search_terms(customer_id, campaign_ids)
    resultado.candidatos_negative = analisis["candidatos_negative"][:10]
    if resultado.candidatos_negative:
        total_desperdicio = sum(
            t["costo_desperdiciado"] for t in resultado.candidatos_negative
        )
        resultado.alertas.append(
            f"{len(resultado.candidatos_negative)} términos candidatos a negative, "
            f"costo desperdiciado estimado: ${total_desperdicio:,.2f}"
        )

    return resultado


def exportar_reporte_json(resultado: ResultadoAuditoria, ruta: str) -> None:
    """Exporta el resultado de la auditoría como JSON."""
    import dataclasses
    with open(ruta, "w", encoding="utf-8") as f:
        json.dump(dataclasses.asdict(resultado), f, ensure_ascii=False, indent=2)
```

### Ejemplo de uso

```python
resultado = ejecutar_auditoria_completa(
    customer_id="1234567890",
    ga4_conversiones=142.0,
    campaign_ids=["111222333", "444555666"],
)

print(f"Alertas encontradas: {len(resultado.alertas)}")
for alerta in resultado.alertas:
    print(f"  - {alerta}")

exportar_reporte_json(resultado, "auditoria_2026_04.json")
```
