Open-Source Cold Email & Warmup Platform
Warmbly is the open-source platform for cold email: warm your mailboxes, send campaigns, and triage every reply. Run it on our cloud or self-host the whole thing.
Monitor institucional de medios y redes sociales — Departamento de Prensa SMATA
Translation pending
MarketingPython · TypeScriptGitHub @smata-developmentOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Monitor institucional de medios y redes sociales para el Departamento de Prensa de SMATA (sindicato automotriz argentino). Busca publicaciones en X/Twitter, Instagram y TikTok, las puntúa por relevancia con IA y arma informes en Word. El backend respeta una separación estricta por capas; cada cambio se hace de forma quirúrgica sobre la capa que corresponde:Read the full README ↗
Monitor institucional de medios y redes para el Departamento de Prensa de SMATA (sindicato automotriz argentino): rastrea X, Instagram y TikTok, puntúa relevancia con IA (Gemini) y arma informes en Word. Suma un módulo electoral de "boca de urna" y un scraper por visión.
🔗 Demo: https://filtro-redes-sociales-smt.vercel.app
Destacados: scoring con cascada de modelos Gemini + rotación de API key por cuota · dedup con Jaccard calibrado sobre casos reales · scraper por visión con Playwright ante anti-bot · ~270 tests.
| Capa | Tecnología |
|---|---|
| Frontend | Next.js 14 (App Router) → Vercel |
| Backend | FastAPI (Python) → Render (uvicorn, ver Procfile) |
| Búsqueda | SerpAPI (Google con site: por red) |
| Scoring IA | Google Gemini 2.5 Flash (cascada de modelos) |
| Informes | python-docx |
| Secrets | Variables de entorno en Render (backend) y Vercel (frontend) |
El backend respeta una separación estricta por capas; cada cambio se hace de forma quirúrgica sobre la capa que corresponde:
main.py): endpoints, CORS, modelos Pydantic.fetcher.py): solo SerpAPI. No conoce a las otras capas.normalizer.py): scoring con Gemini, parseo de URLs, filtros, y la orquestación fetch_posts.Dependencia: main.py → normalizer.py → fetcher.py.
La Boca de Urna extiende la Capa 3 con un módulo aislado (electoral.py) que reutiliza fetch_posts de normalizer.py y el transporte Gemini de gemini_client.py, sin tocar las otras capas. Dependencia adicional: main.py → electoral.py → gemini_client.py y electoral.py → normalizer.py.
Página separada (/boca-de-urna) que analiza el clima de conversación electoral en redes sociales.
Qué hace: busca publicaciones sobre candidatos presidenciales, las clasifica por postura (a favor / en contra / neutro) mediante Gemini, agrega el sentimiento neto por candidato, y contrasta los porcentajes de redes con encuestas de consultoras cargadas vía CSV.
Encuadre obligatorio: no es una encuesta ni una proyección de resultado. Es un termómetro direccional sobre publicaciones públicas indexadas, no una muestra representativa del electorado. El disclaimer se muestra siempre en la interfaz (DisclaimerBanner) y se incluye en cada respuesta del endpoint (meta.disclaimer).
Cómo funciona (flujo):
electoral.py llama a normalizer.fetch_posts con smata_mode=False para obtener el corpus.CONF_MIN = 0.5).aggregate_net_sentiment agrega pos - neg por candidato y calcula el % de sentimiento neto.compare_vs_pollsters cruza esos % con las encuestas del CSV (última fecha por consultora).candidatos, evidencia (citas + post), comparacion y meta (disclaimer + warnings).Módulos clave:
| Archivo | Responsabilidad |
|---|---|
backend/gemini_client.py | Transporte HTTP compartido a Gemini: cascada de modelos + rotación de API Key por cuota |
backend/electoral.py | Lógica electoral: parse del CSV, análisis con Gemini, agregación, comparación vs consultoras |
frontend/app/boca-de-urna/page.tsx | Página Next.js 14 con layout de dos columnas (sentimiento + evidencia / comparación) |
/
├── vercel.json ← Build del frontend en Vercel
├── .github/workflows/
│ └── keep-warm.yml ← Ping periódico al backend (anti cold-start)
│
├── frontend/ ← Next.js 14 (App Router) → Vercel
│ ├── app/
│ │ ├── page.tsx ← Pantalla principal del monitor
│ │ ├── layout.tsx ← Layout global, header y toggle de tema
│ │ ├── globals.css ← Variables CSS (tema claro/oscuro + branding)
│ │ └── boca-de-urna/
│ │ └── page.tsx ← Termómetro de Redes (Boca de Urna)
│ ├── components/
│ │ ├── SearchPanel.tsx ← Panel de parámetros de búsqueda
│ │ ├── PostCard.tsx ← Card de un post analizado
│ │ ├── ResultsGrid.tsx ← Grilla de resultados rankeados
│ │ ├── ReportButton.tsx ← Botón de informe Word
│ │ ├── ScoreBadge.tsx ← Badge de score
│ │ ├── ThemeToggle.tsx ← Toggle de tema claro/oscuro
│ │ └── urna/
│ │ ├── UrnaParamsBar.tsx ← Panel de parámetros (keywords, CSV, redes, fecha)
│ │ ├── DisclaimerBanner.tsx ← Aviso de encuadre (no encuesta)
│ │ ├── SentimentBarChart.tsx← Barras de sentimiento neto por candidato
│ │ ├── EvidencePanel.tsx ← Citas + posts con postura
│ │ └── ComparisonTable.tsx ← Tabla redes vs consultoras (gap)
│ └── lib/
│ ├── api.ts ← Cliente HTTP del backend (monitor de medios)
│ └── urnaApi.ts ← Cliente HTTP del backend (Boca de Urna)
│
└── backend/ ← FastAPI → Render
├── main.py ← Capa 1: Controlador/API
├── fetcher.py ← Capa 2: Fetcher ciego (SerpAPI)
├── normalizer.py ← Capa 3: Normalización/Filtro/IA (Gemini)
├── gemini_client.py ← Transporte Gemini compartido (cascada + rotación)
├── electoral.py ← Capa 3: Lógica electoral (Boca de Urna)
├── docx_generator.py ← Generación del informe Word
├── config.py ← Colores institucionales del informe
├── test_fetcher.py ← Tests de la Capa 2 (pytest)
├── test_normalizer.py ← Tests de la Capa 3 (pytest)
├── test_gemini_client.py ← Tests del transporte Gemini (pytest)
├── test_electoral.py ← Tests de la lógica electoral (pytest)
├── test_main_urna.py ← Tests del endpoint /api/boca-de-urna (pytest)
├── Procfile ← Comando de arranque en Render
├── requirements.txt
└── requirements-dev.txt ← Dependencias de desarrollo (pytest)
cd backend
python -m venv venv
venv\Scripts\activate # Windows (source venv/bin/activate en Linux/Mac)
pip install -r requirements.txt
# Crear backend/.env con las claves:
# SERPAPI_API_KEY=tu_clave
# GEMINI_API_KEY=tu_clave
# # (opcional) Boca de Urna vía scraping en vez de SerpAPI:
# URNA_FETCH_BACKEND=scraper # default: serpapi (sin cambios)
# URNA_SCRAPER_NETWORKS=twitter # redes con scraper; vacío = todas
# X_SCRAPER_API_KEY=tu_clave_trial # proveedor de scraping de X (ej. twitterapi.io)
python main.py # o: uvicorn main:app --reload
# → http://localhost:8000
cd frontend
npm install
# Crear frontend/.env.local con:
# NEXT_PUBLIC_API_URL=http://localhost:8000
npm run dev
# → http://localhost:3000
cd backend
pip install -r requirements-dev.txt
python -m pytest -q
vercel.json y buildea frontend/.NEXT_PUBLIC_API_URL = URL pública del backend en Render (ej. https://tu-backend.onrender.com). Es una variable de build: requiere redeploy al cambiarla.backend/ (Render usa el Procfile: uvicorn main:app).SERPAPI_API_KEY (Monitor de Medios; y Boca de Urna si el flag de scraping está apagado).GEMINI_API_KEYALLOWED_ORIGINS (opcional; orígenes permitidos por CORS, separados por coma).URNA_FETCH_BACKEND, apagado por defecto = serpapi):
scraper: scraping en vivo vía API de X. Se acota por red con URNA_SCRAPER_NETWORKS (ej. twitter) y usa X_SCRAPER_API_KEY.stored: lee el último snapshot que dejó el scraper local por visión (batch) desde Supabase — la app muestra el último análisis real aunque la PC de la oficina esté apagada. Requiere SUPABASE_URL, SUPABASE_KEY (anon key) y opcional URNA_SNAPSHOT_TABLE (default urna_snapshot). Setup completo: scraper_local/README.md.
El Monitor de Medios sigue siempre en SerpAPI.X_SCRAPER_MIN_INTERVAL (seg entre requests; default 1.2 para el trial con rate-limit agresivo — con saldo pago poné 0 y las corridas bajan a ~30-60s), X_SCRAPER_MAX_RETRIES (default 4), X_SCRAPER_BACKOFF (default 2.0).Nota: en el plan free de Render la instancia se duerme tras ~15 min de inactividad y la primera request luego tarda ~40-50 s. El workflow
keep-warm.ymlmitiga esos cold-starts.
POST /api/searchEnvía los criterios de búsqueda y retorna los posts parseados y puntuados por la IA.
{
"keywords": ["SMATA", "paritaria"],
"hashtags": ["smata"],
"accounts": [],
"networks": ["twitter", "instagram", "tiktok"],
"date": null,
"smata_mode": false
}
smata_mode: true = filtro estricto (solo SMATA / sector automotor), false = monitor de prensa amplio.
POST /api/generate-docxRecibe los posts seleccionados y devuelve el informe en formato Word (.docx).
POST /api/boca-de-urnaTermómetro de Redes — analiza el sentimiento electoral en publicaciones públicas de redes sociales y lo contrasta con encuestas de consultoras.
Encuadre: Este endpoint no es una encuesta ni una proyección electoral. Refleja el clima de conversación en publicaciones públicas indexadas (una muestra no representativa del electorado). Sirve como termómetro direccional, complementario a las consultoras.
Request:
{
"keywords": ["Milei", "Cristina", "elecciones presidenciales"],
"networks": ["twitter", "instagram"],
"date": "2025-09-01",
"country": "ar",
"pollster_csv": "consultora,fecha,candidato,porcentaje\nConsultora X,2025-09-01,Javier Milei,35.5\nConsultora X,2025-09-01,Cristina Kirchner,28.0"
}
| Campo | Tipo | Descripción |
|---|---|---|
keywords | string[] | Términos de búsqueda (candidatos, partidos, etc.) |
networks | string[] | Redes a consultar: "twitter", "instagram", "tiktok" |
date | string | null | Fecha mínima de publicación (YYYY-MM-DD) o null |
country | string | Código ISO 3166-1 alpha-2 (ej. "ar"). Default: "ar" |
pollster_csv | string | Contenido del CSV de encuestas (puede estar vacío) |
Response (HTTP 200):
{
"candidatos": [
{ "nombre": "Javier Milei", "pct": 55.0, "pos": 11, "neg": 3, "neu": 2, "menciones": 16 },
{ "nombre": "Cristina Kirchner", "pct": 45.0, "pos": 7, "neg": 4, "neu": 1, "menciones": 12 }
],
"evidencia": [
{
"candidato": "Javier Milei",
"postura": "a_favor",
"cita": "El presidente está cambiando el país",
"post": {
"network": "twitter",
"author": "usuario123",
"author_url": "https://twitter.com/usuario123",
"text": "El presidente está cambiando el país #Milei",
"post_url": "https://twitter.com/usuario123/status/123",
"date": "2025-09-03"
}
}
],
"comparacion": [
{
"candidato": "Javier Milei",
"redes_pct": 55.0,
"consultoras": [
{ "consultora": "Consultora X", "pct": 35.5, "gap": 19.5 }
],
"promedio_consultoras": 35.5,
"gap_promedio": 19.5
}
],
"meta": {
"total_posts": 50,
"posts_electorales": 28,
"disclaimer": "Este indicador refleja el clima de conversación en redes sociales...",
"warnings": []
}
}
Errores:
| HTTP | Causa |
|---|---|
400 | CSV de consultoras con header inválido o dato malformado irrecuperable |
500 | error interno inesperado |
503 | Gemini o SerpAPI sin cuota o caídos |
CSV de consultoras — esquema:
El campo pollster_csv acepta el contenido (como string) de un CSV con las siguientes columnas obligatorias:
| Columna | Formato | Ejemplo |
|---|---|---|
consultora | texto | Consultora X |
fecha | YYYY-MM-DD | 2025-09-01 |
candidato | texto (nombre canónico) | Javier Milei |
porcentaje | número (42.5 o 42,5) | 35.5 |
consultora o candidato vacíos, fecha no ISO-8601 o porcentaje no numérico se saltean con un warning (no abortan la operación).400 de inmediato.(consultora, candidato), se usa la de fecha más reciente.""): en ese caso se omite la comparación vs consultoras.Post)interface Post {
id: string;
network: "twitter" | "instagram" | "tiktok";
author: string;
author_url: string;
text: string;
date: string;
post_url: string;
relevance_score: number; // 0-100, calculado por Gemini
relevance_level: "alta" | "media" | "baja";
matched_terms: string[];
video_url: string | null;
}
Colores institucionales aplicados en la interfaz:
--smata-green-dark: #1B4D2E;
--smata-green-mid: #2E7D32;
--smata-green-light: #4CAF50;
--smata-green-pale: #E8F5E9;
--smata-gold: #FFC107;