### Prompt para construir aplicación de generación y escritura automática de ALTs vacíos en WordPress.
Quiero que generes **una aplicación CLI en Python** (un solo repositorio) que haga lo siguiente de principio a fin:
#### 0) Metas y alcance
* Objetivo: **rellenar de forma segura y masiva el `alt_text` de imágenes en WordPress** usando un modelo multimodal de OpenRouter y **contexto semántico** (H2 más próximo si la imagen está incrustada en un post; H1/título si es imagen destacada).
* Debe **descargar y comprimir** cada imagen para abaratar el análisis, invocar **OpenRouter (modelo `qwen/qwen-2.5-vl-7b-instruct`)**, escribir un ALT SEO conciso en español, **actualizar WordPress** y **emitir un CSV de resultados** con enlace directo para editar la imagen en WP y una **puntuación de fiabilidad**.
* Todo debe poder ejecutarse con una **sola orden CLI** y con **.env** para los secretos.
---
#### 1) Estructura del proyecto
Crea un repo con estos archivos:
* `app.py` (entrypoint CLI con Typer)
* `wp_client.py` (cliente REST de WordPress)
* `vision_client.py` (cliente OpenRouter)
* `image_utils.py` (descarga, normalización y compresión)
* `context_extractor.py` (encontrar H2/H1 de contexto)
* `report.py` (CSV)
* `process_all_images.py` (script para procesamiento masivo con logging detallado)
* `process_post_complete.py` (script para procesar un post específico)
* `delete_alts.py` (script para eliminar ALT texts incorrectos)
* `force_update_alts.py` (script para forzar actualización de ALT texts específicos)
* `test_app.py` (script de pruebas)
* `requirements.txt`
* `.env.example`
* `README.md`
* `PROJECT_SUMMARY.md` (resumen del proyecto)
* `docs/IMPLEMENTATION.md` (documentación técnica)
* `examples/` (ejemplos de uso)
---
#### 2) Dependencias (requirements.txt)
Usa solo librerías mantenidas y comunes:
* `httpx>=0.25.0` (para HTTP con timeouts/concurrency)
* `python-dotenv>=1.0.0`
* `beautifulsoup4>=4.12.0`, `lxml>=4.9.0`
* `Pillow>=10.0.0`
* `tqdm>=4.66.0`
* `tenacity>=8.2.0` (reintentos)
* `pandas>=2.0.0` (generar CSV)
* `cairosvg>=2.7.0` (para rasterizar SVG a PNG antes de comprimir)
* `typer>=0.9.0` (CLI framework)
---
#### 3) Configuración por `.env`
Cargar al inicio con `dotenv` y fallar con mensaje claro si falta algo.
Variables:
* `WP_BASE_URL`
* `WP_USERNAME`
* `WP_APPLICATION_PASSWORD` (Application Password de WordPress; autenticación Basic)
* `OPENROUTER_API_KEY`
* `OPENROUTER_BASE_URL` = `https://openrouter.ai/api/v1`
* `OPENROUTER_MODEL` = `qwen/qwen-2.5-vl-7b-instruct`
* Opcionales:
* `MIN_CONFIDENCE` (por defecto `0.55`)
* `CONCURRENCY` (por defecto `4`)
* `TIMEOUT_SECONDS` (por defecto `30`)
---
#### 4) CLI (app.py)
Proveer flags con Typer:
* `--report PATH` (por defecto `report_updated.csv`)
* `--dry-run` (por defecto `True`. Si `True`, **no** actualiza alt en WP, solo simula y genera CSV)
* `--limit N` (procesar hasta N imágenes)
* `--include-pages` (incluir páginas además de posts; por defecto `False`)
* `--min-confidence FLOAT` (override de `MIN_CONFIDENCE`)
* `--post-id` (sirve para testear con un solo post, solo recupera las imágenes de ese post y hace el proceso de detección de alts vacíos, reconocimiento de la imagen, generación del texto, y escritura de los nuevos alts. El propósito es testeos)
* `--regenerate-mapping` (solo regenerar el mapeo del sitio)
Progreso con `tqdm`. Logging legible a stdout con colores y emojis usando `typer.secho`.
---
#### 5) Descubrimiento de imágenes sin ALT en WordPress (`wp_client.py`)
* Autenticación: **Basic** con `username:application_password` (Application Passwords nativo de WP ≥5.6).
* Inicialmente haremos un mapeo de todos los posts/pages del blog, guardando la relación de imágenes por post, y almacenando la situación de cada imagen: id, y si el alt está vacío, obtención del contexto... se detalla a continuación.
* Endpoints:
* **Media**: `GET /wp-json/wp/v2/media?per_page=100&page=…&_fields=id,source_url,alt_text,media_type,mime_type,media_details`
* Paginar hasta agotar resultados.
* Filtrar **en cliente** las imágenes con `alt_text` vacío o nulo. Considerar solo mime `image/*`.
* **Posts**: `GET /wp-json/wp/v2/posts?per_page=100&page=…&_fields=id,title,content,featured_media,link`
* **Pages** (si `--include-pages=1`): `GET /wp-json/wp/v2/pages?...` con los mismos campos.
* Para cada **attachment** encontrado sin ALT:
* `attachment.id`, `attachment.source_url`, `attachment.mime_type`.
* **Contexto**:
* **Como destacada**: si existe un post/página cuyo `featured_media == attachment.id`, entonces **contexto = H1 = `title.rendered`** de ese post/página (limpiar HTML).
* **Como incrustada**: escanear `content.rendered` HTML de posts/páginas; localizar `<img>` cuyo `src` o `srcset` contenga la `source_url` (o cuyo `class` contenga `wp-image-{id}`). Con BeautifulSoup:
* obtener el `h2` más cercano **anterior** (o el heading más cercano en el mismo bloque) como **contexto**; si no hay H2, usar el heading más próximo (H3/H4…); si no hay, fallback al **H1 (título)** del post.
* Permitir múltiples usos; priorizar **H2** si existe, si no H1 del post/página destacada. Guarda `context_type` = `featured|embedded|unknown` y `context_heading` (texto limpio).
---
* el mapeo se archivará en el fichero `{url_base}_mapeo.json`
* una vez completado el mapeo ya podemos proceder con los siguientes pasos
* No se procesarán imágenes duplicadas
* Incluir estadísticas detalladas del mapeo (total posts, imágenes con/sin ALT, etc.)
---
#### 6) Descarga y preprocesado de imagen (`image_utils.py`)
* **Descargar** por `source_url` con timeouts y reintentos (tenacity).
* **Normalizar**:
* Si `SVG`: intentar rasterizar con `cairosvg` → PNG → abrir con Pillow; si falla, **omitir** con aviso y registrar en CSV (status=`skipped_svg`).
* Abrir con Pillow; si tiene alpha, **componer sobre fondo blanco**.
* **Redimensionar a ancho 500px** manteniendo aspecto **solo si** la imagen es más ancha de 500px.
* Convertir a **JPEG** con calidad **60** (o similar), `optimize=True`, modo RGB.
* Retornar **bytes** del JPEG comprimido + dimensiones resultantes.
---
#### 7) Cliente OpenRouter (`vision_client.py`)
* Consumir OpenRouter usando API **OpenAI-compatible** mediante `httpx` directo (no dependas de SDKs externos).
* Endpoint: `POST {OPENROUTER_BASE_URL}/chat/completions`
* Headers:
* `Authorization: Bearer {OPENROUTER_API_KEY}`
* `HTTP-Referer: {WP_BASE_URL}` (si procede)
* `X-Title: GuruWalk Alt Text Generator`
* **Modelo**: `{OPENROUTER_MODEL}` = `qwen/qwen-2.5-vl-7b-instruct`.
* **Mensaje** con contenido multimodal:
* Pasa **la imagen como data URL** `data:image/jpeg;base64,...` **y** el **contexto textual** (H2/H1, tipo de contexto, URL pública, nombre del post) en el mismo turno de usuario.
* Si el modelo no acepta data URL, usa la **URL pública** de la imagen (fallback).
* **Instrucciones del sistema (ES)** (inclúyelas como primer mensaje `role=system`):
```
Eres un experto en accesibilidad y SEO. Escribe texto alternativo (atributo alt) en español neutro, conciso (ideal 8–16 palabras, máx. ~125–140 caracteres), sin comillas, sin "imagen de", sin stuffing de keywords, sin marcas salvo que aparezcan visualmente. Incluye texto legible en la imagen (pósters/carteles) cuando sea importante.
Si la imagen es un logo: "Logo de {marca}".
Si es claramente decorativa o un icono genérico sin valor informativo, responde con skip=true.
Devuelve SIEMPRE JSON estricto con esta forma:
{
"alt_text": "<string en español>",
"confidence": <float 0..1>,
"reason": "<máx. 15 palabras justificando la confianza>",
"skip": <true|false>
}
Criterios de confianza: nitidez, ausencia de ambigüedad, coincidencia con el contexto (H2/H1), ausencia de alucinaciones, identificación correcta de escena/objeto, inclusión de texto visible relevante.
```
* **Mensaje de usuario** (plantilla):
```
CONTEXTO:
- Tipo: {context_type}
- Heading: {context_heading} // cadena limpia, puede estar vacía
- Post/Página: {post_title} | ID: {post_id}
- URL pública imagen: {source_url}
- Idioma: es
TAREA: Genera un alt_text SEO-friendly siguiendo las instrucciones del sistema. Responde SOLO el JSON.
[IMAGEN ADJUNTA]
```
* Parsear respuesta; **validar JSON**; si falla, reintentar con prompt "solo JSON".
---
#### 8) Lógica de decisión y actualización (`app.py`)
Para cada attachment sin ALT:
1. **Descargar + comprimir** (500px) → bytes JPEG.
2. **Construir contexto** (H2/H1) como arriba.
3. **Llamar OpenRouter** con la imagen + contexto.
4. Si `skip==true` → registrar fila `status=skipped_decorative` y continuar.
5. Si `confidence < MIN_CONFIDENCE` → registrar `status=low_confidence` y **no** actualizar salvo que el usuario pase `--min-confidence` menor.
6. Si OK:
* Si `--dry-run=False`: **POST** a `WP /wp-json/wp/v2/media/{id}` con body `{"alt_text": "<nuevo alt>"}` y Basic Auth. Manejar 401/403/429/5xx con **reintentos exponenciales**.
* Considerar éxito si HTTP 200 y `alt_text` coincide.
7. Añadir fila a **CSV** con:
* `image_url` (source\_url)
* `attachment_id`
* `context_type` (`featured|embedded|unknown`)
* `context_heading`
* `post_id` (si aplica)
* `post_title` (si aplica)
* `edit_media_url` = `{WP_BASE_URL}/wp-admin/post.php?post={attachment_id}&action=edit`
* `old_alt_text` (vacío o existente)
* `new_alt_text`
* `confidence`
* `model` (qwen/qwen-2.5-vl-7b-instruct)
* `status` (`updated|dry_run|low_confidence|skipped_decorative|skipped_svg|error`)
* `error_message` (si hay)
> Importante: El CSV **principal** debe listar **solo las imágenes cuyo ALT fue cambiado** (`status=updated` o `dry_run` si se hubiera actualizado). Crea además `report_all.csv` con todas las filas para auditoría.
---
#### 9) CSV y reporting (`report.py`)
* Escribir **`report_updated.csv`** con las columnas anteriores filtradas por `status in {updated,dry_run}`.
* Escribir **`report_all.csv`** con todo.
* Mostrar resumen final en consola: total candidatos, saltos, actualizados, tiempo total.
---
#### 10) Scripts adicionales
**`process_all_images.py`**: Script para procesamiento masivo con logging detallado completo, incluyendo información de cada imagen procesada, tiempos, y estadísticas.
**`process_post_complete.py`**: Script para procesar un post específico con análisis completo.
**`delete_alts.py`**: Script para eliminar ALT texts incorrectos de una lista predefinida de IDs.
**`force_update_alts.py`**: Script para forzar la actualización de ALT texts específicos con valores predefinidos.
**`test_app.py`**: Script de pruebas para verificar la funcionalidad básica.
---
#### 11) Robustez
* Timeouts configurables, reintentos (`tenacity`) para HTTP WP y OpenRouter.
* Sanitizar HTML de headings (strip tags, normalizar espacios).
* Manejar formatos: jpg/jpeg/png/webp/gif; gif animado → usa solo primer frame. svg → intentar rasterizar; si no, saltar.
* No procesar **duplicados**: de-dup por `source_url` y por `attachment_id`.
* Respeta `--limit N`.
* Logging colorido con emojis usando `typer.secho`.
* Manejo de errores con mensajes informativos en colores.
---
#### 12) README.md (instrucciones de uso)
Incluye:
* Cómo crear `Application Password` en WP.
* Cómo rellenar `.env` (proveer `.env.example`).
* Instalación: `python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt`
* Ejemplos:
* **Simulación**:
`python app.py --report report_updated.csv --dry-run --limit 200 --min-confidence 0.6`
* **Aplicar cambios**:
`python app.py --dry-run=False --report report_updated.csv`
* **Procesar solo un post**:
`python app.py --post-id 123 --dry-run`
* **Regenerar mapeo**:
`python app.py --regenerate-mapping`
* **Procesamiento masivo**:
`python process_all_images.py`
* Notas de privacidad: solo se envían a OpenRouter **la imagen comprimida** (o URL si data URL no aceptada) + contexto mínimo.
---
#### 13) Criterios de aceptación (tests manuales)
* La app detecta imágenes con `alt_text` vacío (al menos 10 ejemplos) y **genera** un `report_all.csv`.
* La app **actualiza** el `alt_text` de al menos un attachment cuando `--dry-run=False`.
* El enlace `edit_media_url` abre la pantalla de edición del adjunto en WP.
* El ALT generado:
* está en español,
* no supera \~140 caracteres,
* no usa "imagen de",
* incorpora texto visible si lo hay,
* se ajusta al contexto H2/H1.
* El CSV principal **solo** contiene filas actualizadas (o que se actualizarían en dry-run).
* Los scripts adicionales funcionan correctamente para casos específicos.
---
**Genera todo el código y archivos descritos arriba.**
El lenguaje del ALT debe ser **español**.
Usa `qwen/qwen-2.5-vl-7b-instruct` en OpenRouter.
No incluyas credenciales en el repo.
El resultado final debe ser ejecutable con los pasos del README.
Usa `typer` para la CLI con colores y emojis.
Incluye logging detallado y manejo de errores robusto.
Continua leyendo
Leer más sobre: SEO, Programación