---
name: shopify-costo-adquisicion-por-fuente
description: "Solo lectura: estima el costo de adquisición de clientes (CAC) por fuente de tráfico, cruzando los pedidos de clientes nuevos por página de llegada o referente con la inversión publicitaria que indiques. Úsala cuando quieras saber qué canal te trae clientes más baratos."
---

## Propósito
Estima el costo de adquisición de clientes (CAC) de cada fuente de tráfico combinando el número de pedidos de clientes nuevos atribuidos a una página de llegada o a un referente con la inversión publicitaria por fuente que tú indiques. El resultado responde: «por cada unidad de la moneda de la tienda invertida en la fuente X, ¿cuántos clientes nuevos conseguimos y a qué costo por cliente?». Solo lectura: no ejecuta mutaciones. Da la base de datos para redistribuir el presupuesto de medios pagos.

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

## Parámetros

| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|-----------|------|-------------|----------------|-------------|
| store | string | sí | — | Dominio de la tienda (p. ej., tu-tienda.myshopify.com) |
| days_back | integer | no | 30 | Ventana de días hacia atrás de los pedidos que se atribuyen |
| ad_spend | object | no | {} | Mapa de nombre de fuente → inversión en la moneda de la tienda, p. ej. `{"google": 4500, "meta": 3200, "tiktok": 1800}` |
| new_customers_only | bool | no | true | Cuenta como «adquiridos» solo a los clientes en su primer pedido |
| min_orders_per_source | integer | no | 5 | Mínimo de pedidos para que una fuente aparezca en el reporte |
| format | string | no | human | Formato de salida: `human` o `json` |

## Seguridad

> ℹ️ Skill de solo lectura: no ejecuta mutaciones. Puedes correrla en cualquier momento. Los valores de inversión publicitaria los entregas tú; esta skill no los trae de ninguna plataforma de anuncios.

## Pasos

1. **OPERACIÓN:** `orders` — consulta
   **Entradas:** `query: "created_at:>='<NOW - days_back days>'"`, `first: 250`, selecciona `customer { id, numberOfOrders }`, `customerJourneySummary { firstVisit { landingPage referrerUrl source } }`, `landingPageUrl`, `referrerUrl`, `totalPriceSet`, cursor de paginación
   **Resultado esperado:** Todos los pedidos de la ventana con su atribución de referente y de cliente; pagina hasta `hasNextPage: false`

2. Agrupa los pedidos por fuente normalizada. Orden de resolución:
   - `customerJourneySummary.firstVisit.source`, si existe
   - Si no, toma el dominio de `referrerUrl`
   - Si no, lee los parámetros UTM de `landingPageUrl` (utm_source)
   - Si no, clasifícalo como `direct`

3. Si `new_customers_only: true`, descarta los pedidos donde `customer.numberOfOrders > 1` para que cada cliente se cuente una sola vez

4. Suma por fuente: `orders_count`, `new_customers_count`, `revenue_attributed`

5. Cruza con el mapa `ad_spend`: `cac = ad_spend[source] / new_customers_count`. Las fuentes sin datos de inversión reportan `cac: null` (orgánicas o sin atribución)

6. Filtra para dejar solo las fuentes con `orders_count >= min_orders_per_source`

## 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
        customerJourneySummary {
          firstVisit {
            landingPage
            referrerUrl
            source
            sourceType
            utmParameters {
              source
              medium
              campaign
            }
          }
        }
        totalPriceSet {
          shopMoney {
            amount
            currencyCode
          }
        }
        customer {
          id
          numberOfOrders
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

## Seguimiento de la sesión

**Claude DEBE mostrar lo siguiente en cada etapa. Es obligatorio.**

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Costo de adquisición por fuente      ║
║  Tienda: <dominio de la tienda>              ║
║  Inicio: <AAAA-MM-DD HH:MM UTC>              ║
╚══════════════════════════════════════════════╝
```

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

**Al terminar**, muestra:

Para `format: human` (predeterminado):
```
══════════════════════════════════════════════
CAC POR FUENTE  (<days_back> días)
  Pedidos analizados:            <n>
  Clientes nuevos adquiridos:    <n>
  Inversión total (indicada):    <monto>
  CAC combinado:                 <monto>

  Por fuente (de menor a mayor CAC):
    google      Clientes: <n>  Inversión: <n>   CAC: <n>
    meta        Clientes: <n>  Inversión: <n>   CAC: <n>
    direct      Clientes: <n>  Inversión: —     CAC: orgánico
    referral    Clientes: <n>  Inversión: —     CAC: orgánico

  Archivo: cac_by_source_<fecha>.csv
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-costo-adquisicion-por-fuente",
  "store": "<dominio>",
  "period_days": 30,
  "orders_analyzed": 0,
  "new_customers": 0,
  "blended_cac": 0,
  "currency": "<moneda de la tienda>",
  "by_source": [],
  "output_file": "cac_by_source_<fecha>.csv"
}
```

## Formato de salida
Archivo CSV `cac_by_source_<AAAA-MM-DD>.csv` con las columnas:
`source`, `orders_count`, `new_customers_count`, `revenue_attributed`, `ad_spend`, `cac`, `currency`

## 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 |
| `ad_spend` vacío | No se indicó inversión | Reporta pedidos y clientes por fuente con `cac: null` |
| Falta `customerJourneySummary` | Pedidos antiguos o checkout como invitado | Recurre a `referrerUrl` → `landingPageUrl` → `direct` |
| Todos los pedidos vienen de `direct` | No se capturó ningún referente | Probablemente el seguimiento está mal configurado: muéstralo como advertencia |

## Buenas prácticas
- Entrega la inversión publicitaria de la **misma ventana** que `days_back`: si las ventanas no coinciden, las cifras de CAC engañan.
- Combínala con `shopify-cohortes-clientes` para confirmar que las fuentes con CAC bajo también traen clientes con alto valor de vida (LTV).
- Las fuentes que aparecen como `direct` suelen esconder fugas de atribución: revisa el etiquetado UTM y las políticas de referente antes de sacar conclusiones.
- Trata el resultado como un **CAC estimado**: la atribución de primer contacto de Shopify no captura recorridos entre varios dispositivos, así que las fuentes que dependen de visualizaciones sin clic (display, video) quedan subcontadas.
- Vuelve a correrla cada semana para detectar cambios en el CAC antes de que las campañas dejen de ser rentables.
