---
name: shopify-auditoria-archivos
description: "Solo lectura: lista todos los archivos guardados en Shopify, revisa si se usan en productos, páginas o artículos del blog y marca los que nadie usa. Úsala cuando quieras saber qué archivos sobran antes de hacer limpieza."
---

## Propósito
Hace un inventario de todos los archivos (imágenes, videos y archivos genéricos) de la biblioteca de archivos de la tienda en el CDN y cruza cada uno con los productos, las páginas y los artículos del blog para saber si realmente se usa. Los archivos huérfanos inflan el almacenamiento, hacen más lenta la búsqueda en el admin y esconden los recursos de marca. Solo lectura: no ejecuta mutaciones. Sirve como base de datos para una limpieza o un archivado manual.

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

## Parámetros

| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|-----------|------|-------------|-------------|-------------|
| store | string | sí | — | Dominio de la tienda (p. ej., tu-tienda.myshopify.com) |
| min_age_days | integer | no | 30 | Marca solo los archivos con más de estos días de antigüedad (para no incluir archivos recién subidos que todavía se están usando) |
| file_types | string | no | all | Filtro: `IMAGE`, `VIDEO`, `GENERIC_FILE` o `all` |
| sample_orphans | integer | no | 25 | Cuántos archivos huérfanos mostrar en el resumen final en formato `human` |
| format | string | no | human | Formato de salida: `human` o `json` |

## Seguridad

> ℹ️ Skill de solo lectura: no ejecuta mutaciones. Puedes correrla en cualquier momento. Esta skill no elimina ningún archivo; solo genera un reporte.

## Pasos

1. **OPERACIÓN:** `files` — consulta
   **Entradas:** `first: 250`, selecciona `id`, `alt`, `createdAt`, `fileStatus`, `__typename`, más los campos de URL y tamaño propios de cada tipo, cursor de paginación
   **Resultado esperado:** Inventario completo de archivos con sus URL del CDN y su tamaño en bytes; pagina hasta que `hasNextPage: false`

2. **OPERACIÓN:** `products` — consulta
   **Entradas:** `first: 250`, selecciona `media { ... on MediaImage { image { url } id }, ... on Video { sources { url } } }`, cursor de paginación
   **Resultado esperado:** Conjunto de IDs y URL de archivos que usa algún producto

3. **OPERACIÓN:** `pages` — consulta
   **Entradas:** `first: 250`, selecciona `body` (el HTML de la página, para buscar referencias en etiquetas `<img src=...>`)
   **Resultado esperado:** Contenido de las páginas; extrae las URL `cdn.shopify.com/...`

4. **OPERACIÓN:** `articles` — consulta
   **Entradas:** `first: 250`, selecciona `body` e `image { url }`
   **Resultado esperado:** Contenido de los artículos e imágenes principales; extrae las URL de archivos referenciadas

5. Cruce: cualquier archivo del paso 1 cuyo `id` o URL canónica no aparezca en el conjunto de referencias de los pasos 2, 3 y 4 → huérfano.

6. Aplica el filtro `min_age_days`: excluye de la lista de huérfanos los archivos creados en los últimos N días para no marcar archivos de prueba o recién subidos.

## Operaciones GraphQL

```graphql
# files:query — validado con api_version 2025-01
query FileInventory($after: String, $query: String) {
  files(first: 250, after: $after, query: $query) {
    edges {
      node {
        id
        alt
        createdAt
        fileStatus
        __typename
        ... on MediaImage {
          image { url width height }
          originalSource { fileSize }
        }
        ... on Video {
          sources { url mimeType fileSize }
        }
        ... on GenericFile {
          url
          mimeType
          originalFileSize
        }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

```graphql
# products:query — validado con api_version 2025-01
query ProductMediaReferences($after: String) {
  products(first: 250, after: $after) {
    edges {
      node {
        id
        media(first: 50) {
          edges {
            node {
              ... on MediaImage { id image { url } }
              ... on Video { id sources { url } }
            }
          }
        }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

```graphql
# pages:query — validado con api_version 2025-01
query PageBodyReferences($after: String) {
  pages(first: 250, after: $after) {
    edges { node { id title body } }
    pageInfo { hasNextPage endCursor }
  }
}

# articles:query — validado con api_version 2025-01
query ArticleBodyReferences($after: String) {
  articles(first: 250, after: $after) {
    edges { node { id title body image { url } } }
    pageInfo { hasNextPage endCursor }
  }
}
```

## Seguimiento de la sesión

**Claude DEBE mostrar esta salida en cada etapa. Es obligatorio.**

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: Auditoría de archivos                ║
║  Tienda: <dominio de la tienda>              ║
║  Inicio: <YYYY-MM-DD HH:MM UTC>              ║
╚══════════════════════════════════════════════╝
```

**Después de cada paso**, muestra:
```
[N/TOTAL] <CONSULTA|MUTACIÓN>  <NombreOperación>
          → Parámetros: <resumen breve de las entradas clave>
          → Resultado: <conteo o resultado>
```

**Al terminar**, muestra:

Para `format: human` (por defecto):
```
══════════════════════════════════════════════
AUDITORÍA DE ARCHIVOS
  Total de archivos:     <n>   ( <total_size_mb> MB )
    Imágenes:            <n>
    Videos:              <n>
    Archivos genéricos:  <n>
  Archivos en uso:       <n>   ( <ref_size_mb> MB )
  Archivos huérfanos:    <n>   ( <orphan_size_mb> MB ,  <pct>%)

  Muestra de huérfanos:
    "<nombre del archivo>"  <tamaño>  subido: <YYYY-MM-DD>
  Archivo: file_audit_<fecha>.csv
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "shopify-auditoria-archivos",
  "store": "<dominio>",
  "total_files": 0,
  "total_size_bytes": 0,
  "referenced_files": 0,
  "orphaned_files": 0,
  "orphaned_size_bytes": 0,
  "orphan_pct": 0,
  "output_file": "file_audit_<fecha>.csv"
}
```

## Formato de salida
Archivo CSV `file_audit_<YYYY-MM-DD>.csv` con las columnas:
`file_id`, `file_type`, `url`, `alt`, `size_bytes`, `created_at`, `age_days`, `is_referenced`, `referenced_by_count`, `referenced_by_sample`

## 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 |
| `ACCESS_DENIED` en `files` | Falta el permiso `read_files` | Vuelve a autenticarte agregando `read_files` |
| Archivo sin campo de tamaño | Los metadatos del CDN todavía se están propagando | Usa `size_bytes = null` e inclúyelo en el reporte con una nota |
| No se detecta la URL en el contenido | La página o el artículo usa una ruta de recurso del tema, no una URL del CDN | Márcalo como `referenced_by: theme` y exclúyelo de la lista de huérfanos |

## Buenas prácticas
- Córrela antes de cualquier recarga grande de imágenes o videos (p. ej., una renovación del catálogo) para tener una línea base del almacenamiento actual.
- Usa `min_age_days: 30` para no marcar archivos recién subidos que todavía no se han asociado a un producto o página.
- Ordena el CSV por `size_bytes` de mayor a menor: unos pocos videos grandes suelen concentrar la mayor parte del almacenamiento.
- NO borres archivos en lote directamente a partir del reporte. Revisa primero 10 huérfanos al azar: las referencias desde el tema y las plantillas de correo no siempre se pueden detectar con la Admin API.
- Guarda el CSV del mes anterior y compáralo con el nuevo para seguir el crecimiento neto del almacenamiento.
