Radar de tendencias AI (X)
Métricas Clave
Puntuación Final = Popularidad en X × Frescura del Proyecto
puntuación_cruda = likes × log(1 + likes_por_hora)- Velocidad = likes / (tiempo actual - tiempo de publicación) en horas
- Verificación de frescura del proyecto: Tras detectar un proyecto en GitHub, se debe navegar a la página del repositorio para validar su fecha de creación.
puntuación_final = puntuación_cruda × multiplicador_frescura- Proyecto < 7 días: × 1.0
- Proyecto 7-30 días: × 0.7
- Proyecto 30-90 días: × 0.3
- Proyecto > 90 días: Marcado como ⚠️ Proyecto antiguo reciclado, no participa en el ranking.
🔴 Lección Clave: UI-TARS-desktop (bytedance) tiene 29K estrellas pero el repositorio existe desde hace meses. El post en X era nuevo pero el proyecto no → Oportunidad de arbitraje cero. Alta popularidad no implica proyecto nuevo.
Matriz de Búsqueda (6 Capas de Detección)
Cada capa tiene un objetivo distinto, cubriendo toda la cadena: punto caliente salvaje → lanzamiento de producto → viralización → explosión de código abierto → necesidad FOMO.
L0: Virus Salvaje (ventana de 48h) 🆕
Detecta productos/juegos/herramientas que se viralizan sin etiqueta de IA. Es la capa clave para capturar juegos H5, herramientas web y otros puntos calientes no relacionados con IA.
("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (game OR tool OR app OR website OR "web app" OR builder OR generator OR platform OR "waitlist") min_faves:200 since:SINCE_48H
⚠️ Nota: L0 no requiere palabras clave de IA, se eleva min_faves a 200 para filtrar ruido.
L1: Lanzamiento de Nuevo Producto - IA (ventana de 24h)
Captura la señal más temprana de herramientas de IA "recién lanzadas/de código abierto".
("just launched" OR "just dropped" OR "just shipped" OR "new AI tool" OR "just released" OR "introducing" OR "launching today") (AI OR LLM OR GPT OR agent OR open source) min_faves:30 since:SINCE_24H
L1-b: Lanzamiento de Nuevo Producto General (ventana de 24h) 🆕
Captura nuevos lanzamientos de productos/juegos/herramientas no relacionados con IA.
("just launched" OR "just dropped" OR "just shipped" OR "just released" OR "introducing" OR "launching today" OR "new" OR "announcing") (game OR tool OR app OR website OR "web app" OR builder OR platform) min_faves:100 since:SINCE_24H
L2: Propagación Viral (ventana de 48h)
Captura productos de IA que están siendo masivamente compartidos.
("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (AI OR LLM OR GPT OR agent OR tool) min_faves:100 since:SINCE_48H
L3: Explosión de Estrellas en GitHub (ventana de 48h)
Proyectos de IA de código abierto que explotan repentinamente.
("github.com" OR "open source") (AI OR LLM OR GPT OR agent) ("stars" OR "trending" OR "blew up" OR "blowing up" OR "just hit") min_faves:100 since:SINCE_48H
L4: FOMO / Lista de Espera (ventana de 72h)
Esperar, pedir invitación, largas colas = señal de demanda desbordada.
("waitlist" OR "beta access" OR "invite" OR "early access" OR "can't wait" OR "need this") (AI OR LLM OR tool OR app) min_faves:50 since:SINCE_72H
Flujo de Ejecución
[Cron: Cada 12 horas]
↓
[Inyectar cookie de X en el navegador → Estado de sesión iniciada]
↓
[Búsqueda por capas L0-L4 → Recopilación con scroll en JS]
↓
[Combinar y deduplicar → Calcular puntuación_cruda]
↓
[🔍 Paso 2.5: Verificación de frescura del proyecto]
↓
[Extracción con IA: palabra clave + nombre del producto + antigüedad del proyecto + resumen en una frase]
↓
[Ordenar Top 10 → Generar reporte]
↓
[Enviar directamente a Feishu]
Paso 1: Construir URL de Búsqueda
const now = new Date();
const since24h = new Date(now - 24*60*60*1000).toISOString().split('T')[0];
const since48h = new Date(now - 48*60*60*1000).toISOString().split('T')[0];
const since72h = new Date(now - 72*60*60*1000).toISOString().split('T')[0];
const SEARCH_GROUPS = [
{
layer: 'L0-VirusSalvaje',
q: `("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (game OR tool OR app OR website OR "web app" OR builder OR generator OR platform OR "waitlist") min_faves:200 since:${since48h}`,
since: since48h
},
{
layer: 'L1-NuevoProducto-IA',
q: `("just launched" OR "just dropped" OR "just shipped" OR "new AI tool" OR "just released" OR "introducing") (AI OR LLM OR GPT OR agent OR "open source") min_faves:30 since:${since24h}`,
since: since24h
},
{
layer: 'L1-b-NuevoProductoGeneral',
q: `("just launched" OR "just dropped" OR "just shipped" OR "just released" OR "introducing" OR "launching today" OR "new" OR "announcing") (game OR tool OR app OR website OR "web app" OR builder OR platform) min_faves:100 since:${since24h}`,
since: since24h
},
{
layer: 'L2-PropagacionViral',
q: `("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (AI OR LLM OR GPT OR agent OR tool) min_faves:100 since:${since48h}`,
since: since48h
},
{
layer: 'L3-ExplosionGitHub',
q: `("github.com" OR "open source") (AI OR LLM OR GPT OR agent) ("stars" OR "trending" OR "blew up" OR "blowing up" OR "just hit") min_faves:100 since:${since48h}`,
since: since48h
},
{
layer: 'L4-FOMO',
q: `("waitlist" OR "beta access" OR "invite" OR "early access" OR "can't wait" OR "need this") (AI OR LLM OR tool OR app) min_faves:50 since:${since72h}`,
since: since72h
}
];
Paso 2: Recolectar y Calcular Puntuación de Calor
// Ejecutar en la consola del navegador
async function collectAndScore(sinceHours) {
let allPosts = [];
let prevCount = 0;
let scrolls = 0;
const maxScrolls = 12;
while (scrolls < maxScrolls) {
const articles = document.querySelectorAll('article');
articles.forEach((a) => {
const textEl = a.querySelector('[data-testid="tweetText"]') || a.querySelector('[lang]');
const text = textEl?.innerText || '';
if (!text || text.length < 30) return;
const likesEl = a.querySelector('[data-testid="like"] span');
let likes = 0;
if (likesEl) {
const txt = likesEl.innerText.replace(/,/g, '');
if (txt.includes('K')) likes = Math.round(parseFloat(txt.replace('K','')) * 1000);
else if (txt.includes('M')) likes = Math.round(parseFloat(txt.replace('M','')) * 1000000);
else likes = parseInt(txt) || 0;
}
// Extraer autor
const allLinks = Array.from(a.querySelectorAll('a[role="link"]'));
const authorLink = allLinks.find(l => l.href && l.href.includes('x.com/') && !l.href.includes('/status/') && !l.href.includes('search') && !l.href.includes('hashtag'));
const author = authorLink?.href?.split('/').pop() || '';
// Extraer tiempo
const timeEl = a.querySelector('time');
const time = timeEl?.getAttribute('datetime') || '';
// Extraer enlace
const statusLink = allLinks.find(l => l.href && l.href.includes('/status/'));
const link = statusLink?.href || '';
// Extraer número de retweets
const repostEl = a.querySelector('[data-testid="retweet"] span, [data-testid="repost"] span');
let reposts = 0;
if (repostEl) {
const txt = repostEl.innerText.replace(/,/g, '');
if (txt.includes('K')) reposts = Math.round(parseFloat(txt.replace('K','')) * 1000);
else reposts = parseInt(txt) || 0;
}
if (!allPosts.some(p => p.text === text)) {
allPosts.push({ text: text.substring(0, 400), likes, reposts, author, time, link });
}
});
if (allPosts.length === prevCount && scrolls > 2) break;
prevCount = allPosts.length;
window.scrollBy(0, 600);
await new Promise(r => setTimeout(r, 1500));
scrolls++;
}
// Calcular puntuación de calor
const now = new Date();
return allPosts.map(p => {
const postTime = new Date(p.time);
const hoursAgo = Math.max(0.1, (now - postTime) / (1000 * 60 * 60));
const likesPerHour = p.likes / hoursAgo;
const hotScore = Math.round(p.likes * Math.log(1 + likesPerHour));
return { ...p, hoursAgo: Math.round(hoursAgo * 10) / 10, likesPerHour: Math.round(likesPerHour), hotScore };
}).sort((a, b) => b.hotScore - a.hotScore);
}
Paso 2.5: Verificación de Frescura del Proyecto (🔴 Obligatorio)
⚠️ Popularidad en X no implica proyecto nuevo. Un proyecto antiguo puede ser muy popular en X si se retoma, pero la ventana de arbitraje ya se cerró.
Primero revisar la lista de proyectos antiguos conocidos: references/known-stale-repos.md — los que coincidan se excluyen directamente, sin necesidad de navegar a GitHub.
Para cada señal que contenga un enlace de GitHub, es obligatorio navegar a la página del repositorio para verificar la fecha de creación:
// Ejecutar en la página del repositorio de GitHub
const createdEl = document.querySelector('relative-time');
const createdAt = createdEl?.getAttribute('datetime') || '';
const daysSinceCreated = (new Date() - new Date(createdAt)) / (1000 * 60 * 60 * 24);
let freshnessLabel;
if (daysSinceCreated < 7) freshnessLabel = '🆕 Nuevo';
else if (daysSinceCreated < 30) freshnessLabel = '📈 En ascenso';
else if (daysSinceCreated < 90) freshnessLabel = '⚠️ Proyecto antiguo reciclado';
else freshnessLabel = '💀 Anticuado (Excluido)';
const freshnessMultiplier = daysSinceCreated < 7 ? 1.0 : daysSinceCreated < 30 ? 0.7 : daysSinceCreated < 90 ? 0.3 : 0;
const finalScore = Math.round(rawHotScore * freshnessMultiplier);
Regla de filtro: Los proyectos con freshnessMultiplier === 0 se excluyen directamente, no aparecen en el reporte.
Para proyectos no basados en GitHub (como lanzamientos de nuevos productos, APIs, listas de espera), el multiplicador de frescura predeterminado es 0.8 (dando cierto margen a proyectos no open source), pero si se puede extraer la fecha de lanzamiento de la página, se usará la fecha real.
Paso 3: Reglas de Filtro
// Excluir temas: crypto, guerra, NSFW, meme coin
const excludePatterns = [
/crypto|bitcoin|nft|token.*sale/i,
/war|killed|attack|bomb/i,
/nsfw|onlyfans|porn/i,
/ai.*girlfriend|ai.*waifu/i,
/meme.*coin/i
];
const filtered = allPosts
.filter(p => p.hotScore >= 200) // Umbral de puntuación de calor
.filter(p => !excludePatterns.some(r => r.test(p.text)))
.slice(0, 15);
Paso 4: Extracción de Palabras Clave con IA
Ejecutar el siguiente prompt para cada post:
Extrae las palabras clave de tendencia del siguiente post de X (sin limitarse a IA):
Post: {{ text }}
Publicado por: @{{ author }}
Likes: {{ likes }} | Retweets: {{ reposts }}
Publicado hace: {{ hoursAgo }} horas | Velocidad: {{ likesPerHour }} likes/hora
Salida:
- **Palabra clave / Nombre del producto**: [Extraer 1-3 palabras clave principales]
- **Resumen**: [Qué hace esta herramienta/proyecto]
- **Puntuación de calor**: [hotScore]
- **Señal de arbitraje**: Fuerte/Media/Débil (velocidad >500 likes/h = Fuerte, 100-500 = Media, <100 = Débil)
Paso 5: Formato del Reporte de Salida
Estructura de salida JSON (demand-radar.json)
Cada señal debe contener todos los siguientes campos:
{
"date": "YYYY-MM-DD",
"signals": [{
"rank": 1,
"keyword": "Nombre del producto/proyecto",
"description": "Descripción en una frase de qué hace",
"likes": 188, "velocity": 109, "hotScore": 884,
"freshness": "🆕 Nuevo", "finalScore": 884, "signal": "🟡 Media",
"author": "@handle",
"tweetLink": "https://x.com/user/status/xxx",
"hoursAgo": 1.7,
"github": "owner/repo", "stars": "2.8K",
"sourceLayer": "L1 Lanzamiento de Nuevo Producto-IA",
"arbitrageDirection": "Dirección de arbitraje + dominio recomendado",
"domainSuggestions": ["dominio1.com", "dominio2.com"],
"actionPlan": "Plan de acción concreto: qué hacer, cómo hacerlo, por qué ahora es el momento",
"painPoint": "Punto de dolor del usuario: por qué alguien necesita esto"
}]
}
🔴 Regla clave: Cada señal debe incluir tweetLink (enlace al post original), arbitrageDirection (dirección de arbitraje), domainSuggestions (sugerencias de dominio), actionPlan (plan de acción), painPoint (punto de dolor). Las señales que falten alguno de estos campos se consideran incompletas y no aparecerán en el reporte.
🔍 Radar de Tendencias AI | {{DATE}} {{TIME}}
> **Fuente de datos: Captura en tiempo real de X/Twitter @claw0x**
## 📰 Barra lateral de Noticias de Hoy
| Tema | Hora | Número de Posts |
|------|------|-----------------|
| {{topic}} | {{age}} | {{posts}} |
## 🔥 Top 10 de Popularidad
| # | Palabra clave | Likes | Velocidad (likes/h) | Puntuación de calor | Frescura | Puntuación final | Señal de arbitraje |
|---|---------------|-------|---------------------|--------------------|---------|-----------------|-------------------|
| 1 | xxx | 5.2K | 1,200 | 36,000 | 🆕 0.8 días | 36,000 | 🔴 Fuerte |
### 🚀 Acción Inmediata (Señal de arbitraje ≥ Media)
1. **{{keyword}}** — {{summary}}
- @{{author}} | {{likes}} likes | {{likesPerHour}} likes/hora | {{hoursAgo}}h atrás
- GitHub: `{{owner/repo}}` | Creado hace {{daysAgo}} días | {{stars}} estrellas
- Dominio: {{domain}}.com ({{status}})
- Dirección de arbitraje: {{arbitrageDirection}}
## ⚠️ Excluidos (Proyectos Antiguos Reciclados)
| Proyecto | Razón | Estrellas | Antigüedad |
|----------|-------|-----------|------------|
| UI-TARS-desktop (ByteDance) | Agente de automatización de escritorio, retomado en X | 30.9K | >12 meses |
---
📊 Parámetros de Escaneo
- Capas de búsqueda: L0 Salvaje / L1 Nuevo-IA / L1-b Nuevo-General / L2 Viral / L3 GitHub / L4 FOMO
- Rango de tiempo: {{since}} - {{until}}
- Posts originales: {{raw}} → Deduplicados {{deduped}} → Filtrados {{filtered}} → Seleccionados {{top}}
- Verificación GitHub: Excluidos {{excluded}} proyectos antiguos
- Estado del navegador: Sesión iniciada @claw0x
## 🔮 Perspectivas Clave
(Listar 2-4 tendencias clave detectadas en el escaneo de hoy)
Paso 6: Despliegue (git push → CF Pages)
cd /Volumes/sunzi/Code/ludusAI
# Guardar instantánea JSON por día para las páginas de detalle /radar
cp public/data/demand-radar.json "public/data/radar/YYYY-MM-DD.json" 2>/dev/null || true
# Construir manifiesto
ls public/data/radar/*.json | python3 -c "
import json,os,sys
m=[]
for f in sys.stdin.read().strip().split():
d=json.load(open(f))
t=d.get('top10',[])
m.append({'date':os.path.basename(f).replace('.json',''),'topKeywords':[x.get('keyword','') for x in t[:3]],'signalCount':len(t),'newsTopics':[n.get('topic','') for n in d.get('news',[])[:2]],'insights':d.get('insights',[])[:3],'strongSignal':sum(1 for x in t if 'Fuerte' in x.get('signal','')),'mediumSignal':sum(1 for x in t if 'Media' in x.get('signal','')),'raw':d.get('scanParams',{}).get('raw',0),'top':d.get('scanParams',{}).get('top',0),'excluded':d.get('scanParams',{}).get('githubExcluded',0),'layers':d.get('scanParams',{}).get('layers',[])})
m.sort(key=lambda x:x['date'],reverse=True)
json.dump(m,open('public/data/radar-manifest.json','w'),ensure_ascii=False,indent=2)
print(f'manifesto: {len(m)} reportes')
"
git add daily-reports/YYYY-MM-DD/x-demand-radar.md public/data/demand-radar.json public/data/radar/YYYY-MM-DD.json public/data/radar-manifest.json
git commit -m "radar: YYYY-MM-DD HH:MM CST - <resumen de señal superior>"
git pull --rebase && git push # ⚠️ Obligatorio hacer rebase para evitar rechazo por non-fast-forward
⚠️ Es obligatorio usar send_message para enviar a Feishu, no depender de cron announce.
Inyección de Cookie (Antes de Cada Ejecución)
⚠️ Ver detalles completos en references/x-auth-and-collection.md — Incluye flujo completo de inyección, template de IIFE asíncrono, análisis de K/M, fórmula de hotScore.
// Primero navegar a x.com, luego inyectar, y finalmente verificar inicio de sesión en x.com/home
document.cookie = "auth_token=06dec19f563932f39d96f93e9d8c005d3de9b7a9; domain=.x.com; path=/; secure; SameSite=None";
Configuración
- Cuenta X: @claw0x (navegador con sesión iniciada)
- Frecuencia de ejecución: Cada 12 horas (9:00 + 21:00 CST)
- Validez de la cookie: El usuario debe actualizar
auth_tokenperiódicamente - Destino de salida:
daily-reports/YYYY-MM-DD/x-demand-radar.md+public/data/demand-radar.json - Despliegue: git push → CF Pages construye automáticamente
Errores Comunes (Pitfalls)
- La cookie no persiste entre sesiones: Cada vez que Hermes se inicia es una nueva instancia del navegador, la cookie debe reinyectarse. El primer paso de cada ejecución del cron es inyectar la cookie → verificar inicio de sesión.
browser_consoleno puede usarreturndirectamente: Debe envolverse en un IIFE(async function() { ... })(), de lo contrario lanza errorIllegal return statement. Ver detalles enreferences/x-auth-and-collection.md.- La escasez de resultados de búsqueda es normal: Con
min_faves≥100y consultas booleanas complejas, obtener entre 4 y 16 resultados por capa es normal. L3 (GitHub) es especialmente escaso. No reducir el umbralmin_faves, o el ruido abrumará la señal. - La barra lateral de Noticias de Hoy es una fuente gratuita de señales: Cada página de búsqueda muestra temas de noticias del día y el número de posts a la derecha. Son puntos calientes deterministas y deben incluirse en el reporte.
- El envío debe hacerse directamente a Feishu: No depender de cron announce. Enviar el mensaje inmediatamente después de escribir los archivos de resultados.
- Los términos de búsqueda son globales: Todas las consultas se hacen en inglés, sin limitación geográfica. El reporte se centra en tendencias globales (sin limitarse a IA).
execute_codeno soporta JSON en línea extenso: Incrustar datos JSON directamente en una cadena de Python puede causar errores de análisis comoSyntaxError: leading zeros. La forma correcta es usarwrite_filepara escribir en un archivo temporal y luego usaropen()para leer:with open('/tmp/data.json') as f: data = json.load(f). De la misma manera,read_filedevuelve texto con prefijo de número de línea (ej.1|{...}), no se puede usar directamentejson.loads(), se debe usaropen()nativo.git pushpuede ser rechazado (non-fast-forward): El repositorio remoto puede tener otras instancias del cron o commits manuales anteriores al push actual. Ejecutar siempregit pull --rebase && git push, nunca sologit push.- El texto truncado al recolectar con JS puede perder URLs de GitHub:
text.substring(0, 300)puede cortar el enlace de GitHub al final. Si el post menciona GitHub pero no se extrajo el enlacegithub.com, es necesario navegar al post original (browser_navigateal campolink) para extraer la URL completa, o buscar el nombre del repositorio en GitHub. - La capa L0 genera bastante ruido (verificado):
min_faves:200no frena suficientes posts coloquiales con "game changer" (política, deportes, vida cotidiana). En la primera ejecución, de 19 resultados de L0 solo 4 tenían valor potencial como producto. Sugerencia: Si en dos ejecuciones consecutivas la relación señal/ruido de L0 es <20%, elevarmin_favesa 500, o añadir términos de exclusión en la consulta L0 como-(political OR sports OR religion).
Fuentes de Señal Complementarias (Opcional)
- GitHub Trending: https://github.com/trending?since=daily
- Product Hunt: https://www.producthunt.com/
- Hacker News: https://news.ycombinator.com/