--- name: escucha description: "Trae lo que publicó esta semana la gente que vigilamos — Reddit, YouTube (con transcript y comentarios), LinkedIn y las newsletters del inbox. Lee content/escucha/watchlist.md, pregunta qué fuentes correr y entrega Top Señales agrupadas por fuente, rankeadas por relevancia al negocio con la rúbrica de content/escucha/senales.md. No escribe archivo. Activar cuando el usuario diga /escucha, 'qué están publicando', 'qué dice Reddit de X', 'qué publicó esta semana', 'revisa mis newsletters', 'brief de la semana'." triggers: ["/escucha", "qué están publicando", "qué dice reddit de", "revisa mis newsletters", "brief de la semana", "qué publicó esta semana"] extends: [voz-gnb] related: [noticias, newsletter-gnb, carrusel-gnb, radar-repos-gnb] category: Research public: true --- # escucha — Qué está diciendo el mercado Captura multi-fuente de lo que publicaron los que vigilamos. **Escucha lo que dicen**, no lo que hacen: contrataciones, funding y lanzamientos son intención y hoy no se cubren. Salida en sesión: **Top Señales agrupadas por fuente**, hasta 5 por fuente, rankeadas por relevancia al negocio y qué tan reciente — **no por engagement** — con 2 a 4 líneas de análisis debajo de cada bloque. Nada más. La tabla cruda es la Parte 2 y solo sale si la piden. **No se escribe ningún archivo.** ## Fuentes Tres corren por `scripts/escucha.py` con `--source`. **Gmail es la excepción: no tiene script**, se lee con las tools MCP del conector. | Fuente | Costo | Necesita | |---|---|---| | `reddit` | gratis | nada | | `youtube` | gratis | `yt-dlp` en el PATH | | `linkedin` | **cobra por post raspado** | `APIFY_API_KEY` | | gmail | gratis | conector Gmail autorizado en claude.ai | **A quién vigilamos:** `content/escucha/watchlist.md`, en `## LinkedIn`, `## YouTube`, `## Reddit` y `## Gmail`. **Qué cuenta como señal:** `content/escucha/senales.md`. **Es la autoridad** — su lista de temas y su lista de ignorar mandan sobre tu juicio. ## No-negociables 1. **Nunca correr todas las fuentes por default.** Preguntar primero (paso 1). 2. **LinkedIn cobra.** `--dry-run` antes de cualquier corrida con más de 3 líneas. Imprime `max_posts_scraped` sin gastar. 3. **"No encontró nada" y "no se pudo leer" son cosas distintas.** Un perfil inalcanzable, un subreddit con rate limit o Gmail sin conectar se reportan como tales. Nunca se leen como "estuvo callado". El stderr del script dice cuál es cuál — hay que leerlo. 4. **El engagement es desempate, nunca la llave de orden.** 5. **Nunca mezclar fuentes en una lista o una tabla.** Las vistas de YouTube no son upvotes ni reacciones. 6. **Cero invención.** Si no vino en el JSON, no existe. Ni un post, ni una URL, ni un número. Un conteo desconocido es `?`, nunca `0`. 7. **Voz GNB** vía `extends: [voz-gnb]`. El análisis pasa por VOICE GATE; los nombres de herramientas y las citas textuales quedan como son. 8. **No se escribe archivo.** Si el usuario quiere una pieza, eso es `/blog`, `/newsletter-gnb` o `/redes` — otra tarea. Cuando la pieza es la newsletter, las ligas de esta corrida son la materia prima de su sección Fuentes: ahí manda la regla de qué se cita, no aquí. ## Paso 1 — Preguntar Con `AskUserQuestion` (`multiSelect: true`), ofreciendo **solo** las fuentes que tengan entradas en su sección y su llave disponible. Decir cuántas entidades vigila cada una y cuál cobra. Saltar la pregunta cuando el usuario ya nombró la fuente ("revisa Reddit"). Si la pregunta es de tema sin fuente ("qué dicen de los agentes"), es modo búsqueda: preguntar si va a Reddit, YouTube o las dos. **Gmail lee el correo del usuario**, así que nunca se ofrece para una pregunta de tema abierto y nunca corre sin que lo pidan. Si el conector no está autorizado —las tools `search_threads` / `get_thread` no aparecen en la sesión— ofrecerlo etiquetado "falta conectar" y, si lo eligen, mandarlos a claude.ai → Settings → Connectors → Gmail → Connect. Es un flujo de navegador que no puedes hacer por ellos: **nunca pidas un token, un código ni una contraseña de aplicación.** Si dicen que no, se salta y no se vuelve a mencionar. Con Reddit hay que decidir **qué** vigila antes de correr: un tema (`-q`) o los subreddits del watchlist. Inferirlo cuando la frase ya lo decide —un asunto es `-q`, un subreddit nombrado es modo feed— y meterlo en la misma pregunta. ## Paso 2 — Correr ```bash python3 scripts/escucha.py --source reddit -d 7 -n 25 --enrich 5 python3 scripts/escucha.py --source reddit -q "agentes de ia" python3 scripts/escucha.py --source youtube -d 30 -n 15 --per-source 8 --enrich 3 python3 scripts/escucha.py --source youtube -q "claude code" --enrich 3 python3 scripts/escucha.py --source linkedin --dry-run python3 scripts/escucha.py --source linkedin -d 7 -n 25 --per-source 10 ``` Todas imprimen `{"source","count","signals":[...]}` en stdout, **ya ordenadas**: por engagement en Reddit y LinkedIn, por fecha en YouTube. Las notas por entidad van a stderr. El JSON es para ti — **nunca lo pegues en el chat.** Las fuentes son independientes: córrelas en paralelo. ### Reddit Gratis, así que córrelo sin miedo; el único costo es tiempo. Las peticiones van ~2s aparte para no pegarle al rate limit, así que cinco subreddits tardan un par de minutos. **Eso no es que se colgó.** Dos modos, y contestan preguntas distintas: - **Modo tema** (`-q`) busca en todo Reddit y es el fuerte. Alcanza mucho más allá del watchlist. Úsalo cuando nombren un asunto, una marca o un competidor. - **Modo subreddit** (sin `-q`) lee los feeds del watchlist. Rankea por engagement crudo, así que en subs grandes saca lo más relatable, no lo más relevante. Filtra duro contra `senales.md`. **Pasa palabras clave, no la oración del usuario.** La calidad de la query es lo que decide la relevancia: | Pide | Pasa | No | |---|---|---| | "qué se dice de los agentes de IA" | `-q "agentes de ia"` | `-q "qué se dice de los agentes de IA"` | | "monitorea menciones de Taplio" | `-q "Taplio"` | `-q "monitorea menciones de Taplio"` | Con una marca o competidor, **reporta toda mención sin importar el score**: un prospecto diciendo que canceló a un competidor importa con cero upvotes. `--enrich` (default 5) es cuántos posts top traen su hilo de comentarios. **Los comentarios son donde el dolor se dice con nombre y precio** — léelos. Los upvotes vienen de un snapshot del archivo tomado cerca de la publicación, mientras que los scores de comentarios son vivos. Un post con `score_note` trae el conteo subido al de su mejor comentario: trátalo como piso, no como medición, y no lo cites como exacto. `upvotes_stale` guarda el original. Un subreddit que aparece bajo `no posts from ...` en stderr tuvo rate limit o está inalcanzable — **no estuvo callado.** Dilo en la Parte 1. ### YouTube Gratis y sin llave, pero necesita `yt-dlp` (`python3 -m pip install yt-dlp`); si falta, el script lo dice y se sale. Es lento por naturaleza: descubrimiento por canal, luego metadata en lote, luego dos llamadas más por video enriquecido. Presupuesta unos minutos y no lo trates como colgado. **Se ordena por fecha, lo más nuevo primero.** Las vistas solo desempatan entre videos del mismo día. Es a propósito: un video viejo con 500 mil vistas no es noticia, y la subida de esta semana con 400 vistas de un competidor sí. **No lo reordenes por vistas al reportar.** Amplía la ventana: los canales publican semanal en el mejor caso, así que `-d 7` suele volver vacío y `-d 30` es el default útil. `--enrich` (default 5) es cuántos videos top traen **transcript y comentarios**. Esa es la razón de usar la fuente. **Lee el transcript antes de rankear** — el título vende el clic, el transcript es lo que de verdad se dijo, y ahí es donde un competidor nombra su precio o su modelo de entrega. Cita del transcript, no del título. Un video con `transcript_unavailable` no tenía subtítulos o lo estrangularon: dilo, no lo trates como que no dijo nada. Los que quedaron abajo del corte de `--enrich` simplemente no traen la llave `transcript` — eso es presupuesto, no falla, y **nunca se reportan como mudos.** El engagement aquí es `views`, nunca una suma: las vistas aplastan a los likes por órdenes de magnitud. `views`, `likes` y `comments` van reportados aparte, y un `null` es conteo oculto, no cero. ### LinkedIn — la que cobra `--per-source` (default 10) topa los posts por línea del watchlist. Una corrida raspa como máximo `líneas × --per-source`, y eso es la cuenta. `-d` filtra después de traer, así que **achicar la ventana no ahorra nada** — baja `--per-source`. `--dry-run` es gratis e imprime el costo máximo. Proveedor intercambiable con `--linkedin-provider`, como `lib/proveedores/`: `apify` funciona hoy; `unipile` está declarado y **sin implementar**, y falla diciendo qué variable falta. Si Gabriel consigue las credenciales de Unipile, se implementa ahí y el resto del sistema no se entera. Lee el stderr: un perfil que no devolvió nada está **inalcanzable o inactivo**, nunca "callado". La sección `## LinkedIn` del watchlist está vacía a propósito — los archivos de research no traen URLs de LinkedIn y adivinar una raspa a la persona equivocada y la cobra igual. Si el usuario quiere LinkedIn, pídele las URLs. ### Gmail — sin script, con las tools MCP Solo lectura, siempre. Usa `search_threads`, `get_thread` y `get_message`, **y nada más.** El conector también expone enviar, responder, borradores, etiquetas, papelera y spam: **ninguna de esas pertenece a este skill.** La escucha lee; nunca escribe, nunca marca como leído, nunca etiqueta. *El watchlist trae nombres, no direcciones.* Resuelve cada nombre a sus direcciones reales con **una** búsqueda amplia sobre sus palabras distintivas: ``` from:(smart OR creator OR samy) newer_than:90d ``` Espera **más de una dirección por nombre**: Substack manda desde una dirección por publicación, con el patrón `nombre` + `publicacion` en el buzón. Toma todas las que claramente son de esa publicación y **di cuáles emparejaste** al reportar, para que un mal match se vea. Si un nombre no resuelve a nada, dilo y pide un número reenviado en vez de adivinar una dirección. *Alcance: dos anillos, ambos acotados.* Ya resuelto, toda query va clavada a esas direcciones más las categorías de newsletter: ``` from:(REMITENTE_1 OR REMITENTE_2) newer_than:7d {category:promotions category:updates} newer_than:7d -in:sent ``` **Nunca ampliar más allá de esos dos.** Un barrido de la bandeja, `in:anywhere`, un `from:` a una persona, o buscar en la categoría principal lee correo personal y de clientes, y este skill no hace eso — sin importar cómo esté formulada la pregunta. Una pregunta de tema se contesta agregando términos **dentro** de los dos anillos, nunca quitándolos. Lo que las categorías saquen y claramente no sea newsletter —un recibo, un aviso de calendario, un reset de contraseña, un hilo personal que cayó en updates— se descarta sin resumirlo ni citarlo. **Di cuántos descartaste, no cuáles eran.** *Una llamada por nombre para resolver, luego una sola para todo el watchlist.* Usa `view: THREAD_VIEW_MINIMAL` para que el listado traiga asunto y snippet, rankea con eso, y solo llama `get_thread` con `messageFormat: "PLAIN_TEXT"` a los mejores. `PLAIN_TEXT` convierte el HTML a texto, que es justo la razón de no escribir un script. Ese segundo paso es el `--enrich` de Gmail: **5 números por default.** Los cuerpos son largos y traer veinte completos acaba el contexto antes de escribir el reporte. Los que quedan abajo del corte se reportan desde asunto y snippet, y se marca en una palabra — es presupuesto, no falla, y nunca se llaman vacíos. `newer_than:7d` es el equivalente de `-d`. Ampliar como YouTube: la mayoría son semanales, así que `newer_than:30d` suele servir más. *Dale la misma forma que a las demás fuentes* para que el paso 3 la trate igual: `entity` es el nombre del watchlist, `author` el remitente, `text` el cuerpo en texto plano, `date` la fecha de recepción y `url` el permalink del hilo (`https://mail.google.com/mail/u/0/#inbox/`). `engagement` es **siempre `null`** en Gmail: una newsletter no reporta conteos públicos. Escribe `—` en la tabla, nunca `0`, y **nunca inventes** una tasa de apertura. Se ordena por fecha, lo más nuevo primero. ## Paso 3 — Reportar Todo va **agrupado por fuente**. Una corrida de Reddit y YouTube produce un bloque de Reddit y uno de YouTube, en el mismo formato. Ordena los bloques por cuánto devolvieron, el mayor primero, y adentro usa el orden propio de la fuente. ### Parte 1 — Top Señales, por fuente Encabeza cada bloque con la fuente, su ventana y su conteo: ``` **Reddit** — 7 días, 34 posts, 6 que valen ``` Luego hasta **5 posts por fuente** (no 5 en toda la corrida), rankeados por relevancia al negocio y luego por qué tan reciente. Un post de 30,000 de engagement fuera del nicho no entra; uno de 90 de un competidor directo describiendo su modelo de entrega, sí. `content/escucha/senales.md` es la autoridad. Si no existiera, cae a la ficha de negocio de `CLAUDE.md` y di una vez que llenarlo afila las siguientes corridas. **Los comentarios de Reddit y YouTube le ganan al texto del post.** Cuando un item trae `top_comments`, léelos antes de rankear: en las respuestas alguien dice el precio que pagó, nombra a la agencia que corrió, o admite el problema que el post solo insinúa. Cita un comentario cuando diga algo que el post no, y atribúyelo a quien lo escribió. Formato de cada post, una línea: `` Debajo, **2 a 4 líneas de análisis** solo de esa fuente: qué están publicando y qué venden por debajo, qué se repite, qué es nuevo contra lo que ya venía, dónde los números dicen algo que el texto no. Cada afirmación amarrada a un post o un número concreto — **cero consejo genérico de marketing de contenidos.** Menciona brevemente cuando los posts de mayor engagement de esa fuente se hayan excluido por estar fuera de tema, para que la omisión se vea. Menos de 5 en un bloque es correcto cuando la fuente vino flaca — **nunca rellenes.** Una fuente que no calificó nada recibe una línea diciéndolo y **su bloque igual aparece**, para que el silencio se vea en vez de faltar. Con dos o más fuentes, cierra con **hasta 3 líneas de cruce**: qué aparece en más de un lado y dónde se contradicen. Es el único lugar donde se comparan fuentes. Cierra con **una** línea, máximo, de qué vale la pena hacer, y solo si los datos la sostienen. **No lo conviertas en un memo de estrategia.** **Y ahí párate.** Termina el turno con una línea ofreciendo el resto, nombrando qué queda guardado por fuente: *"Guardados: 28 posts más de Reddit, 11 de YouTube — dime y saco las tablas."* No uses `AskUserQuestion` para esto: es una oferta, no una pregunta que bloquea. Lo que devolvió vacío, tronó, se reintentó o trajo duplicados va **en el bloque de su fuente**, no guardado con la tabla. ### Parte 2 — Tablas crudas, por fuente (solo si las piden) **No las imprimas por default.** Cuestan muchos tokens y las Top Señales suelen contestar la pregunta solas. Solo cuando pidan "tabla completa", "muéstrame todo" o pregunten por posts fuera del top. **Una tabla por fuente, nunca una mezclada.** Cada una con su orden. Encabeza con fuente, ventana y cuántos de cuántos se muestran. **Cada tabla es un extracto:** las **15 filas top** por fuente, y una línea abajo nombrando el resto — *"19 posts más de Reddit en la corrida — pide el resto."* Solo si vuelven a pedir imprimes todas. Una fuente con 15 o menos ya está completa: imprímela entera y no digas nada de resto. El extracto se toma directo del orden, incluyendo los que quedaron fuera de tema — eso es lo que hace visible el corte. Columnas, en este orden, en todas: | Autor | Eng | Desglose | Resumen | Liga | |---|---|---|---|---| - **Autor** — el `author`, no el `entity` del watchlist. En Reddit es el redditor y el subreddit va en el resumen; en YouTube es el canal; en Gmail el nombre de la newsletter, no la dirección. - **Eng** — el número de `engagement`, dígitos pelones. Un `null` es **`?`**, nunca `0`. En YouTube son vistas. En Gmail no hay número: `—` en toda la columna. - **Desglose** — `/` en Reddit, `//` en LinkedIn, `//` en YouTube. En Gmail va la fecha de recepción `YYYY-MM-DD`, que es por lo que está ordenada. - **Resumen** — 1 o 2 oraciones de qué dice el post, ~200 caracteres. Prefiere lo concreto: números, nombres de herramientas, precios. **No** por qué importa ni qué hacer — eso fue la Parte 1. - **Liga** — liga markdown etiquetada `liga`. **Prohibido dentro de las tablas:** temas, agrupaciones, patrones entre posts, lecturas competitivas, oportunidades y recomendaciones. Las filas son descriptivas. Orden estricto por la llave de la fuente. El JSON de la corrida sigue disponible toda la sesión: una petición posterior se contesta desde ahí. **Nunca vuelvas a correr el script para rearmar una tabla** — una re-corrida de LinkedIn cobra otra vez. ## Límite conocido `scripts/escucha.py` pega a `reddit.com`, `arctic-shift.photon-reddit.com`, `youtube.com` y `api.apify.com`. **Ninguno de los cuatro está permitido por la política de red de las sesiones de Claude Code en la nube** (el proxy contesta 403 al CONNECT). El script corre bien en la laptop y en cualquier runner con salida abierta; en una sesión web devuelve `count: 0` con el 403 en stderr. Si estás en la nube y las cuatro fuentes vuelven vacías con 403, **no es el watchlist ni el script**: dilo así y ofrece correrlo en local. Gmail sí funciona en la nube — el conector no pasa por ese proxy. ## Antes de entregar — GATE - [ ] ¿Preguntaste qué fuentes antes de correr? - [ ] ¿Corriste `--dry-run` si LinkedIn traía más de 3 líneas? - [ ] ¿Leíste el stderr de cada corrida y separaste "vacío" de "no se pudo leer"? - [ ] ¿Leíste los `top_comments` y los `transcript` antes de rankear? - [ ] ¿El orden es relevancia primero y engagement solo como desempate? - [ ] ¿Cada fuente tiene su bloque, aunque no haya calificado nada? - [ ] ¿Cero números inventados? ¿Los desconocidos van como `?` y no como `0`? - [ ] ¿VOICE GATE de `voz-gnb` sobre el análisis? - [ ] ¿No escribiste ningún archivo?