---
name: shopify-ajuste-masivo-precios
description: "Cambia en lote el precio de todas las variantes de una colección o etiqueta, por porcentaje o monto fijo, con precio mínimo y máximo opcionales. Úsala para una rebaja, para volver a los precios normales o para trasladar un alza del proveedor."
---

## Propósito
Aplica un ajuste de precio porcentual o fijo a todas las variantes de una colección o etiqueta de Shopify en un solo flujo automatizado, sin recorrer los productos uno por uno en el panel de administración ni exportar un CSV, editarlo y volver a importarlo. Usa esta skill cuando necesites hacer una rebaja en toda la tienda o en una colección, volver a los precios anteriores cuando termine una promoción, trasladar un aumento de costos del proveedor o alinear los precios de un grupo de productos.

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

## 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 | false | Muestra las operaciones sin ejecutar mutaciones |
| collection_id | texto | no* | — | GID de la colección objetivo (p. ej., `gid://shopify/Collection/123`) |
| tag | texto | no* | — | Etiqueta de producto para filtrar (alternativa a `collection_id`) |
| adjustment_type | texto | sí | — | `percent` o `fixed` |
| adjustment_value | decimal | sí | — | Cuánto ajustar. Positivo = sube, negativo = baja. En porcentaje: `-10` = 10 % de descuento. |
| min_price | decimal | no | 0 | Precio mínimo: ninguna variante quedará por debajo de este valor |
| max_price | decimal | no | — | Precio máximo: ninguna variante quedará por encima de este valor (opcional) |

*Se requiere `collection_id` o `tag`.

## Seguridad

> ⚠️ El paso 2 ejecuta mutaciones `productVariantsBulkUpdate` que cambian los precios publicados de inmediato. Los cambios de precio no se pueden deshacer en lote por la API: cada variante se tiene que revertir por separado. Corre siempre primero con `dry_run: true` para revisar todos los cambios antes de aplicarlos. Compara el CSV de la simulación con lo que esperas antes de continuar.

## Pasos

1. **OPERACIÓN:** `products` — consulta
   **Entradas:** `first: 250`, `query: "collection_id:'<id>'"` o `query: "tag:'<tag>'"` y el cursor de paginación
   **Resultado esperado:** Lista de productos con todos los IDs de variantes, precios actuales y SKU; pagina hasta que `hasNextPage: false`

2. **OPERACIÓN:** `productVariantsBulkUpdate` — mutación
   **Entradas:** Por cada producto: `productId` + un arreglo de `{id, price}` con los precios nuevos calculados (respetando `min_price`/`max_price`)
   **Resultado esperado:** `price` actualizado por variante y el arreglo `userErrors`; reúne todos los errores de todos los lotes

## Operaciones GraphQL

```graphql
# products:query — validada con api_version 2025-01
query ProductsForPriceAdjustment($first: Int!, $after: String, $query: String) {
  products(first: $first, after: $after, query: $query) {
    edges {
      node {
        id
        title
        tags
        variants(first: 100) {
          edges {
            node {
              id
              title
              price
              compareAtPrice
              sku
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

```graphql
# productVariantsBulkUpdate:mutation — validada con api_version 2025-01
mutation ProductVariantsBulkUpdate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    productVariants {
      id
      price
      compareAtPrice
    }
    userErrors {
      field
      message
    }
  }
}
```

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: shopify-ajuste-masivo-precios        ║
║  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, p. ej., "143 registros devueltos">
```

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):
```
══════════════════════════════════════════════
RESUMEN DEL RESULTADO
  <Nombre de la métrica>:   <valor>
  ...
  Errores:          <cantidad, 0 si no hay>
  Archivo:          <nombre del archivo o "ninguno">
══════════════════════════════════════════════
```

Para `format: json`, muestra un objeto JSON con este esquema:
```json
{
  "skill": "shopify-ajuste-masivo-precios",
  "store": "<dominio>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": false,
  "steps": [
    {
      "step": 1,
      "operation": "<OperationName>",
      "type": "query|mutation",
      "params_summary": "<texto>",
      "result_summary": "<texto>",
      "skipped": false
    }
  ],
  "outcome": {
    "<metric_key>": "<valor>",
    "errors": 0,
    "output_file": "<nombre_de_archivo|null>"
  }
}
```

## Formato de salida
Archivo CSV `price_changes_<YYYY-MM-DD>.csv` con las columnas: `product_id`, `variant_id`, `sku`, `title`, `old_price`, `new_price`. En la simulación (`dry_run`) el CSV se genera igual, pero no se ejecuta ninguna mutación.

## Manejo de errores
| Error | Causa | Solución |
|-------|-------|----------|
| `Neither collection_id nor tag provided` | Los dos parámetros están vacíos | Indica `collection_id` o `tag` |
| `userErrors` en la respuesta de la mutación | Precio inválido o variante no encontrada | Registra el error por variante, sigue con las demás variantes y repórtalo en el resultado |
| `Product not found in collection` | El `collection_id` está mal o la colección está vacía | Verifica el GID de la colección en el panel de Shopify |
| Límite de solicitudes (429) | Demasiadas mutaciones seguidas | Reduce el tamaño del lote y reintenta con espera exponencial |

## Buenas prácticas
1. Corre siempre primero con `dry_run: true` y revisa el CSV para confirmar los precios antes de aplicarlos. No hay forma de deshacer en lote.
2. Pon tu costo como `min_price` para evitar que un descuento porcentual deje variantes por debajo del costo.
3. En colecciones con más de 250 productos, la skill pagina automáticamente: el CSV tendrá todas las variantes, sin importar cuántas páginas haya.
4. Usa `adjustment_type: percent` con un valor negativo para rebajas (p. ej., `-15` para un 15 % de descuento). Usa `adjustment_type: fixed` para ajustes de monto fijo (p. ej., `-5` para bajar cada variante en 5, en la moneda de la tienda).
5. Después de aplicar los cambios, revisa una muestra de precios en el panel de Shopify antes de anunciar la rebaja: los `userErrors` se registran, pero no detienen la ejecución.
