---
name: shopify-auditoria-inventario-bodegas
description: "Revisa los niveles de inventario en todas las ubicaciones activas y marca las variantes con disponible negativo o con más comprometido que en existencia, señal de un inventario desincronizado. Úsala cuando sospeches que tus cantidades en Shopify no cuadran con lo que hay en bodega."
---

## Propósito
Saca a la luz las diferencias de sincronización de inventario entre ubicaciones; en concreto, las variantes donde `available` es negativo o `committed` > `on_hand`, lo que indica un desfase entre el contador de comprometido de Shopify y el stock físico real. Causas comunes: un operador logístico (3PL) que tarda en registrar las devoluciones, descuentos del sistema de gestión de bodega (WMS) que se suman al comprometido de Shopify o problemas de sincronización entre varias tiendas. Solo lectura: no ejecuta mutaciones. Reemplaza las hojas de cálculo de conciliación manual de inventario y la necesidad de revisar cada ubicación por separado en el admin de Shopify.

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

## 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 | false | Muestra las operaciones sin ejecutar mutaciones |
| location_ids | array | no | — | GID de las ubicaciones específicas que se auditan (predeterminado: todas las ubicaciones activas) |
| flag_negative_available | bool | no | true | Marca las variantes donde `available < 0` |
| flag_committed_exceeds_onhand | bool | no | true | Marca las variantes donde `committed > on_hand` |
| include_untracked | bool | no | false | Incluye las variantes sin control de inventario |

## Pasos

1. **OPERACIÓN:** `locations` — consulta
   **Entradas:** `first: 50`, `query: "is_active:true"`
   **Resultado esperado:** Lista de ID y nombres de las ubicaciones activas; filtra por el parámetro `location_ids` si se indicó

2. **OPERACIÓN:** `inventoryItems` — consulta (mediante `location.inventoryLevels`)
   **Entradas:** Por ubicación: trae los niveles de inventario con las cantidades `available`, `committed` y `onHand` de cada variante
   **Resultado esperado:** Matriz de inventario por ubicación; calcula las diferencias y reúne las variantes marcadas

## Operaciones GraphQL

```graphql
# locations:query — validada con api_version 2025-01
query ActiveLocations($first: Int!) {
  locations(first: $first, query: "is_active:true") {
    edges {
      node {
        id
        name
        isActive
        inventoryLevels(first: 250) {
          edges {
            node {
              id
              available
              item {
                id
                sku
                tracked
                variant {
                  id
                  displayName
                  product {
                    id
                    title
                  }
                }
              }
            }
          }
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }
    }
  }
}
```

```graphql
# inventoryItems:query — validada con api_version 2025-01
query InventoryItemLevels($id: ID!, $after: String) {
  location(id: $id) {
    id
    name
    inventoryLevels(first: 250, after: $after) {
      edges {
        node {
          id
          available
          item {
            id
            sku
            tracked
            inventoryLevel(locationId: $id) {
              available
              incoming
            }
            variant {
              id
              displayName
              inventoryQuantity
              product {
                id
                title
              }
            }
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

Nota: la operación `inventoryItems:query` representa la consulta de los datos de los artículos de inventario a través de los niveles de inventario de cada ubicación. La Admin API de Shopify expone el inventario por ubicación mediante `location.inventoryLevels`.

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: shopify-auditoria-inventario-bodegas ║
║  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>
```

**Al terminar**, muestra:

Para `format: human` (predeterminado):
```
══════════════════════════════════════════════
RESUMEN DEL RESULTADO
  Ubicaciones auditadas:          <n>
  Variantes revisadas:            <n>
  Diferencias encontradas:        <n>
  Disponible negativo:            <n>
  Comprometido > En existencia:   <n>
  Errores:                        0
  Archivo:                        inventory-audit-<fecha>.csv
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-auditoria-inventario-bodegas",
  "store": "<dominio>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": false,
  "steps": [
    { "step": 1, "operation": "ActiveLocations", "type": "query", "params_summary": "first: 50, is_active: true", "result_summary": "<n> ubicaciones", "skipped": false },
    { "step": 2, "operation": "InventoryItemLevels", "type": "query", "params_summary": "por ubicación, first: 250", "result_summary": "<n> variantes revisadas", "skipped": false }
  ],
  "outcome": {
    "locations_audited": 0,
    "variants_scanned": 0,
    "discrepancies_found": 0,
    "negative_available_count": 0,
    "committed_exceeds_onhand_count": 0,
    "errors": 0,
    "output_file": "inventory-audit-<fecha>.csv"
  }
}
```

## Formato de salida
Archivo CSV `inventory-audit-<AAAA-MM-DD>.csv` con las columnas: `location_name`, `product_title`, `variant_title`, `sku`, `available`, `flag`.

También muestra una tabla resumen en pantalla:
| Ubicación | Variantes revisadas | Diferencias | Disponible negativo | Comprometido > En existencia |
|-----------|---------------------|-------------|---------------------|------------------------------|

## Manejo de errores
| Error | Causa | Solución |
|-------|-------|----------|
| No se devuelven ubicaciones | No hay ubicaciones activas | Verifica en el admin de Shopify que haya al menos una ubicación activa |
| `inventoryLevels` vacío en una ubicación | La ubicación no tiene inventario asignado | Es normal en ubicaciones solo de preparación de pedidos o virtuales |
| Límite de solicitudes (429) | Inventario grande y varias ubicaciones | Baja el tamaño de página por ubicación a 100 |
| `item.tracked: false` en todas las variantes | La tienda no controla el inventario | Activa el control de inventario o usa `include_untracked: true` |

## Buenas prácticas
1. Haz esta auditoría cada semana: el desfase de inventario se acumula poco a poco y es más fácil de corregir antes de que crezca.
2. Un valor `available` negativo no siempre es un problema real: puede pasar de forma legítima durante ventas relámpago. Compáralo con tu volumen de pedidos antes de hacer correcciones manuales.
3. Usa el CSV con la skill `shopify-ajuste-inventario` para aplicar las correcciones apenas la auditoría detecte diferencias.
4. Haz la auditoría antes y después de que el operador logístico registre un lote de devoluciones, para medir si el inventario devuelto se está acreditando bien.
5. Filtra una sola ubicación con `location_id` para auditorías puntuales (por ejemplo, solo tu bodega principal) y así reducir las llamadas a la API en catálogos grandes.
