---
name: shopify-clientes-vip
description: "Identifica a los clientes que más compran (el N % superior por gasto total o por frecuencia de compra), exporta la lista de candidatos VIP y, si quieres, les pone la etiqueta VIP. Úsala para armar segmentos de fidelización o campañas exclusivas."
---

## Propósito
Ordena a los clientes por gasto total y frecuencia de compra, identifica el N % superior (por valor, por frecuencia o por ambos) y genera un CSV con los candidatos VIP. Opcionalmente aplica una etiqueta VIP a los clientes que califican con `customerUpdate`. Sirve para armar segmentos de fidelización, dar prioridad a una atención preferencial o lanzar campañas de acceso exclusivo. El gasto total y la cantidad de pedidos salen directamente de los acumulados de clientes de Shopify: no necesitas un CRM externo.

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

## Parámetros

| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|-----------|------|-------------|----------------|-------------|
| store | string | sí | — | Dominio de la tienda (p. ej., mitienda.myshopify.com) |
| format | string | no | human | Formato de salida: `human` o `json` |
| dry_run | bool | no | true | Muestra la lista VIP sin aplicar etiquetas |
| rank_by | string | no | spend | Criterio para ordenar: `spend` (gasto total), `frequency` (cantidad de pedidos) o `both` (puntaje combinado) |
| top_pct | float | no | 5 | Percentil superior que califica como VIP (p. ej., 5 = el 5 % superior) |
| min_orders | integer | no | 2 | Pedidos mínimos en toda su historia para ser elegible |
| min_spend | float | no | 0 | Gasto total mínimo (en la moneda de la tienda) para ser elegible |
| tag_customers | bool | no | false | Si es true, aplica la etiqueta VIP a los clientes que califican con `customerUpdate` |
| tag | string | no | vip | Etiqueta que se aplica cuando `tag_customers: true` |

## Seguridad

> ⚠️ Con `tag_customers: true`, el paso 4 ejecuta mutaciones `customerUpdate` que modifican la lista de etiquetas de los clientes. Las etiquetas quedan hasta que se quiten a mano. Ejecuta primero con `dry_run: true` para confirmar la lista VIP y los umbrales para calificar. El valor predeterminado es `dry_run: true`: para aplicar etiquetas tienes que poner explícitamente `dry_run: false` y `tag_customers: true`.

## Pasos

1. **OPERACIÓN:** `customers` — consulta
   **Entradas:** `first: 250`, `query: "orders_count:>=<min_orders>"`, campos `id`, `displayName`, `defaultEmailAddress { emailAddress }`, `numberOfOrders`, `amountSpent { amount currencyCode }`, `tags`, cursor de paginación
   **Resultado esperado:** Todos los clientes que cumplen el umbral de `min_orders`; pagina hasta `hasNextPage: false`

2. **OPERACIÓN:** `orders` — consulta (solo cuando se ordena por frecuencia, para anotar la compra más reciente)
   **Entradas:** Para cada candidato principal: `query: "customer_id:<id>"`, `first: 1`, `sortKey: CREATED_AT`, `reverse: true`
   **Resultado esperado:** El pedido más reciente de cada candidato, para anotarlo en la exportación

3. Filtra a `amountSpent.amount >= min_spend`. Puntúa a cada cliente: `spend` → gasto; `frequency` → pedidos; `both` → 0.6 × gasto normalizado + 0.4 × frecuencia normalizada. Toma el `top_pct%` superior.

4. **OPERACIÓN:** `customerUpdate` — mutación (solo si `tag_customers: true` y `dry_run: false`)
   **Entradas:** `input: { id: <customer_id>, tags: [...existing_tags, <tag>] }`
   **Resultado esperado:** `customer.id`, `customer.tags`, `userErrors`

## Operaciones GraphQL

```graphql
# customers:query — validada con api_version 2025-01
query VIPCandidateCustomers($first: Int!, $after: String, $query: String) {
  customers(first: $first, after: $after, query: $query) {
    edges {
      node {
        id
        displayName
        firstName
        lastName
        defaultEmailAddress {
          emailAddress
        }
        numberOfOrders
        amountSpent {
          amount
          currencyCode
        }
        tags
        createdAt
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

```graphql
# orders:query — validada con api_version 2025-01
query VIPLastOrder($query: String!) {
  orders(first: 1, query: $query, sortKey: CREATED_AT, reverse: true) {
    edges {
      node {
        id
        name
        createdAt
        totalPriceSet {
          shopMoney { amount currencyCode }
        }
      }
    }
  }
}
```

```graphql
# customerUpdate:mutation — validada con api_version 2025-01
mutation CustomerUpdateVipTag($input: CustomerInput!) {
  customerUpdate(input: $input) {
    customer {
      id
      displayName
      tags
    }
    userErrors {
      field
      message
    }
  }
}
```

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Encuentra a tus clientes VIP         ║
║  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>
```

Si `dry_run: true`, antepón `[SIMULACIÓN]` a cada paso de mutación y no lo ejecutes.

**Al terminar**, muestra:

Para `format: human` (predeterminado):
```
══════════════════════════════════════════════
REPORTE DE CLIENTES VIP
  Clientes revisados:      <n>
  Elegibles (≥ mínimo):    <n>
  VIP (<pct>% superior):   <n>
  Gasto mínimo VIP:        <monto>
  Pedidos mínimos VIP:     <n>
  Clientes etiquetados:    <n>  (o "omitido — dry_run")

  Top 10 VIP por <rank_by>:
    <nombre>  Gasto: <monto>  Pedidos: <n>  Último: <fecha>
  Archivo: vip_customers_<fecha>.csv
══════════════════════════════════════════════
```

Los montos van en la moneda de la tienda.

Para `format: json`, muestra:
```json
{
  "skill": "shopify-clientes-vip",
  "store": "<dominio>",
  "dry_run": true,
  "rank_by": "spend",
  "outcome": {
    "customers_scanned": 0,
    "eligible": 0,
    "vips_identified": 0,
    "threshold_spend": 0,
    "customers_tagged": 0,
    "errors": 0,
    "output_file": "vip_customers_<fecha>.csv"
  }
}
```

## Formato de salida
Archivo CSV `vip_customers_<AAAA-MM-DD>.csv` con las columnas:
`customer_id`, `name`, `email`, `lifetime_spend`, `currency`, `orders_count`, `last_order_date`, `composite_score`, `rank`, `tag_applied`

## 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 |
| `userErrors` en customerUpdate | Cliente no encontrado o conflicto de etiquetas | Regístralo, omítelo y continúa |
| Menos elegibles que el `top_pct%` | Base de clientes pequeña | Baja `min_orders` o `min_spend` |
| Tiendas con varias monedas | `currencyCode` varía | Convierte a la moneda principal de la tienda antes de ordenar |

## Buenas prácticas
- Usa `rank_by: both` para equilibrar a los que gastan mucho con los clientes fieles: ordenar solo por gasto puede darle demasiado peso a una única compra grande.
- Vuelve a ejecutarla cada trimestre con una etiqueta fechada (p. ej., `vip-2026-Q2`) para que los VIP que dejaron de comprar salgan de la lista en lugar de acumularse para siempre.
- Combínala con `shopify-recuperar-clientes-inactivos`: los VIP que se vuelven inactivos deberían marcarse para una reactivación prioritaria.
- Ejecuta primero con `dry_run: true`; revisa el gasto mínimo resultante para confirmar que el corte coincide con tu definición de VIP.
