---
name: shopify-codigos-promocionales-masivos
description: "Crea en lote códigos de descuento únicos para campañas, sorteos o aliados; cada código es un descuento independiente de un solo uso por defecto. Úsala cuando necesites entregar un código distinto a cada persona, por ejemplo en una campaña con influenciadores."
---

## Propósito
Genera N códigos de descuento únicos (p. ej., 100 códigos para un sorteo con un influenciador o 500 para repartir entre aliados) con un prefijo común y valor, límite de uso y vigencia configurables. Cada código se crea como un descuento `DiscountCodeBasic` independiente, para que puedas seguirlo por separado en el admin de Shopify y revocarlo individualmente si se filtra. Uso típico: entregar cada código a un destinatario distinto cuando cada canje debe quedar ligado a una sola persona.

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

## 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 los códigos que se crearían sin ejecutar mutaciones |
| prefix | string | sí | — | Prefijo de todos los códigos generados (p. ej., `SORTEO` produce `SORTEO-A4F2X9`) |
| count | integer | sí | — | Cantidad de códigos únicos que se generan (máximo 1000 por ejecución) |
| value_type | string | sí | — | Tipo de descuento: `percentage`, `fixed_amount` o `free_shipping` |
| value | float | condicional | — | Valor numérico: porcentaje de descuento (1–100) para `percentage`; monto en la moneda de la tienda para `fixed_amount`; se ignora para `free_shipping` |
| usage_limit | integer | no | 1 | Cuántas veces se puede canjear cada código |
| starts_at | string | no | now | Fecha y hora ISO8601 desde la que los códigos son válidos |
| ends_at | string | no | — | Fecha y hora ISO8601 en la que vencen los códigos (omítela para que no venzan) |
| applies_to | string | no | order | A qué aplica el código: `order` (todo el pedido) o `shipping_only` (con el tipo de valor `free_shipping`) |

## Seguridad

> ⚠️ El paso 2 ejecuta una mutación `discountCodeBasicCreate` por cada código generado. Una vez creados, los códigos aparecen de inmediato en el admin de Shopify y se pueden canjear desde `starts_at`. Los códigos no se pueden borrar en masa con la API de Admin: hay que eliminarlos uno por uno con `discountCodeDelete`. Ejecuta con `dry_run: true` para confirmar la cantidad, el prefijo y el valor antes de aplicar los cambios. El valor predeterminado es `dry_run: true`. Para cantidades altas (>100), confirma que `usage_limit` corresponda a lo que quieres: si no necesitas un código único por destinatario, suele ser mejor un solo código con límite alto que N códigos de un solo uso.

## Pasos

1. Genera `count` sufijos aleatorios únicos (6 caracteres alfanuméricos en mayúscula por sufijo). Únelos como `<prefix>-<suffix>`. Valida que no haya duplicados en memoria ni códigos de más de 32 caracteres.

2. **OPERACIÓN:** `discountCodeBasicCreate` — mutación (una llamada por código)
   **Entradas:** `basicCodeDiscount.title: "<prefix> generación masiva <fecha>"`, `basicCodeDiscount.code: <generated_code>`, `basicCodeDiscount.startsAt: <starts_at>`, `basicCodeDiscount.endsAt: <ends_at>`, `basicCodeDiscount.usageLimit: <usage_limit>`, `basicCodeDiscount.appliesOncePerCustomer: true`, `basicCodeDiscount.customerGets.value`: porcentaje, monto fijo o envío gratis según `value_type`, `basicCodeDiscount.customerGets.items.all: true`, `basicCodeDiscount.customerSelection.all: true`
   **Resultado esperado:** `codeDiscountNode.id`, `codeDiscountNode.codeDiscount.codes.edges[0].node.code`, `userErrors`

## Operaciones GraphQL

```graphql
# discountCodeBasicCreate:mutation — validada con api_version 2025-01
mutation PromoCodeCreate($basicCodeDiscount: DiscountCodeBasicInput!) {
  discountCodeBasicCreate(basicCodeDiscount: $basicCodeDiscount) {
    codeDiscountNode {
      id
      codeDiscount {
        ... on DiscountCodeBasic {
          title
          startsAt
          endsAt
          usageLimit
          appliesOncePerCustomer
          codes(first: 1) {
            edges {
              node {
                code
              }
            }
          }
          customerGets {
            value {
              ... on DiscountPercentage {
                percentage
              }
              ... on DiscountAmount {
                amount {
                  amount
                  currencyCode
                }
              }
              ... on DiscountOnQuantity {
                effect {
                  ... on DiscountPercentage {
                    percentage
                  }
                }
              }
            }
          }
        }
      }
    }
    userErrors {
      field
      message
      code
    }
  }
}
```

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Crea códigos de descuento en lote    ║
║  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):
```
══════════════════════════════════════════════
GENERACIÓN MASIVA DE CÓDIGOS PROMOCIONALES
  Prefijo:              <prefijo>
  Códigos solicitados:  <n>
  Códigos creados:      <n>  (o "omitido — dry_run")
  Tipo de valor:        <tipo>
  Valor:                <monto>
  Límite de uso/código: <n>
  Vigencia:             <starts_at> → <ends_at>
  Errores:              <n>
  Archivo:              promo_codes_<prefijo>_<fecha>.csv
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-codigos-promocionales-masivos",
  "store": "<dominio>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": true,
  "prefix": "<prefijo>",
  "outcome": {
    "codes_requested": 0,
    "codes_created": 0,
    "value_type": "<tipo>",
    "value": 0,
    "usage_limit_per_code": 1,
    "starts_at": "<ISO8601>",
    "ends_at": "<ISO8601 o null>",
    "errors": 0,
    "output_file": "promo_codes_<prefijo>_<fecha>.csv"
  }
}
```

## Formato de salida
Archivo CSV `promo_codes_<prefijo>_<AAAA-MM-DD>.csv` con las columnas:
`code`, `discount_node_id`, `value_type`, `value`, `usage_limit`, `starts_at`, `ends_at`, `created_at`, `status`

## 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.code: TAKEN` | El código choca con un descuento existente | Genera otro sufijo y reintenta ese código |
| `userErrors` por valor inválido | Porcentaje fuera de 1–100 o monto fijo negativo | Valida antes de enviar |
| `count > 1000` | Límite de seguridad por ejecución | Divide en varias ejecuciones |
| Falla de red a mitad del lote | Proceso completado a medias | El CSV registra `created` frente a `pending`; vuelve a ejecutar solo con la lista de sufijos pendientes |

## Buenas prácticas
- Usa `usage_limit: 1` en sorteos tipo influenciador, donde cada destinatario recibe un código privado; usa `usage_limit: N` solo cuando los códigos se reparten públicamente con un tope fijo.
- Elige un `prefix` con sentido (p. ej., `INFLUENCER-Q2`, `SORTEO-LANZAMIENTO`) para que los códigos sean fáciles de filtrar en el admin de Shopify y en los reportes de ventas.
- Define siempre `ends_at`: los códigos sin fecha de fin generan un costo de descuento que se arrastra en el tiempo y complican el análisis de retorno.
- Ejecuta primero con `dry_run: true` y revisa los sufijos generados; si te equivocas, los códigos no se pueden borrar en masa.
- Para `free_shipping`, usa `applies_to: shipping_only` y deja `value` sin definir; Shopify ignora el valor numérico en los descuentos de envío gratis.
- Combínala con `shopify-roi-descuentos` después de la campaña para medir el retorno por prefijo de código.
