---
name: shopify-atribucion-utm
description: "Solo lectura: lee los parámetros UTM de la página de llegada de cada pedido y suma ingresos, pedidos y ticket promedio por fuente, medio y campaña. Úsala para saber qué campañas te traen ventas sin depender de otra herramienta."
---

## Propósito
Trae los pedidos recientes, extrae los parámetros UTM que vienen en la URL de llegada (`landingPageUrl`) de cada pedido y suma ingresos, cantidad de pedidos y ticket promedio (AOV) por `utm_source`, `utm_medium` y `utm_campaign`. Arma un reporte de atribución de marketing directamente con los datos de pedidos propios de Shopify, sin necesidad de herramientas externas de analítica. Solo lectura: no ejecuta mutaciones.

## Requisitos
- Sesión autenticada de Shopify CLI: `shopify store auth --store <tu-tienda>.myshopify.com --scopes read_orders`
- Las consultas se ejecutan con `shopify store execute`.
- Permisos de la API: `read_orders`

## Parámetros

| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|-----------|------|-------------|----------------|-------------|
| store | texto | sí | — | Dominio de la tienda (p. ej., tu-tienda.myshopify.com) |
| days_back | entero | no | 30 | Ventana de días hacia atrás de los pedidos por atribuir |
| group_by | texto | no | source | Dimensión principal de agrupación: `source`, `medium`, `campaign` o `source_medium` |
| min_orders | entero | no | 1 | Mínimo de pedidos por grupo para incluirlo en el reporte |
| include_organic | booleano | no | true | Si es `false`, deja fuera del desglose los pedidos sin parámetros UTM |
| format | texto | no | human | Formato de salida: `human` o `json` |

## Seguridad

> ℹ️ Skill de solo lectura: no ejecuta mutaciones. Puedes correrla en cualquier momento. La precisión de la atribución depende de que la tienda online lleve los parámetros UTM hasta el checkout: los pedidos que no pasan por la tienda online (POS, borradores de pedido, suscripciones) no tienen URL de llegada.

## Pasos

1. **OPERACIÓN:** `orders` — consulta
   **Entradas:** `query: "created_at:>='<HOY - days_back días>' financial_status:paid"`, `first: 250`; selecciona `landingPageUrl`, `referrerUrl`, `customerJourneySummary`, `totalPriceSet` y el cursor de paginación
   **Resultado esperado:** Todos los pedidos pagados de la ventana con su URL de llegada; pagina hasta que `hasNextPage: false`

2. Para cada pedido, lee la cadena de parámetros de `landingPageUrl` y extrae `utm_source`, `utm_medium`, `utm_campaign`, `utm_term` y `utm_content`. Los pedidos sin parámetros UTM se agrupan como `(directo/orgánico)` si `include_organic: true`.

3. Totaliza por la dimensión `group_by`: suma la cantidad de pedidos y los ingresos (en la moneda de la tienda) y calcula el ticket promedio = ingresos / pedidos.

4. Ordena los grupos por ingresos, de mayor a menor; descarta los grupos con menos de `min_orders` pedidos.

## Operaciones GraphQL

```graphql
# orders:query — validada con api_version 2025-01
query OrdersWithAttribution($query: String!, $after: String) {
  orders(first: 250, after: $after, query: $query) {
    edges {
      node {
        id
        name
        createdAt
        landingPageUrl
        referrerUrl
        displayFinancialStatus
        totalPriceSet {
          shopMoney {
            amount
            currencyCode
          }
        }
        customerJourneySummary {
          firstVisit {
            landingPage
            source
            sourceType
            referrerUrl
            utmParameters {
              source
              medium
              campaign
              term
              content
            }
          }
          lastVisit {
            landingPage
            source
            sourceType
            utmParameters {
              source
              medium
              campaign
            }
          }
          momentsCount
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

## Seguimiento de la sesión

**Claude DEBE mostrar la siguiente salida en cada etapa. Es obligatorio.**

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Ventas por campaña (UTM)             ║
║  Tienda: <dominio de la tienda>              ║
║  Inicio: <YYYY-MM-DD HH:MM UTC>              ║
╚══════════════════════════════════════════════╝
```

**Después de cada paso**, muestra:
```
[N/TOTAL] <QUERY|MUTATION>  <OperationName>
          → Parámetros: <resumen breve de las entradas clave>
          → Resultado: <cantidad o desenlace>
```

**Al terminar**, muestra:

Para `format: human` (predeterminado):
```
══════════════════════════════════════════════
REPORTE DE ATRIBUCIÓN DE PEDIDOS  (<days_back> días)
  Pedidos atribuidos:      <n>
  Ingresos totales:        $<monto>
  Sin UTM (directo):       <n>  (<pct>%)

  Principales fuentes por ingresos:
    <fuente>     Pedidos: <n>   Ingresos: $<n>   Ticket: $<n>
    <fuente>     Pedidos: <n>   Ingresos: $<n>   Ticket: $<n>
  Archivo: attribution_report_<fecha>.csv
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-atribucion-utm",
  "store": "<dominio>",
  "period_days": 30,
  "group_by": "source",
  "orders_attributed": 0,
  "total_revenue": 0,
  "currency": "USD",
  "groups": [
    { "key": "google", "orders": 0, "revenue": 0, "aov": 0 }
  ],
  "output_file": "attribution_report_<fecha>.csv"
}
```

## Formato de salida
Archivo CSV `attribution_report_<YYYY-MM-DD>.csv` con las columnas:
`group_key`, `utm_source`, `utm_medium`, `utm_campaign`, `orders`, `revenue`, `aov`, `currency`, `pct_of_revenue`

## Manejo de errores
| Error | Causa | Solución |
|-------|-------|----------|
| `THROTTLED` | Se superó el límite de solicitudes de la API | Espera 2 segundos y reintenta hasta 3 veces |
| `landingPageUrl` es nulo | Pedido hecho por POS, como borrador o por suscripción | Agrúpalo como `(directo/orgánico)` y cuéntalo aparte |
| Cadena de parámetros mal formada | Etiquetado UTM manual o incompleto | Ignora el error de lectura, trátalo como directo y registra cuántos hubo |
| Acceso denegado a `customerJourneySummary` | El plan de la tienda no expone este campo | Usa solo la lectura de `landingPageUrl` |

## Buenas prácticas
- Usa `group_by: source_medium` para distinguir el tráfico pago (`google/cpc`) del orgánico (`google/organic`).
- Un porcentaje alto de `(directo/orgánico)` suele indicar que faltan las UTM en las campañas pagas: corrige las URL de las campañas, no el reporte.
- Córrela cada semana durante las campañas activas para seguir los cambios en la atribución, y cada mes para el reporte de rutina.
- Cruza los ingresos de este reporte con la inversión que muestran tus plataformas de anuncios para calcular el ROAS real: esta skill solo aporta el lado de los pedidos (el numerador).
- Para atribución multitoque, muestra también `customerJourneySummary.firstVisit` frente a `lastVisit` para comparar los modelos de primer clic y último clic.
