1 · El problema · cada imagen arranca de cero
Generaste tu primera imagen con IA. Quedó linda. La segunda no se parece a la primera. La tercera tampoco. Todas se ven hechas por máquina, ninguna se ve hecha para tu marca.
Lo que falta no es el modelo. El modelo está bien. Lo que falta es contexto. Tu paleta, tu tono, tu público, lo que sí muestras y lo que nunca. Eso no vive en ninguna parte que el modelo pueda leer.
Cada vez tecleas el prompt de cero. Cada vez se te olvida un detalle. Cada vez sale distinta.
Este patrón cierra esa fuga. Tres piezas:
- Un cerebro de marca en un archivo
.mdque viaja con cada prompt. - Tu API key guardada en el Keychain de macOS, no en un
.env. - Un script de Python que junta las dos cosas y llama a Gemini.
Lo invocas desde tu terminal. O le pides a Claude Code dentro de VS Code que lo invoque por ti. Al final tienes imágenes consistentes con tu marca, sin repetir tu storytelling cada vez.
2 · La idea · cerebro · key · cliente
Antes del paso a paso, mira el flujo de lejos.
Cuatro piezas, una sola dirección:
- Cerebro (capa 1). Un
.mdque tiene tu marca por escrito. Es la fuente. - Cliente (capa 2). Un script Python que lee el cerebro, lee tu prompt y lee la key.
- Modelo (capa 3). Gemini recibe el cerebro como contexto y tu petición como instrucción.
- Salida (capa 4). Un PNG que ya viene con tu paleta y tu estilo.
La pieza opcional es la referencia visual. Si pasas una foto local (tu producto, tu cara, una textura), Gemini la usa como ancla y te devuelve variaciones que parten desde ahí.
El cerebro no es un prompt largo que copias y pegas cada vez. Vive en un archivo. El script lo concatena solo. Tú nunca te peleas con el contexto.
3 · Tu key de Gemini · 3 líneas
Pídela una sola vez en Google AI Studio.
Sacar la key
Entra a https://aistudio.google.com/app/apikey. Si no tienes proyecto, crea uno. Click en Create API key. Copia el valor (empieza con AIza...).
Guardarla en Keychain
Desde tu terminal (Mac). El -w sin valor te pregunta el token en interactivo, así no queda en .zsh_history:
security add-generic-password \
-a "santaia" \
-s "santaia:gemini-key" \
-l "Gemini API" \
-w
Cambia santaia por el nombre que uses tú. Ese mismo nombre va en el argumento --project del script.
Verificar que quedó
security find-generic-password -s "santaia:gemini-key" -w | head -c 12 && echo "..."
Si imprime los primeros 12 caracteres seguido de ..., está guardada. Listo.
Ya no la necesitas en ningún archivo. El script la pide al Keychain solo cuando va a llamar al modelo.
4 · La ficha tipo JARVIS de la key
El patrón completo no es solo guardar la key. Es dejar metadata de qué key es, dónde vive y cómo se rota. Yo lo hago con un archivo por servicio en una carpeta llamada 09-CREDENTIALS/.
La regla es simple: metadata en el .md, valor en el Keychain. Nunca al revés.
Esta es la ficha tipo que uso (la versión vacía está en el starter que descargas más abajo):
---
servicio: Google Gemini API Key
cuenta: [email protected]
proyectos_que_lo_usan:
- mi-proyecto
keychain_service: mi-proyecto:gemini-key
keychain_account: mi-proyecto
fecha_guardado: 2026-05-24
expira: no expira (revocable manualmente)
console_url: https://aistudio.google.com/app/apikey
---
# Gemini API Key · mi-proyecto
Una línea: para qué se usa en este proyecto.
## Cómo guardarla, leerla, borrarla
[comandos de security]
## Si se filtra
1. Revoca en console_url
2. Genera nueva
3. delete del Keychain
4. add de nuevo
5. Actualiza fecha_guardado
¿Por qué este patrón importa? Porque dentro de 6 meses, cuando rotes la key o entre alguien nuevo a tu equipo, este archivo es el que responde "dónde está esa cosa". El Keychain guarda el valor, la ficha guarda la historia.
5 · El cerebro de marca · qué va dentro
El cerebro es un .md con frontmatter. Lo que escribas ahí viaja con cada prompt. Mientras más concreto, más consistente sale la imagen.
Para el starter inventé una marca demo, una academia ficticia de panadería casera llamada Levadura Lenta:
---
marca: Levadura Lenta
mision: enseñar panadería casera a gente que nunca ha amasado
voz: cálida, paciente, sin tecnicismos
paleta:
primario: "#C97B4A"
secundario: "#F4E4C1"
acento: "#3D2817"
publico: principiantes 25-45 años, viven en ciudad
estilo_visual: fotografía cálida con grano, luz dorada, manos visibles, ingredientes humildes
do:
- mostrar el proceso, no solo el resultado
- personas reales, manos sucias de harina
dont:
- planos perfectos de revistas
- tipografía cursiva exagerada
---
Las 3 reglas para escribir un buen cerebro
Sé específico, no decorativo
"Fondo neutro" no le dice nada al modelo. "Mesa de madera oscura con luz lateral suave" sí. Si una palabra del cerebro la podrías reemplazar por su opuesto sin que cambie nada, sobra.
Hex codes, no nombres de colores
"Naranja terracota" lo interpreta cada modelo distinto. #C97B4A no. Nombra los colores por su hex en el frontmatter. El modelo los respeta.
Lista 2 o 3 dont claros
Las prohibiciones son lo que más diferencia tu marca de la imagen genérica. "Sin tipografía cursiva exagerada" te ahorra 30 iteraciones contra el default romántico de stock.
6 · El wrapper · keychain.py y generate_image.py
Dos piezas de código. La primera lee la key. La segunda llama a Gemini.
shared/secrets/keychain.py · wrapper de Keychain
Lee, guarda y borra secretos del Keychain de macOS. Lo importas desde el script principal. Si la key no existe, te dice exactamente qué comando correr para guardarla.
from __future__ import annotations
import subprocess
DEFAULT_PROJECT = "santaia"
def _service_name(key: str, project: str) -> str:
if ":" in key:
return key
return f"{project}:{key}"
def get_secret(key: str, project: str = DEFAULT_PROJECT) -> str | None:
try:
result = subprocess.run(
["security", "find-generic-password",
"-s", _service_name(key, project), "-w"],
capture_output=True, text=True, check=False,
)
except FileNotFoundError:
return None
return result.stdout.strip() if result.returncode == 0 else None
def require_secret(key: str, project: str = DEFAULT_PROJECT) -> str:
value = get_secret(key, project=project)
if value is None:
service = _service_name(key, project)
raise RuntimeError(
f"Secret '{service}' no encontrado en Keychain.\n"
f"Guárdalo con: security add-generic-password "
f"-s {service} -a {project} -w '<valor>' -U"
)
return value
(En el starter va completo con set_secret y delete_secret también.)
generate_image.py · el script CLI
Argumentos:
--prompt "texto": qué quieres ver (obligatorio).--ref foto.jpg: imagen local de referencia (opcional, activa modo B).--brain brand_brain.md: cerebro de marca (default./brand_brain.md).--out salida.png: ruta del PNG (default:out_<timestamp>.png).--project santaia: prefijo de tu proyecto en Keychain.
El flujo es directo:
from google import genai
from google.genai import types
from PIL import Image
from shared.secrets.keychain import require_secret
MODEL = "gemini-3-pro-image-preview"
api_key = require_secret("gemini-key", project=args.project)
client = genai.Client(api_key=api_key)
brain_text = Path(args.brain).read_text(encoding="utf-8")
full_prompt = (
"Generate an image following the brand brain below as visual + tone "
"context. The brand brain defines colors (use the hex codes exactly), "
"visual style, do/don't and audience.\n\n"
f"=== BRAND BRAIN ===\n{brain_text}\n=== END BRAND BRAIN ===\n\n"
f"User request: {args.prompt}"
)
contents = []
if args.ref:
contents.append(types.Part.from_bytes(
data=Path(args.ref).read_bytes(),
mime_type="image/jpeg",
))
contents.append(full_prompt)
resp = client.models.generate_content(
model=MODEL,
contents=contents,
config=types.GenerateContentConfig(response_modalities=["IMAGE"]),
)
raw = resp.candidates[0].content.parts[0].inline_data.data
Image.open(io.BytesIO(raw)).save(out_path, format="PNG")
7 · Modo A · texto a imagen
Una vez instalas dependencias (pip install google-genai pillow), la primera invocación es esta:
python generate_image.py \
--prompt "una hogaza de pan recién horneada sobre una mesa de madera oscura, luz lateral, vapor saliendo"
Output:
[modelo] gemini-3-pro-image-preview
[cerebro] brand_brain.md
[gemini] generando ...
[ok] guardada -> out_20260524-181830.png (1024x1024)
La imagen sale con la paleta y el mood del cerebro. Sin que tuvieras que repetir "estilo cálido, grano, manos visibles" en el prompt. Eso ya viajó adentro.
Cuando el resultado no es lo que querías
Dos caminos, en este orden:
Ajusta el prompt si fue un error de petición
Pediste "pan" y querías "croissant". Cambias el prompt y vuelves a correr.
Ajusta el cerebro si el problema se repite
Pediste "pan" y siempre te sale en un plano cenital perfecto de revista. El problema no es el prompt: es que tu cerebro no tiene dont: planos cenitales todavía. Edítalo. La siguiente vez sale distinto.
8 · Modo B · imagen a imagen con foto local
Aquí es donde la mayoría de APIs te dejan colgado. Kie.ai, por ejemplo, solo recibe URLs públicas. No puedes pasarle una foto que vive en tu disco.
Gemini sí. Le pasas la ruta local con --ref y la usa como ancla visual:
python generate_image.py \
--prompt "el mismo pan pero cortado a la mitad, mostrando la miga" \
--ref ./mi-hogaza.jpg
Output:
[ref] cargada mi-hogaza.jpg
[modelo] gemini-3-pro-image-preview
[cerebro] brand_brain.md
[gemini] generando ...
[ok] guardada -> out_20260524-182112.png (1024x1024)
El modelo recibe tres cosas en orden: la imagen de referencia, el cerebro de marca, tu petición. Gemini usa la foto como composición base y la regenera siguiendo el cerebro.
9 · Que Claude Code lo invoque por ti
Hasta acá invocas el script tú, desde tu terminal. El siguiente paso es no escribir el comando.
Abres VS Code en la carpeta del starter. Tienes Claude Code corriendo (si no, mira Claude Code recién instalado). Le dices en lenguaje natural:
genera 3 variaciones de una foto cenital de masa madre sobre mesa de madera,
con la luz de la tarde, siguiendo el brand brain. Guárdalas en ./posts/
Claude lee tu petición. Sabe que tiene un script generate_image.py. Construye el comando con --prompt y --out, lo corre 3 veces variando levemente el prompt, te entrega las 3 imágenes en la carpeta posts/.
Tú nunca tocas la terminal. La diferencia es de minutos a segundos.
10 · Descarga el starter
Te dejo el cerebro de marca demo, el wrapper de Keychain, el script con los dos modos, el inventario base de credenciales y un README con los 3 pasos para arrancar. Lo descomprimes en tu carpeta de proyectos y lo adaptas:
11 · Lo que tienes que recordar
- El cerebro de marca vive en un
.mdcon frontmatter. Hex codes explícitos, lista dedont. Eso es lo que hace consistente cada imagen. - La key vive en Keychain. La metadata vive en una ficha en
09-CREDENTIALS/. Nunca al revés. - El wrapper de Python lee la key solo cuando va a llamar al modelo. El valor no toca disco en ningún archivo del repo.
- Modo A para texto a imagen. Modo B para foto local de referencia. Cuando la referencia es local, el camino es Gemini.
- Itera el cerebro, no el prompt. Cuando un resultado no te guste, agrega una regla al cerebro y la próxima imagen ya sale distinto.
- Claude Code dentro de VS Code llama el script por ti en lenguaje natural. Tú nunca escribes el comando.
Qué viene después
Este patrón está atado a Gemini, que es lo que uso hoy. Pero la lógica (cerebro + key + wrapper + cliente) no depende del proveedor.
Video del canal · YouTubeMira el flujo entero en videoCerebro, Keychain, script, Claude Code en VS Code, todo en una sola sentada. Suscríbete y la próxima sale ahí primero.Ver en YouTube @Santa_lA
¿El cerebro te quedó pegado pero no logras que el script corra? ¿Tu primera imagen salió rara? Escríbeme por DM en @santaia.lab con el prompt y el resultado. Las dudas reales son material de la próxima guía.


