---
name: shopify-texto-alternativo-imagenes
description: "Genera y aplica texto alternativo descriptivo a las imágenes de producto para que los agentes de IA, que no «ven» las fotos, entiendan cómo es cada producto y lo recomienden. Úsala cuando tus imágenes no tengan texto alternativo o solo tengan el nombre del archivo."
---

## Propósito
Un agente de compras con IA lee el **texto alternativo** (alt text) de las imágenes para entender cómo se ve un producto: color, material, estilo, uso. Un texto alternativo vacío o con forma de nombre de archivo («IMG_2931.jpg») no le dice nada al agente, así que no puede relacionar el producto con una búsqueda visual («vestido midi de lino rojo») ni describírselo a un comprador. Esta skill encuentra las imágenes de producto sin texto alternativo o con uno de poco valor, y escribe textos alternativos breves y descriptivos a partir del título, las opciones, el tipo y las etiquetas del producto. Resuelve el problema de fichas de producto con imágenes sin texto alternativo.

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

## Parámetros
Todas las skills aceptan estos parámetros generales:

| 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` (predeterminado) o `json` |
| dry_run   | bool   | no          | false          | Muestra una vista previa de los cambios sin ejecutarlos |

Parámetros propios de esta skill:

| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|-----------|------|-------------|----------------|-------------|
| collection_id | string | no | — | Limita la revisión a una colección (GID) |
| tag | string | no | — | Limita la revisión a una etiqueta de producto |
| overwrite | bool | no | false | Si es true, también reescribe los textos alternativos con forma de nombre de archivo o de relleno; si es false, solo completa los vacíos |
| max_chars | int | no | 125 | Largo máximo del texto alternativo (accesibilidad y legibilidad para agentes) |

## Seguridad

> ⚠️ El paso 3 (`fileUpdate`) escribe el texto alternativo en las imágenes publicadas. Con `overwrite: true` reemplaza el texto alternativo existente, y eso no se puede revertir en bloque. Ejecuta primero con `dry_run: true` y revisa el texto propuesto para cada imagen. El modo predeterminado (`overwrite: false`) solo completa los vacíos y tiene bajo riesgo.

## Pasos

1. **OPERACIÓN:** `products` — query
   **Entradas:** `first: 250`, filtro opcional por colección o etiqueta; campos `title`, `productType`, `tags`, `options`, `media{ MediaImage{ id image{ altText } } }`; pagina hasta terminar.
   **Resultado esperado:** Las imágenes de cada producto con su texto alternativo actual, más el contexto del producto necesario para generar un buen texto alternativo.

2. **CÁLCULO (sin API):** para cada imagen con texto alternativo vacío (o, si `overwrite` está activo, de relleno), arma el texto alternativo con el `title` + los valores de opción relevantes (color, material, estilo) + el tipo de producto, recortado a `max_chars`. Muestra una tabla de vista previa (id de imagen → texto propuesto).

3. **OPERACIÓN:** `fileUpdate` — mutation
   **Entradas:** `files: [{ id: <MediaImage id>, alt: <texto generado> }]` en lotes (≤ 25).
   **Resultado esperado:** Archivos actualizados; recoge los `userErrors`.

## Operaciones GraphQL

```graphql
# products:query — validado con api_version 2025-01
query AltTextProducts($first: Int!, $after: String, $query: String) {
  products(first: $first, after: $after, query: $query) {
    edges {
      node {
        id
        title
        productType
        tags
        options { name values }
        media(first: 20) {
          edges {
            node { ... on MediaImage { id image { altText url } } }
          }
        }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

```graphql
# fileUpdate:mutation — validado con api_version 2025-01
mutation AltTextUpdate($files: [FileUpdateInput!]!) {
  fileUpdate(files: $files) {
    files { ... on MediaImage { id alt } }
    userErrors { field message code }
  }
}
```

## Seguimiento de la sesión

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

**Al iniciar**, muestra:
```
╔══════════════════════════════════════════════╗
║  SKILL: <nombre de la skill>                 ║
║  Tienda: <dominio de la tienda>              ║
║  Inicio: <AAAA-MM-DD HH:MM UTC>              ║
╚══════════════════════════════════════════════╝
```

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

Si `dry_run: true`, antepón `[DRY RUN]` 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:          0
  Archivo:          <nombre del archivo o "ninguno">
══════════════════════════════════════════════
```

Para `format: json`, muestra:
```json
{
  "skill": "<slug-de-la-skill>",
  "store": "<dominio>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": false,
  "steps": [
    {
      "step": 1,
      "operation": "<NombreDeOperación>",
      "type": "query",
      "params_summary": "<texto>",
      "result_summary": "<texto>",
      "skipped": false
    }
  ],
  "outcome": {
    "metric_key": 0,
    "errors": 0,
    "output_file": null
  }
}
```

## Formato de salida
`human`: cantidad de imágenes actualizadas + un CSV (`product, image_id, old_alt, new_alt`). `json`: `{ images_updated, images_skipped, errors, output_file }`.

## Manejo de errores
| Error | Causa | Solución |
|-------|-------|----------|
| `THROTTLED` | Límite de solicitudes de la API | Espera 2 s y reintenta hasta 3 veces |
| `userErrors` no vacío | Id de archivo inválido o no es una MediaImage | Registra el mensaje, omite la imagen y continúa |
| El texto supera el límite | El texto generado es demasiado largo | Recórtalo a `max_chars` sin cortar palabras |

## Buenas prácticas
- Empieza el texto alternativo con el sustantivo concreto + el atributo que lo distingue («Suéter de cuello redondo de merino gris carbón»), no con frases de marketing: eso es lo que los agentes comparan.
- Deja `overwrite: false` en la primera pasada para corregir sin riesgo el peor vacío (las imágenes sin texto); más adelante haz una pasada revisada con `overwrite: true` para los textos con forma de nombre de archivo.
- No rellenes con palabras clave: una frase precisa vale más que una lista de términos separados por comas, y además se lee mejor para quienes usan lectores de pantalla.
- Vuelve a ejecutar `shopify-auditoria-agentes-ia` después para confirmar que mejoró la cobertura de texto alternativo.
