---
name: shopify-unir-clientes
description: "Une registros de clientes duplicados: usa la unión nativa de Shopify cuando se puede y, si no, pasa las etiquetas y notas del duplicado al registro que se conserva. Úsala cuando tengas dos fichas para la misma persona y quieras dejar una sola."
---

## Propósito
Resuelve los registros de clientes duplicados que detecta `shopify-clientes-duplicados`. Cuando la Admin API de Shopify ofrece `customerMerge` (una unión nativa que mueve pedidos, direcciones, suscripciones y metacampos al registro principal), esta skill la llama directamente. Cuando `customerMerge` no está disponible o falla para ese par de cuentas, la skill recurre a consolidar en el registro principal los datos que sirven para buscar (etiquetas, notas, consentimiento de marketing) mediante `customerUpdate`, y luego deja una anotación clara en el registro duplicado para que el equipo termine la unión a mano en el admin de Shopify.

En esta skill, el registro **principal** (winner) es el que se conserva y el registro **duplicado** (loser) es el que se integra en él.

## 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 (scopes) de API: `read_customers`, `write_customers`

## Parámetros

| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|-----------|------|-------------|----------------|-------------|
| store | string | sí | — | Dominio de la tienda (p. ej., tu-tienda.myshopify.com) |
| format | string | no | human | Formato de salida: `human` o `json` |
| dry_run | bool | no | true | Muestra el plan de unión sin ejecutar las mutaciones |
| customer_winner_id | string | sí | — | GID del registro de cliente que se conserva (p. ej., `gid://shopify/Customer/12345`) |
| customer_loser_id | string | sí | — | GID del registro de cliente que se une al principal |
| use_native_merge | bool | no | true | Intenta primero con `customerMerge`; si falla o no está disponible, consolida con `customerUpdate` |
| merge_tags | bool | no | true | Suma las etiquetas del duplicado a las del principal |
| merge_note | bool | no | true | Agrega la nota del duplicado a la del principal (con la fecha como prefijo) |
| annotate_loser | bool | no | true | Escribe una nota en el registro duplicado que apunta al GID del principal, para la limpieza manual |

## Seguridad

> ⚠️ Los pasos 2 a 4 ejecutan mutaciones que modifican registros de clientes. `customerMerge` es irreversible: una vez que los pedidos y direcciones pasan al registro principal, el duplicado se cierra y no se puede volver a separar. Ejecuta primero con `dry_run: true` para confirmar los GID del principal y del duplicado y el plan de unión. El valor predeterminado es `dry_run: true`. Antes de aplicar cambios, verifica siempre con el resultado de `shopify-clientes-duplicados` que ambos registros son de la misma persona (mismo correo, teléfono y nombre). No unas un cliente con suscripciones activas o pedidos sin preparar sin confirmar que los sistemas conectados seguirán al nuevo GID del titular.

## Pasos

1. **OPERACIÓN:** `customer` — consulta (se llama dos veces: principal y duplicado)
   **Entradas:** `id: <customer_id>`, selecciona `id`, `displayName`, `firstName`, `lastName`, `defaultEmailAddress { emailAddress }`, `phone`, `tags`, `note`, `numberOfOrders`, `amountSpent`, `emailMarketingConsent { marketingState }`, `smsMarketingConsent { marketingState }`, `addresses(first: 25) { id }`, `createdAt`
   **Resultado esperado:** Los datos de identidad completos de ambos registros; detente si alguno de los GID no existe

2. **OPERACIÓN:** `customerMerge` — mutación (solo si `use_native_merge: true` y no es `dry_run`)
   **Entradas:** `customerOneId: <customer_winner_id>`, `customerTwoId: <customer_loser_id>`, `overrideFields`: prioriza el nombre, correo, teléfono, idioma y consentimiento de marketing del principal
   **Resultado esperado:** `job.id` (la unión se procesa de forma asíncrona), `userErrors`. Si `userErrors` indica que la unión no se admite para este par (B2B, titular de tarjeta de regalo, suscriptor, etc.), sigue con la alternativa del paso 3.

3. **OPERACIÓN:** `customerUpdate` — mutación (principal) — camino alternativo o cuando `use_native_merge: false`
   **Entradas:** `input.id: <customer_winner_id>`, `input.tags: <unión de winner.tags y loser.tags>` (solo si `merge_tags`), `input.note: <winner.note + "\n[AAAA-MM-DD] Unido desde <loser_email>:\n" + loser.note>` (solo si `merge_note`)
   **Resultado esperado:** `customer.id`, `customer.tags`, `customer.note`, `userErrors`

4. **OPERACIÓN:** `customerUpdate` — mutación (duplicado) — solo si `annotate_loser: true`
   **Entradas:** `input.id: <customer_loser_id>`, `input.note: "<nota existente>\n[AAAA-MM-DD] DUPLICADO — destino de la unión: <customer_winner_id>. Ciérralo a mano en el admin de Shopify después de revisar los pedidos."`, `input.tags: <existentes + ["duplicate", "merged-loser"]>`
   **Resultado esperado:** `customer.id`, `customer.tags`, `customer.note`, `userErrors`

## Operaciones GraphQL

```graphql
# customer:query — validada con api_version 2025-01
query CustomerForMerge($id: ID!) {
  customer(id: $id) {
    id
    displayName
    firstName
    lastName
    defaultEmailAddress { emailAddress }
    phone
    tags
    note
    numberOfOrders
    amountSpent { amount currencyCode }
    emailMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
    smsMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
    addresses(first: 25) { id address1 city provinceCode countryCodeV2 zip }
    createdAt
  }
}
```

```graphql
# customerMerge:mutation — validada con api_version 2025-01
mutation CustomerMerge(
  $customerOneId: ID!
  $customerTwoId: ID!
  $overrideFields: CustomerMergeOverrideFields
) {
  customerMerge(
    customerOneId: $customerOneId
    customerTwoId: $customerTwoId
    overrideFields: $overrideFields
  ) {
    job { id done }
    resultingCustomerId
    userErrors { field message code }
  }
}
```

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

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Une clientes duplicados              ║
║  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):
```
══════════════════════════════════════════════
RESULTADO DE LA UNIÓN DE CLIENTES
  Principal:              <nombre> (<correo>)  Pedidos: <n>  Gastado: <n>
  Duplicado:              <nombre> (<correo>)  Pedidos: <n>  Gastado: <n>
  Camino usado:           <nativo|alternativo|omitido>
  Tarea de unión:         <id o "n/a">
  Etiquetas consolidadas: <n>
  Nota agregada:          <sí/no>
  Duplicado anotado:      <sí/no>
  Errores:                <n>
  Archivo:                ninguno
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-unir-clientes",
  "store": "<dominio>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": true,
  "winner_id": "<gid>",
  "loser_id": "<gid>",
  "outcome": {
    "path": "native|fallback",
    "merge_job_id": "<id o null>",
    "resulting_customer_id": "<gid o null>",
    "tags_consolidated": 0,
    "note_appended": false,
    "loser_annotated": false,
    "errors": 0,
    "output_file": null
  }
}
```

## Formato de salida
No genera CSV. El resumen de la sesión informa el ID de la tarea de unión, el GID del cliente resultante y el camino que se tomó. Para unir varios pares, ejecuta esta skill una vez por par y guarda la salida JSON.

## 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 |
| userError de `customerMerge`: el cliente tiene suscripciones | Suscripción activa en el duplicado | Cancela la suscripción antes de unir o usa el camino alternativo |
| userError de `customerMerge`: cliente B2B | Registro asociado a una empresa | Usa el camino alternativo; une a mano en el admin de Shopify |
| userError de `customerMerge`: titular de tarjeta de regalo | El duplicado tiene saldo en una tarjeta de regalo | Transfiere la tarjeta de regalo o usa el camino alternativo |
| No se encuentra alguno de los GID | ID equivocado o cliente eliminado | Vuelve a ejecutar `shopify-clientes-duplicados` |
| Tarea de unión pendiente | La unión asíncrona aún no termina | Vuelve a consultar `Job(id)` hasta confirmar `done: true` |

## Buenas prácticas
1. Ejecuta siempre primero `shopify-clientes-duplicados` para confirmar que el par de verdad está duplicado. Un error de clasificación manual no tiene arreglo.
2. Elige con cuidado el registro principal: normalmente el que tiene más pedidos, el correo verificado o el `createdAt` más antiguo. Evita que el registro con consentimiento de marketing quede como duplicado.
3. Ejecuta primero con `dry_run: true`; la vista previa muestra los pedidos y el gasto de ambos registros para que hagas una revisión rápida antes de aplicar cambios.
4. Para limpiezas grandes de duplicados, escribe un script que llame a esta skill una vez por par y la alimente con el CSV de `shopify-clientes-duplicados`; nunca unas varios pares en una sola llamada.
5. Después de una unión nativa, es normal recibir `job.done: false`: Shopify procesa las uniones de forma asíncrona. Vuelve a consultar el ID de la tarea hasta que termine antes de dar por cerrado el duplicado.
6. El camino alternativo (solo consolidar con `customerUpdate`) no mueve los pedidos. Conserva la posibilidad de buscar el registro mediante etiquetas y notas para que una persona termine la unión en el admin de Shopify (Clientes → Unir).
