para generar el expediente digital.
Ve a Cloudflare Dashboard → Workers & Pages → Create y crea un nuevo Worker. El nombre que le pongas será parte de la URL del endpoint, por ejemplo: gemini-caso-abierto.tuusuario.workers.dev
Una vez creado, entra en Edit Worker, reemplaza el código por el contenido del fichero worker.js y pulsa Deploy.
const allowed = "https://mmaria.pages.dev" y cámbiala por el dominio real de MyPublicInbox antes de deploychear.1. Crea un proyecto en Google AI Studio.
→ Ir a Google AI Studio2. Crea una API key en el panel de API Keys. Cópiala ahora — la necesitarás en el paso siguiente.
3. Elige Tier 1 y añade prepago. El mínimo es 10€. Se carga a medida que consumes tokens.
→ Configurar facturación4. Establece un Spend Cap de 1€/mes. Equivale a unas 1.500 peticiones a Gemini 2.5 Flash. Si se agota, las peticiones devuelven error 429 — controlable, sin sorpresas en la factura.
En el Dashboard de Cloudflare, entra en tu Worker → Settings → Variables and Secrets. Añade una nueva variable de tipo Secret con este nombre exacto:
GEMINI_API_KEY
Pega el valor de tu API key y guarda. Cloudflare la encripta en reposo — ni tú podrás verla de nuevo desde el panel. El Worker la usa internamente como env.GEMINI_API_KEY.
const WORKER_URL = 'https://tu-worker.tuusuario.workers.dev';
async function generarExpediente(nombre) {
const res = await fetch(WORKER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nombre, modo: 'base' })
});
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = JSON.parse(await res.text());
if (data.error) throw new Error(data.error);
return data; // { nombre, epiteto, bloques[], conclusion }
}
try {
const data = await generarExpediente(nombre);
renderizarPlaybook(data);
} catch (e) {
const cod = e.message.match(/\d{3}/)?.[0];
if (cod === '503') mostrarError('Pico de demanda. Reintenta.'); // tokens: intactos
else if (cod === '429') mostrarError('Spend Cap agotado.'); // tokens: intactos
else if (cod === '502') mostrarError('Timeout. Reintenta.'); // tokens: consumidos (probablemente)
else mostrarError('Error: ' + e.message); // revisar Worker y key
}
| Error | Causa | Tokens | Acción |
|---|---|---|---|
| 503 | Gemini sobrecargado | Intactos | Reintento |
| 429 | Spend Cap agotado | Intactos | Ampliar cap |
| 502 | Timeout Worker (tier gratis) | Prob. consumidos | Worker de pago |
| otros | Config. errónea, CORS... | Varía | Revisar Worker y key |
{
"nombre": "Nombre analizado",
"epiteto": "2-4 palabras que resumen el patrón digital",
"bloques": [
{
"id": "titular", // o: narrativa | senales | identidad | oportunidades
"label": "Lo que Internet dice de ti",
"contenido":"Texto compacto · separado con · si hay items"
}
// ... 5 bloques en total
],
"conclusion": "Diagnóstico principal en 1 frase"
}
// Fragmenta texto separado por · en array limpio
function parseFrases(texto) {
if (!texto) return [];
return texto.split('·').map(f => f.trim()).filter(f => f.length > 0);
}
responseMimeType: "application/json" para forzar JSON puro, con sanitización de code fences como red de seguridad.El playbook no navega a otra página — se genera dinámicamente y se inyecta en un <iframe srcdoc="...">. Esto permite pantalla completa con requestFullscreen() y PDF con contentWindow.print() manteniendo estilos y fondos oscuros.
function renderizarPlaybook(data) {
const iframe = document.getElementById('playbook-iframe');
iframe.srcdoc = construirPlaybookHTML(data); // HTML completo generado por JS
document.getElementById('playbook-frame').classList.add('visible');
document.getElementById('playbook-toolbar-name').textContent = data.nombre;
}
// Pantalla completa (requiere interacción directa del usuario)
function abrirFullscreen() {
const iframe = document.getElementById('playbook-iframe');
if (iframe.requestFullscreen) iframe.requestFullscreen();
else if (iframe.webkitRequestFullscreen) iframe.webkitRequestFullscreen();
}
// PDF: funciona solo en remoto (no en localhost/file://)
function descargarPDF() {
const iframe = document.getElementById('playbook-iframe');
if (iframe && iframe.contentWindow) iframe.contentWindow.print();
}
requestFullscreen() si se llama desde un setTimeout o sin interacción directa del usuario. El botón de toolbar es la solución correcta — no intentar dispararlo automáticamente al cargar el iframe.Esta demo corre sobre un Spend Cap de 1€/mes — suficiente para probar, no para producción. En el producto real hay que implementar un cooldown de 1 semana por usuario para controlar la inversión en tokens.
function iniciaCooldown(segundos = 20) {
const btn = document.getElementById('btn-generar');
const fill = document.getElementById('cooldown-fill');
const lbl = document.getElementById('cooldown-label');
document.getElementById('cooldown-wrap').classList.add('visible');
btn.disabled = true;
fill.style.width = '100%';
let seg = segundos;
const iv = setInterval(() => {
seg--;
lbl.textContent = 'Cooldown: ' + seg + 's';
fill.style.width = (seg / segundos * 100) + '%';
if (seg <= 0) {
clearInterval(iv);
btn.disabled = false;
document.getElementById('cooldown-wrap').classList.remove('visible');
}
}, 1000);
}
En esta demo los playbooks se guardan en un array en memoria de sesión — al recargar desaparecen. En producción necesitamos persistencia para el cooldown de 7 días, el historial de informes anteriores, y re-renderizar sin gastar tokens nuevos.
Cloudflare KV es el almacén clave-valor de Cloudflare Workers. El JSON del informe (~2KB) se guardaría con la key user_id:semana y se recuperaría en el Worker antes de llamar a Gemini si ya existe un informe de esa semana.
Wrangler es la CLI oficial de Cloudflare para Workers. Requiere Node.js. La UI del Dashboard funciona perfectamente — esto es solo si prefieres terminal.
# Instalar Wrangler globalmente (requiere Node.js) npm install -g wrangler # Autenticarse con tu cuenta Cloudflare (abre navegador) wrangler login # Deploy del Worker desde la carpeta con worker.js wrangler deploy worker.js --name tu-worker-name # Configurar el secret (te pedirá el valor de la API key) wrangler secret put GEMINI_API_KEY