---
name: shopify-etiquetas-clientes-masivas
description: "Agrega o quita etiquetas a muchos clientes a la vez, elegidos por filtro o por lista de IDs, con simulación previa. Úsala para cambiar tu sistema de etiquetas, retirar la etiqueta de una campaña o marcar un segmento nuevo."
---

## Propósito
Aplica cambios masivos de etiquetas (agregar, quitar o ambos) a clientes seleccionados con un filtro de búsqueda (p. ej., `total_spent:>=500`, `tag:newsletter`) o con una lista explícita de GID de clientes. Las etiquetas son la forma en que Shopify segmenta a los clientes para descuentos, marketing y flujos de atención; esta skill hace que los cambios en lote sean seguros, se puedan simular antes y queden registrados. Úsala cuando migres de un sistema de etiquetas a otro, cuando retires una etiqueta de una campaña puntual o cuando apliques una etiqueta de segmento nueva que salió de un reporte de análisis.

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

## Parámetros

| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|-----------|------|-------------|----------------|-------------|
| store | texto | sí | — | Dominio de la tienda (p. ej., tu-tienda.myshopify.com) |
| format | texto | no | human | Formato de salida: `human` o `json` |
| dry_run | booleano | no | true | Muestra los clientes que coinciden y los cambios de etiquetas planeados sin ejecutar mutaciones |
| filter | texto | condicional | — | Filtro de búsqueda de clientes (p. ej., `tag:newsletter`, `total_spent:>=500`); obligatorio si se omite `customer_ids` |
| customer_ids | lista | condicional | — | Lista explícita de GID de clientes; obligatoria si se omite `filter` |
| add_tags | lista | no | [] | Etiquetas por agregar (se suman a las existentes) |
| remove_tags | lista | no | [] | Etiquetas por quitar (se restan de las existentes) |
| mode | texto | no | merge | Modo de escritura de etiquetas: `merge` (aplica las altas y bajas sobre las etiquetas existentes) o `replace` (reemplaza todas las etiquetas solo con `add_tags`) |
| max_customers | entero | no | 1000 | Tope de clientes por ejecución; aborta si el filtro coincide con más clientes que este número |

## Seguridad

> ⚠️ El paso 3 ejecuta una mutación `customerUpdate` por cada cliente del conjunto encontrado. Los cambios de etiquetas son inmediatos y los ven tu equipo y cualquier app que lea las etiquetas de clientes (fidelización, automatización de marketing, segmentación). `mode: replace` sobrescribe por completo las etiquetas existentes: se perderán las etiquetas operativas que se pusieron a mano. Los valores predeterminados son `dry_run: true` y `mode: merge`. Corre siempre primero la simulación, revisa cuántos clientes coinciden y confirma que `add_tags`/`remove_tags` estén bien escritas: en Shopify las etiquetas distinguen entre mayúsculas y minúsculas.

## Pasos

1. **OPERACIÓN:** `customers` — consulta
   **Entradas:** Si hay `filter`: `query: <filter>`, `first: 250` y el cursor de paginación. Si hay `customer_ids`: consulta por lotes con `query: "id:<id1> OR id:<id2> ..."` (en grupos de 25 IDs). Selecciona `id`, `displayName`, `defaultEmailAddress { emailAddress }`, `tags`.
   **Resultado esperado:** Lista de clientes con sus etiquetas actuales. Aborta si `match_count > max_customers`.

2. Para cada cliente encontrado, calcula el conjunto final de etiquetas:
   - Si `mode: merge`: `target = (existing ∪ add_tags) \ remove_tags`
   - Si `mode: replace`: `target = add_tags` (se ignora `remove_tags`)
   Omite al cliente si `target == existing` (no hay nada que cambiar).

3. **OPERACIÓN:** `customerUpdate` — mutación
   **Entradas:** Para cada cliente con diferencias: `input: { id: <customer_id>, tags: <target_tag_array> }`
   **Resultado esperado:** `customer.id`, `customer.tags`, `userErrors`; registra los fallos

## Operaciones GraphQL

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

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

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Etiqueta clientes en lote            ║
║  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>
```

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):
```
══════════════════════════════════════════════
RESULTADO DE LA ACTUALIZACIÓN DE ETIQUETAS
  Filtro:                    <filtro o "<n> IDs explícitos">
  Modo:                      <merge|replace>
  Etiquetas por agregar:     <lista>
  Etiquetas por quitar:      <lista>
  Clientes encontrados:      <n>
  Clientes actualizados:     <n>  (u "omitido: simulación")
  Sin cambios (ya estaban así): <n>
  Errores:                   <n>
  Archivo:                   bulk_tag_update_<fecha>.csv
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-etiquetas-clientes-masivas",
  "store": "<dominio>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": true,
  "mode": "merge",
  "outcome": {
    "matched": 0,
    "updated": 0,
    "noop": 0,
    "errors": 0,
    "add_tags": [],
    "remove_tags": [],
    "output_file": "bulk_tag_update_<fecha>.csv"
  }
}
```

## Formato de salida
Archivo CSV `bulk_tag_update_<YYYY-MM-DD>.csv` con las columnas:
`customer_id`, `name`, `email`, `previous_tags`, `tags_added`, `tags_removed`, `new_tags`, `status`

La columna `status` indica `updated`, `noop` o `error: <mensaje>`.

## 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` | Entrada inválida o cliente de solo lectura | Registra el error, omite al cliente y continúa |
| `match_count > max_customers` | El filtro es demasiado amplio | Afina el filtro o sube `max_customers` a propósito |
| `filter` y `customer_ids` vacíos | No hay selección | Aborta con un error de parámetros |
| Etiqueta vacía | Entrada con solo espacios | Quita los espacios y omite los valores vacíos |
| Un valor de `remove_tags` con mayúsculas distintas | Las etiquetas distinguen entre mayúsculas y minúsculas | Vuelve a correrla con la escritura exacta |

## Buenas prácticas
- Corre siempre primero con `dry_run: true`: revisa cuántos clientes coinciden y una muestra de los cambios `previous_tags` → `new_tags` antes de aplicarlos.
- Prefiere `mode: merge` (el predeterminado) para casi todo; `mode: replace` solo tiene sentido cuando vas a reiniciar por completo el sistema de etiquetas de un cliente y tienes un respaldo verificado de sus etiquetas anteriores.
- Las etiquetas distinguen entre mayúsculas y minúsculas: en Shopify, `VIP` y `vip` son etiquetas distintas. Unifica la escritura en tu sistema de etiquetas.
- Para segmentos operativos que se repiten, usa nombres de etiqueta con fecha (p. ej., `cohort-2026-Q2`), así podrás identificar las cohortes históricas a medida que se acumulan etiquetas nuevas.
- Combínala con `shopify-clientes-vip` o `shopify-niveles-gasto-clientes` para llevar a la ficha del cliente las etiquetas de segmento que salen de tus análisis.
- Usa `remove_tags` como limpieza al terminar una campaña: dejar etiquetas viejas de campañas ensucia la segmentación en tus herramientas de marketing.
- Cuando pasas `customer_ids` directamente (p. ej., desde el CSV de otra skill), la ejecución es totalmente predecible: no hay ambigüedad de filtro.
