---
title: "Portadas generadas por agentes: Nano Banana 2, Cloud Storage y un Worker"
description: "El MCP de mi web ahora genera sus propias portadas: Nano Banana 2 crea la imagen, sharp la deja 85% más liviana, Cloud Storage la guarda y un Worker de Cloudflare la sirve como CDN. Todo desde el chat, a ~$0.07 por portada."
url: https://gabrielherencia.com/insights/portadas-generadas-por-agentes-nano-banana-2-cloud-storage-y-un-worker
author: "Gabriel Herencia"
published: 2026-07-14
updated: 2026-07-14
category: "Automatización"
tags: ["IA", "MCP", "serverless", "vercel", "gemini", "cloudflare"]
reading_time_min: 2
---

# Portadas generadas por agentes: Nano Banana 2, Cloud Storage y un Worker

> El MCP de mi web ahora genera sus propias portadas: Nano Banana 2 crea la imagen, sharp la deja 85% más liviana, Cloud Storage la guarda y un Worker de Cloudflare la sirve como CDN. Todo desde el chat, a ~$0.07 por portada.

![Portada: Portadas generadas por agentes: Nano Banana 2, Cloud Storage y un Worker](https://img.gabrielherencia.com/covers/portadas-generadas-por-agentes-nano-banana-2-cloud-storage-y-un-worker-965d0eb9.jpg)

En el insight anterior conté que [mi-web-es-su-propio-mcp-publicar-desde-cualquier-agente](https://gabrielherencia.com/insights/mi-web-es-su-propio-mcp-publicar-desde-cualquier-agente). Cerré diciendo que el siguiente paso eran las portadas generadas automáticamente. Acá están — y el camino dejó más aprendizajes que el resultado.

## El flujo completo

```mermaid
flowchart LR
  A["Agente IA<br/>(Claude, o el que sea)"] -->|"tool generar_portada<br/>vía MCP"| B["Next.js en Vercel"]
  B -->|prompt| C["Nano Banana 2<br/>(Gemini API)"]
  C -->|"imagen 1K"| D["sharp<br/>JPEG -85%"]
  D --> E[("Cloud Storage<br/>covers/slug-hash.jpg")]
  E --> F["Cloudflare Worker<br/>img.gabrielherencia.com"]
  B -->|cover_image_url| G[("Postgres")]
  B -->|revalidate| H["Web + OG image"]
```

El agente escribe el prompt de la imagen con libertad total y elige el formato según destino (16:9 para la web, cuadrado o vertical para redes). La tool hace el resto y el post queda con su portada online al instante.

## Lo que aprendí hoy

**No hizo falta Vertex.** Para usar los modelos de imagen de Gemini alcanza una API key de AI Studio: mismo modelo y mismo precio que Vertex, pero una sola variable de entorno en lugar de service accounts y SDKs. Ojo: los modelos de imagen no tienen free tier — la key necesita billing. Nano Banana 2 a 1K cuesta ~$0.07 por imagen; generar las portadas de todo este blog sale menos que un café.

**Cloud Storage desde serverless es un no-problema.** ¿Cómo usa una web en Vercel un bucket de GCS si no hay servidor? Una service account con permiso de escritura *solo sobre ese bucket*, y su key JSON en base64 dentro de una variable de entorno. Tres líneas para instanciar el cliente.

**El CDN con dominio propio pedía un Worker.** El CNAME clásico de GCS (`c.storage.googleapis.com`) es HTTP-only y exige verificar el dominio. Un Worker de Cloudflare de 20 líneas proxea el bucket por HTTPS real, cachea en el edge y me da `img.gabrielherencia.com`. Plan free, sobra.

**Optimizar antes de subir: -85%.** La imagen sale de Gemini pesando ~500 KB. Un paso de sharp (JPEG progresivo, mozjpeg, calidad 82) la deja en ~80 KB sin diferencia visible. Elegí JPEG y no WebP a propósito: la portada también es la `og:image`, y los scrapers de las redes sociales siguen siendo poco confiables con WebP.

**Nombres versionados por contenido.** Cada archivo se llama `slug-{hash}.jpg` y se sube con `Cache-Control: immutable`. Regenerar una portada produce una URL nueva: nunca hay que invalidar caché ni pelearse con el CDN.

**El bonus track: sharp rompió producción.** El primer deploy devolvía 500 en todo el MCP. ¿La causa? El bundler intentaba empaquetar los binarios nativos de sharp dentro de la función serverless. La solución: `serverExternalPackages` en la config de Next (que se resuelvan en runtime) e imports lazy, para que si una dependencia pesada falla, rompa una sola tool y no todo el servidor. Verdad de fondo aplicada: el código era correcto localmente; la evidencia (500 sin auth, resto del sitio sano) apuntaba al empaquetado, no a la lógica.

## La foto completa

Publicar un insight hoy es una conversación: lo redacto con un agente, el agente lo publica, genera la portada con el estilo que le pida, y si quiero lo comparte en LinkedIn. La web dejó de ser algo que mantengo y pasó a ser algo con lo que converso.
