---
name: shopify-costo-devoluciones
description: "Solo lectura: calcula el costo real de las devoluciones por motivo y por producto: dinero reembolsado, envío perdido, costo de los artículos dados de baja y trabajo de reingreso al inventario. Úsala cuando quieras saber qué devoluciones te cuestan más y qué corregir primero."
---

## Propósito
Cuantifica el costo completo de las devoluciones en un periodo, no solo el monto reembolsado. Suma los reembolsos, el ingreso por envío perdido, el costo de los productos vendidos (COGS) de los artículos que no se pueden volver a vender (p. ej., `DEFECTIVE`) y la mano de obra de reingreso al inventario, y arma un estado de pérdidas y ganancias de las devoluciones por motivo y por producto. Solo lectura. Úsala para decidir qué motivos o líneas de producto merecen ajustes operativos: mejor empaque, guías de tallas, fichas más precisas.

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

## 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` |
| days_back | integer | no | 90 | Ventana de días hacia atrás para las devoluciones |
| group_by | string | no | reason | Nivel de agrupación: `reason`, `product`, `sku` o `reason_x_product` |
| min_returns | integer | no | 3 | Devoluciones mínimas por grupo para incluirlo en el resumen |
| writeoff_reasons | array | no | `["DEFECTIVE"]` | Motivos de devolución cuyos artículos se consideran no revendibles (se da de baja el COGS completo) |
| flat_restocking_cost | float | no | 5.00 | Costo promedio de mano de obra por artículo devuelto, en la moneda de la tienda, para modelar el trabajo de reingreso al inventario |

## Seguridad

> ℹ️ Skill de solo lectura: no ejecuta mutaciones. Las cifras de costo son estimaciones basadas en `unitCost`, los totales reembolsados y `flat_restocking_cost`: ajusta el costo fijo a tu operación antes de tomar los resultados como cifras contables.

## Pasos

1. **OPERACIÓN:** `returns` — consulta
   **Entradas:** `query: "created_at:>='<NOW - days_back days>'"`, `first: 250`; trae las devoluciones con el precio de cada artículo, el producto y la variante, y `inventoryItem.id`
   **Resultado esperado:** Todas las devoluciones del periodo con el precio por artículo

2. **OPERACIÓN:** `orders` — consulta
   **Entradas:** Para el `order.id` de cada devolución, trae `refunds { totalRefundedSet refundLineItems { quantity subtotalSet totalTaxSet lineItem { id } } }`
   **Resultado esperado:** Montos reembolsados que se pueden asociar a cada artículo

3. **OPERACIÓN:** `inventoryItems` — consulta — en lote, con los `inventoryItem.id` únicos del paso 1; devuelve el `unitCost` de cada artículo

4. Por cada artículo calcula: `refund_amount` (el refundLineItem correspondiente, proporcional a la cantidad devuelta), `shipping_loss` (envío del pedido × participación del artículo en el valor, cuando se devuelve el pedido completo; si no, 0), `cogs_writeoff` (`unitCost × qty` solo si `returnReason in writeoff_reasons`), `restocking_labor` (`flat_restocking_cost × qty`). Suma y agrupa según `group_by`.

## Operaciones GraphQL

```graphql
# returns:query — validada con api_version 2025-01
query ReturnsForCostAttribution($query: String!, $after: String) {
  returns(first: 250, after: $after, query: $query) {
    edges {
      node {
        id
        status
        createdAt
        totalQuantity
        order {
          id
          name
          totalShippingPriceSet { shopMoney { amount currencyCode } }
          totalPriceSet { shopMoney { amount currencyCode } }
        }
        returnLineItems(first: 50) {
          edges { node {
            id
            quantity
            returnReason
            fulfillmentLineItem { lineItem {
              id
              title
              quantity
              discountedTotalSet { shopMoney { amount currencyCode } }
              originalUnitPriceSet { shopMoney { amount currencyCode } }
              variant { id sku inventoryItem { id } }
              product { id title vendor }
            } }
          } }
        }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

```graphql
# orders:query — validada con api_version 2025-01
query OrderRefundsForReturns($query: String!, $after: String) {
  orders(first: 250, after: $after, query: $query) {
    edges {
      node {
        id
        name
        refunds {
          id
          createdAt
          totalRefundedSet { shopMoney { amount currencyCode } }
          refundLineItems(first: 50) {
            edges { node {
              quantity
              subtotalSet { shopMoney { amount currencyCode } }
              totalTaxSet { shopMoney { amount currencyCode } }
              lineItem { id }
            } }
          }
        }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

```graphql
# inventoryItems:query — validada con api_version 2025-01
query InventoryUnitCosts($ids: [ID!]!) {
  nodes(ids: $ids) {
    ... on InventoryItem {
      id
      unitCost { amount currencyCode }
      tracked
    }
  }
}
```

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Cuánto te cuestan las devoluciones   ║
║  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):
```
══════════════════════════════════════════════
COSTO DE LAS DEVOLUCIONES  (<days_back> días, grupo: <group_by>)
  Devoluciones analizadas:    <n>
  Costo total de devoluciones: <monto>  (reembolso <pct>%, envío <pct>%, COGS <pct>%, mano de obra <pct>%)

  Principales fuentes de costo:
    <grupo>   Devoluciones: <n>  Total: <monto>  Promedio: <monto>  Motivo principal: <motivo>
  Archivo: return_cost_<fecha>.csv
══════════════════════════════════════════════
```

Los montos van en la moneda de la tienda.

Para `format: json`, muestra:
```json
{
  "skill": "shopify-costo-devoluciones",
  "store": "<dominio>",
  "period_days": 90,
  "group_by": "reason",
  "returns_analyzed": 0,
  "totals": {
    "total_cost": 0, "refund": 0, "shipping_loss": 0,
    "cogs_writeoff": 0, "restocking_labor": 0, "currency": "<moneda de la tienda>"
  },
  "groups": [],
  "output_file": "return_cost_<fecha>.csv"
}
```

## Formato de salida
Archivo CSV `return_cost_<AAAA-MM-DD>.csv` con las columnas:
`group_key`, `return_count`, `units`, `refund_amount`, `shipping_loss`, `cogs_writeoff`, `restocking_labor`, `total_cost`, `avg_cost_per_return`, `top_return_reason`, `currency`

## 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 |
| Falta `unitCost` | El costo no está registrado | Toma el COGS como 0 y marca la fila |
| Reembolso aún sin procesar | Al cliente todavía no se le ha reembolsado | Usa el total con descuento del artículo como estimación |
| Varios reembolsos por devolución | Historial de reembolsos parciales | Suma los reembolsos asociados a los artículos de la devolución |
| Sin costo de envío | Envío gratis | Pérdida por envío = 0 |

## Buenas prácticas
- Usa `group_by: reason_x_product` para encontrar combinaciones críticas como `DEFECTIVE × <tu SKU estrella>`: problemas de calidad del proveedor que se pueden atacar en el origen.
- Vuelve a ejecutarla después de actualizar `unitCost`; un costo desactualizado es lo que más distorsiona la baja de COGS.
- Combínala con `shopify-motivos-devolucion` para comparar «lo que más se devuelve» con «lo que más cuesta»: muchas veces no coinciden.
