const { useState, useEffect, useCallback } = React; // ─── CONSTANTS ────────────────────────────────────────────────────────────── const USER_EMAIL = "juanmejia88@gmail.com"; const STORAGE_KEY = "radar-prefs-juan"; const ORGS = [ { name: "BCW Global / Burson", cat: "Consultora PA", priority: 5, twitter: "@BCW_Global", web: "bcwglobal.com" }, { name: "SEGIB", cat: "Org. Internacional", priority: 5, twitter: "@segib", web: "segib.org" }, { name: "FIIAPP", cat: "Organismo Público", priority: 5, twitter: "@fiiapp", web: "fiap.gob.es" }, { name: "Real Instituto Elcano", cat: "Think Tank", priority: 5, twitter: "@rielcano", web: "realinstitutoelcano.org" }, { name: "Club de Madrid", cat: "Org. Internacional", priority: 5, twitter: "@ClubdeMadrid", web: "clubmadrid.org" }, { name: "Comisión Europea España", cat: "Institución UE", priority: 5, twitter: "@ComisionEuropea", web: "spain.representation.ec.europa.eu" }, { name: "LLYC", cat: "Consultora PA", priority: 5, twitter: "@llorenteycuenca", web: "llyc.global" }, { name: "Kreab Madrid", cat: "Consultora PA", priority: 5, twitter: "@KreabES", web: "kreab.com/espana" }, { name: "Harmon Corporate Affairs", cat: "Consultora PA", priority: 5, twitter: "@harmonCA", web: "harmon.es" }, { name: "Fundación Carolina", cat: "Think Tank", priority: 5, twitter: "@fcarolina_es", web: "fundacioncarolina.es" }, { name: "Embajada de EE.UU. (Madrid)", cat: "Embajada", priority: 5, twitter: "@USembassyMadrid", web: "es.usembassy.gov" }, { name: "APRI", cat: "Asociación Prof.", priority: 5, twitter: "@Lobby_ES", web: "relacionesinstitucionales.es" }, { name: "AmCham Spain", cat: "Cámara / Red", priority: 5, twitter: "@AmChamSpain", web: "amchamspain.com" }, { name: "Civio", cat: "ONG / Media", priority: 5, twitter: "@civio", web: "civio.es" }, { name: "FTI Consulting Madrid", cat: "Consultora PA", priority: 5, twitter: "@FTIConsulting", web: "fticonsulting.com/en/spain" }, { name: "ECFR Madrid", cat: "Think Tank", priority: 4, twitter: "@ecfr", web: "ecfr.eu/madrid" }, { name: "Casa de América", cat: "Inst. Público", priority: 4, twitter: "@casamerica", web: "casamerica.es" }, { name: "ACOP", cat: "Asociación Prof.", priority: 4, twitter: "@compolitica", web: "compolitica.com" }, { name: "Newtral", cat: "Media", priority: 4, twitter: "@newtral_media", web: "newtral.es" }, { name: "Agencia EFE", cat: "Media", priority: 4, twitter: "@EFEnoticias", web: "efe.com" }, { name: "AECID", cat: "Organismo Público", priority: 4, twitter: "@AECID_es", web: "aecid.es" }, { name: "Atrevia", cat: "Consultora PA", priority: 4, twitter: "@atrevia", web: "atrevia.com" }, { name: "beBartlet", cat: "Consultora PA", priority: 4, twitter: "@beBartlet", web: "bebartlet.com" }, { name: "NITID Corporate Affairs", cat: "Consultora PA", priority: 4, twitter: "@NITIDcorp", web: "masconsulting.es" }, { name: "Gobe Studio", cat: "GovTech", priority: 4, twitter: "@gobestudio", web: "gobe.studio" }, { name: "Maldita.es", cat: "Media", priority: 4, twitter: "@malditaes", web: "maldita.es" }, { name: "DIRCOM", cat: "Asociación Prof.", priority: 3, twitter: "@DIRCOM_ES", web: "dircom.org" }, { name: "IE School of Politics", cat: "Universidad", priority: 3, twitter: "@IE_SPEGA", web: "ie.edu" }, { name: "Casa Árabe", cat: "Inst. Público", priority: 3, twitter: "@casaarabe", web: "casaarabe.es" }, ]; const CATS = ["Todas", ...Array.from(new Set(ORGS.map(o => o.cat))).sort()]; const CAT_COLOR = { "Consultora PA": "#B8860B", "Org. Internacional": "#0B7A80", "Organismo Público": "#1A7A4A", "Think Tank": "#7B2D8B", "Institución UE": "#1B3A6B", "Embajada": "#2E4A8B", "Asociación Prof.": "#16A085", "Cámara / Red": "#8E44AD", "ONG / Media": "#C0392B", "Media": "#A93226", "Inst. Público": "#2E6DB4", "GovTech": "#117A65", "Universidad": "#566573", }; // ─── EMAIL HTML BUILDER ───────────────────────────────────────────────────── function buildEmailHtml(results, orgs, searchType, dateStr) { const typeLabels = { eventos: "Eventos & Actos Próximos", vacantes: "Vacantes Activas", noticias: "Noticias Recientes", }; const typeIcons = { eventos: "📅", vacantes: "💼", noticias: "📰" }; return ` Radar Organizaciones · ${typeLabels[searchType]}

Juan Mejía Saravia · Madrid

Radar de Organizaciones

${typeIcons[searchType]} ${typeLabels[searchType]} · ${dateStr}

Organizaciones escaneadas

${orgs.join(" · ")}

${results}

Radar · Plan Estratégico 2026

Generado automáticamente · juanmejiasaravia.com

`; } function textToEmailHtml(text) { if (!text) return "

No se encontraron resultados para esta búsqueda.

"; const lines = text.split("\n").filter(l => l.trim()); let html = ""; for (const line of lines) { const t = line.trim(); if (t.startsWith("##") || t.startsWith("###")) { const content = t.replace(/^#+\s*/, "").replace(/\*\*/g, ""); html += `

${content}

`; } else if (t.startsWith("**") && t.endsWith("**")) { html += `

${t.replace(/\*\*/g, "")}

`; } else if (t.startsWith("- ") || t.startsWith("• ")) { html += `

${t.replace(/^[-•]\s*/, "").replace(/\*\*(.+?)\*\*/g, "$1")}

`; } else if (t) { html += `

${t.replace(/\*\*(.+?)\*\*/g, "$1")}

`; } } return html; } // ─── MAIN COMPONENT ───────────────────────────────────────────────────────── function RadarAlertas() { const [tab, setTab] = useState("radar"); const [selected, setSelected] = useState([]); const [catFilter, setCatFilter] = useState("Todas"); const [searchType, setSearchType] = useState("eventos"); const [status, setStatus] = useState("idle"); // idle | searching | sending | done | error const [rawText, setRawText] = useState(""); const [searchQueries, setSearchQueries] = useState([]); const [lastSent, setLastSent] = useState(null); const [history, setHistory] = useState([]); const [toast, setToast] = useState(null); const [previewMode, setPreviewMode] = useState(false); // ── Persistent storage ────────────────────────────────────────────────── useEffect(() => { try { const res = localStorage.getItem(STORAGE_KEY); if (res) { const saved = JSON.parse(res); if (saved.selected?.length) setSelected(saved.selected); if (saved.searchType) setSearchType(saved.searchType); if (saved.history) setHistory(saved.history); if (saved.lastSent) setLastSent(saved.lastSent); } } catch (_) { console.log("No saved prefs yet or localstorage blocked"); } }, []); const savePrefs = useCallback((sel, type, hist, sent) => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify({ selected: sel, searchType: type, history: hist, lastSent: sent, })); } catch (_) { } }, []); const showToast = (msg, type = "ok") => { setToast({ msg, type }); setTimeout(() => setToast(null), 4000); }; const toggleOrg = (name) => { const next = selected.includes(name) ? selected.filter(n => n !== name) : [...selected, name]; setSelected(next); savePrefs(next, searchType, history, lastSent); }; const filtered = catFilter === "Todas" ? ORGS : ORGS.filter(o => o.cat === catFilter); const selectedOrgs = ORGS.filter(o => selected.includes(o.name)); // ── SEARCH ────────────────────────────────────────────────────────────── const PROMPTS = { eventos: (orgs) => `Busca eventos, conferencias, seminarios, foros, webinars y actos públicos próximos (marzo-mayo 2026) organizados por o relacionados con estas organizaciones en España/Madrid: ${orgs.join(", ")}. Para cada evento encontrado indica: nombre del evento, organizador, fecha aproximada, formato (presencial/online/híbrido), ubicación si aplica, y URL o fuente donde inscribirse. Si no hay eventos confirmados aún, menciona los eventos recurrentes que estas organizaciones suelen organizar y cómo recibir avisos (newsletter, LinkedIn). Responde en español, estructura por organización con secciones claras.`, vacantes: (orgs) => `Busca vacantes de empleo activas (enero-marzo 2026) en estas organizaciones de Madrid/España: ${orgs.join(", ")}. Enfócate en puestos de: comunicación estratégica, asuntos públicos, relaciones institucionales, proyectos UE, cooperación internacional. Para cada vacante indica: título, organización, requisitos clave, y URL de la oferta. Responde en español, estructura por organización.`, noticias: (orgs) => `Busca las noticias y publicaciones más relevantes de enero-febrero 2026 de estas organizaciones: ${orgs.join(", ")}. Para cada una resume el anuncio más importante y su relevancia para un profesional de comunicación estratégica y asuntos públicos en Madrid. Responde en español.`, }; const runSearch = async () => { if (!selected.length) return; setStatus("searching"); setRawText(""); setSearchQueries([]); setPreviewMode(false); const orgNames = selected.map(n => ORGS.find(o => o.name === n)?.name).filter(Boolean); try { // Connect to our secure PHP backend instead of direct API call const resp = await fetch("radar_api.php?action=search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: PROMPTS[searchType](orgNames) }), }); const data = await resp.json(); if (!data.success) throw new Error(data.message || "Error en la búsqueda"); let text = data.text || ""; let queries = data.queries || []; setRawText(text.trim()); setSearchQueries(queries); setStatus("idle"); setPreviewMode(true); showToast("Búsqueda completada", "ok"); } catch (e) { console.error(e); setStatus("error"); showToast("Error en búsqueda: " + e.message, "error"); setStatus("idle"); } }; // ── SEND EMAIL ──────────────────────────────── const sendDigest = async () => { if (!rawText) return; setStatus("sending"); const dateStr = new Date().toLocaleDateString("es-ES", { day: "numeric", month: "long", year: "numeric" }); const typeLabel = { eventos: "📅 Eventos Próximos", vacantes: "💼 Vacantes Activas", noticias: "📰 Noticias Recientes" }; const subjectLine = `Radar Organizaciones · ${typeLabel[searchType]} · ${dateStr}`; const emailHtml = buildEmailHtml(textToEmailHtml(rawText), selected, searchType, dateStr); const plainText = `Radar de Organizaciones · ${typeLabel[searchType]} · ${dateStr}\n\nOrganizaciones escaneadas: ${selected.join(", ")}\n\n${rawText}`; try { // Connect to our secure PHP backend for email const resp = await fetch("radar_api.php?action=email", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ to: USER_EMAIL, subject: subjectLine, html: emailHtml, text: plainText }), }); const data = await resp.json(); if (data.success) { const newEntry = { id: Date.now(), date: dateStr, type: searchType, orgs: [...selected], preview: rawText.slice(0, 200) + "…", }; const newHistory = [newEntry, ...history].slice(0, 10); setHistory(newHistory); setLastSent(dateStr); savePrefs(selected, searchType, newHistory, dateStr); setStatus("idle"); showToast(`✉️ Digest enviado a ${USER_EMAIL}`, "ok"); } else { throw new Error(data.message || "Error al enviar"); } } catch (e) { console.error(e); setStatus("idle"); showToast("Error al enviar: " + e.message, "error"); } }; // ─── UI ────────────────────────────────────────────────────────────────── const isBusy = status === "searching" || status === "sending"; return (
{/* Animated background */}
{/* TOAST */} {toast && (
{toast.msg}
)} {/* HEADER */}
SISTEMA DE ALERTAS · JUAN MEJÍA SARAVIA

Radar de Organizaciones

📧 {USER_EMAIL} {lastSent && Último envío: {lastSent}}
{/* TABS */}
{/* ══ TAB: RADAR ══════════════════════════════════════════════════════ */} {tab === "radar" && (
{/* LEFT: controls */}
{/* Search type */}
Tipo de búsqueda
{[ { k: "eventos", icon: "📅", label: "Eventos próximos", desc: "Conferencias, foros, seminarios" }, { k: "vacantes", icon: "💼", label: "Vacantes activas", desc: "Comunicación, PA, proyectos UE" }, { k: "noticias", icon: "📰", label: "Noticias recientes", desc: "Anuncios y publicaciones clave" }, ].map(({ k, icon, label, desc }) => ( ))}
{/* Orgs summary */}
Organizaciones seleccionadas 0 ? "#7ECFD4" : "#567" }}> {selected.length} de {ORGS.length}
{selected.length === 0 ? (

Ve a "Mis Organizaciones" para configurar tu watchlist.

) : (
{selectedOrgs.slice(0, 10).map(o => ( {o.name} ))} {selected.length > 10 && ( +{selected.length - 10} más )}
)}
{/* Action buttons */} {rawText && !isBusy && ( )} {searchQueries && searchQueries.length > 0 && (
Búsquedas web realizadas
{searchQueries.map((q, i) => (
🔍 {q}
))}
)}
{/* RIGHT: results */}
{status === "searching" && (
🛰️
Escaneando {selected.length} organizaciones…
Buscando con Anthropic en forma segura
)} {status === "sending" && (
📧
Enviando digest a tu Gmail…
)} {!isBusy && rawText && (
Resultados de búsqueda
{previewMode ? (
) : (
{rawText.split("\n").map((line, i) => { const t = line.trim(); if (!t) return
; if (t.startsWith("##")) return

{t.replace(/^#+\s*/, "")}

; if (t.startsWith("**") && t.endsWith("**")) return

{t.replace(/\*\*/g, "")}

; if (t.startsWith("- ") || t.startsWith("• ")) return (

{t.replace(/^[-•]\s*/, "").replace(/\*\*(.+?)\*\*/g, "$1")}

); // replace bold inside paragraph const pContent = t.replace(/\*\*(.*?)\*\*/g, "$1"); return

; })}
)}
)} {!isBusy && !rawText && (
📡
Lanza una búsqueda para ver resultados
1. Selecciona el tipo de alerta
2. Pulsa "Buscar ahora"
3. Revisa los resultados
4. Envía el digest a tu correo seguro
)}
)} {/* ══ TAB: CONFIG ═════════════════════════════════════════════════════ */} {tab === "config" && (
{selected.length} seleccionadas · Guardado en tu equipo
{filtered.map(org => { const isSel = selected.includes(org.name); const cc = CAT_COLOR[org.cat] || "#555"; return (
toggleOrg(org.name)} style={{ ...S.orgCard, background: isSel ? `rgba(${hexRgb(cc)},0.12)` : "rgba(255,255,255,0.03)", borderColor: isSel ? cc : "rgba(255,255,255,0.08)", }} >
{isSel && }
{org.name}
{"⭐".repeat(org.priority)} · {org.cat}
{org.twitter} · {org.web}
); })}
)} {/* ══ TAB: HISTORY ════════════════════════════════════════════════════ */} {tab === "history" && (
{history.length === 0 ? (
📋
Aún no has enviado ningún digest
Los digests enviados aparecerán aquí
) : (
{history.map(entry => (
{{ eventos: "📅", vacantes: "💼", noticias: "📰" }[entry.type]} {" "} {{ eventos: "Eventos próximos", vacantes: "Vacantes activas", noticias: "Noticias recientes" }[entry.type]}
{entry.date}
{entry.orgs.length} organizaciones: {entry.orgs.slice(0, 4).join(", ")}{entry.orgs.length > 4 ? `…` : ""}
Enviado ✓
{entry.preview}
))}
)}
)} {/* ══ TAB: AUTOMATIZACIÓN ═════════════════════════════════════════════ */} {tab === "auto" && (
Automatización real: Google Apps Script
Para recibir el digest automáticamente cada semana sin abrir nada, usa este script gratuito de Google.
{[ { step: "1", title: "Ir a Google Apps Script", desc: "Abre script.google.com con tu cuenta de Google (juanmejia88@gmail.com)." }, { step: "2", title: "Crear nuevo proyecto", desc: 'Haz clic en "Nuevo proyecto" y dale un nombre: "Radar Organizaciones Madrid".' }, { step: "3", title: "Pegar el código", desc: 'Copia el código de abajo en el editor y reemplaza TU_API_KEY_ANTHROPIC con tu clave de la API de Anthropic (console.anthropic.com).' }, { step: "4", title: "Configurar el trigger semanal", desc: 'Ve a Triggers (reloj) → "+ Añadir trigger" → función "enviarRadar" → Accionado por tiempo → Semana → Lunes 08:00.' }, { step: "5", title: "Autorizar y activar", desc: "Ejecuta la función una vez manualmente para autorizar los permisos de Gmail. Desde ese momento llegará automáticamente cada semana." }, ].map(({ step, title, desc }) => (
{step}
{title}
{desc}
))}
💡 Alternativas sin código
{[ { app: "Google Alerts", desc: "Crea alertas gratuitas en alerts.google.com con el nombre de cada organización. Llegan a tu Gmail automáticamente.", url: "alerts.google.com" }, { app: "Make.com (ex-Integromat)", desc: "Conecta una búsqueda periódica con Gmail usando su interfaz visual. Sin código.", url: "make.com" }, { app: "Zapier", desc: "Monitoriza páginas web y envía alertas a Gmail cuando detecta cambios (plan gratuito disponible).", url: "zapier.com" }, { app: "LinkedIn Alertas", desc: "Sigue las páginas de las organizaciones en LinkedIn y activa notificaciones de publicaciones nuevas.", url: "linkedin.com" }, ].map(({ app, desc, url }) => (
{app} ({url})
{desc}
))}
)}
{/* Global CSS for the internal component */}