Mapea Tu Mercado
Toma una descripción de producto, palabras clave de categoría o nombres de competidores. Busca en Reddit, HN, GitHub Issues, G2 y Google Trends señales de dolor reales. Puntúalas y agrúpalas. Construye un marco de posicionamiento completo: definición de ICP, temas de dolor clasificados con citas textuales, señales de tamaño de mercado y ángulos de mensajería derivados del lenguaje real que usa la gente.
Regla crítica: Cada cita de dolor en la salida debe existir textualmente en los datos brutos recopilados por el script. Cada nombre de proveedor en el mapa de mercado debe provenir de los resultados del scraping de G2 o de los resultados de búsqueda de GitHub. El tamaño del mercado debe decir "las señales sugieren" — nunca estimes una cifra en dólares a partir de proxies endebles. Si una fuente devuelve 0 resultados, reporta 0 — no complementes con ejemplos inventados.
Errores Comunes
| El agente querrá... | Por qué está mal |
|---|---|
| Inventarse puntos de dolor o cifras de tamaño de mercado | Cada cita de dolor debe ser textual de los datos brutos. El tamaño del mercado debe citar las señales encontradas. Nunca estimes el tamaño de mercado "típico". |
| Puntuar por número de posts en lugar de pain_score | Un post con 2,000 upvotes sobre precios es más fuerte que 50 posts con 10 upvotes cada uno. Usa la fórmula pain_score de references/pain-scoring.md. |
| Usar los mismos subreddits para cada categoría | r/politics añade ruido a una búsqueda de devops. Detecta automáticamente los subreddits relevantes a partir de la categoría y los nombres de competidores antes de buscar. |
| Enviar todas las señales brutas a IA sin puntuación previa | Puntúa localmente primero. Envía solo las 60 señales con mayor puntuación de dolor al clustering de IA. Ahorra tokens y mejora la calidad del agrupamiento. |
| Omitir la extracción de ICP a partir de metadatos de posts | El subreddit, el flair, la biografía del autor (HN) y el tipo de organización en GitHub son señales de ICP más ricas que el contenido del post. Captúralos y repórtalos siempre. |
| Confundir el número de proveedores con el tamaño del mercado | "47 proveedores en G2" significa competitivo, no grande. Presenta todas las señales como indicadores direccionales, no como cifras concretas. |
Paso 1: Verificación de Configuración
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-no configurado -- la búsqueda en GitHub Issues funciona a 60 req/h sin autenticación}"
echo "No se requieren otras claves API."
echo ""
echo "Fuentes de datos que se usarán en esta ejecución:"
echo " Reddit public JSON (sin autenticación, 10 req/min)"
echo " HN Algolia API (sin autenticación, gratis)"
echo " GitHub Issues API (${GITHUB_TOKEN:+autenticado, }60-5000 req/h)"
echo " G2 category scrape (sin autenticación, análisis HTML)"
echo " Google Trends (sin autenticación, endpoint no oficial)"
Si GITHUB_TOKEN no está configurado: continúa. La búsqueda no autenticada de GitHub es de 60 req/h — suficiente para una ejecución estándar. Para uso repetido, añade un token en github.com/settings/tokens (no se necesitan permisos para repos públicos).
Paso 2: Analizar Entrada
Recopila de la conversación:
category— palabra(s) clave que describen el espacio de mercado (ej. "observabilidad para desarrolladores", "analítica B2B", "herramientas devops")competitors— lista opcional de nombres de productos competidores o dominios (ej. "Datadog, New Relic, Grafana")product_context— opcional: lo que hace el producto del usuario (ayuda a personalizar los ángulos de mensajería)
Si el usuario proporciona solo una descripción de producto sin una palabra clave de categoría: extrae tú mismo 2-3 palabras clave de categoría.
Si el usuario proporciona solo nombres de competidores sin categoría: infiere la categoría buscando a los competidores.
Escribe la entrada analizada:
python3 << 'PYEOF'
import json, os
data = {
"category": "CATEGORY_AQUÍ",
"competitors": ["COMP_1", "COMP_2"],
"product_context": "CONTEXTO_PRODUCTO_AQUÍ"
}
with open("/tmp/mym-input.json", "w") as f:
json.dump(data, f, indent=2)
print("Entrada escrita en /tmp/mym-input.json")
print(f"Categoría: {data['category']}")
print(f"Competidores: {', '.join(data['competitors']) if data['competitors'] else 'ninguno proporcionado'}")
PYEOF
Paso 3: Ejecutar el Script Independiente de Recolección de Datos
El script maneja toda la recolección de datos. Verifica primero si existe:
ls scripts/fetch.py 2>/dev/null && echo "script disponible" || echo "no encontrado"
Si está disponible, ejecútalo:
GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/fetch.py \
"$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(d['category'])")" \
--competitors "$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(','.join(d['competitors']))")" \
--context "$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(d['product_context'])")" \
--output /tmp/mym-raw.json
Espera a que termine (permite hasta 4 minutos — las búsquedas en Reddit + HN toman ~90 segundos en total).
Verifica la salida:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
print(f'Señales de Reddit: {d[\"market_signals\"][\"reddit_signals_found\"]}')
print(f'Señales de HN: {d[\"market_signals\"][\"hn_signals_found\"]}')
print(f'Señales de GitHub: {d[\"market_signals\"][\"github_issue_signals\"]}')
print(f'Proveedores G2: {d[\"market_signals\"][\"vendor_count_g2\"]}')
print(f'Tendencias: {d[\"market_signals\"][\"trends_direction\"]}')
print(f'Señales totales: {d[\"summary\"][\"total_pain_signals\"]}')
"
Si las señales totales son < 10: detente. Dile al usuario: "Se encontraron menos de 10 señales de dolor para esta categoría. El mercado puede ser demasiado nicho para la cobertura de Reddit/HN, o las palabras clave de categoría necesitan ajuste. Prueba con palabras clave más amplias o añade nombres de competidores."
Paso 4: Agrupación (Clustering) de Dolores por IA
Imprime las 60 señales de dolor principales para el análisis de IA:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
top60 = sorted(d['raw_pains'], key=lambda x: x['pain_score'], reverse=True)[:60]
print(json.dumps(top60, indent=2))
"
Ahora tienes las 60 señales de dolor principales. Analízalas y produce grupos de dolor.
Instrucciones para el análisis de IA:
- Identifica 5-7 temas de dolor recurrentes en todas las fuentes
- Para cada tema: elige un nombre que use el lenguaje del propio mercado (no tus palabras)
- Agrega el pain_score de todas las señales en cada grupo
- Selecciona las 3-5 mejores citas textuales para cada tema (las de mayor puntuación y lenguaje más específico)
- Anota en qué fuentes y subreddits se concentra cada tema
- Marca cualquier tema que aparezca solo en una fuente (menor confianza)
Escribe los grupos en /tmp/mym-clusters.json:
{
"clusters": [
{
"theme": "lenguaje exacto de los datos",
"total_score": 847,
"signal_count": 34,
"sources": {"reddit": 18, "hn": 12, "github_issue": 4},
"top_subreddits": ["devops", "sysadmin"],
"verbatim_quotes": [
{"text": "cita exacta", "source": "reddit", "score": 234, "url": "..."},
{"text": "cita exacta", "source": "hn", "score": 87, "url": "..."}
],
"who_has_this_pain": "descripción de quién está publicando sobre esto"
}
]
}
python3 -c "
import json, os
# Confirmar que el archivo de grupos se escribió
with open('/tmp/mym-clusters.json') as f:
d = json.load(f)
print(f'Grupos escritos: {len(d[\"clusters\"])}')
for c in d['clusters']:
print(f' {c[\"theme\"]} -- puntuación: {c[\"total_score\"]}, señales: {c[\"signal_count\"]}')
"
Paso 5: Perfilado del ICP
Imprime las señales de ICP de los datos brutos:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
print('Señales de ICP:')
print(json.dumps(d['icp_signals'], indent=2))
print()
print('Distribución de subreddits:')
sub_counts = {}
for p in d['raw_pains']:
s = p.get('subreddit', '')
if s:
sub_counts[s] = sub_counts.get(s, 0) + 1
for sub, count in sorted(sub_counts.items(), key=lambda x: -x[1])[:10]:
print(f' r/{sub}: {count} señales')
"
Usando las señales de ICP y la distribución de subreddits anterior, sintetiza el perfil del ICP. Escríbelo en /tmp/mym-clusters.json añadiendo una clave icp:
{
"icp": {
"who_they_are": "Perfil de 2-3 oraciones usando lenguaje de los datos",
"where_they_live": ["r/devops (89 posts)", "r/sysadmin (67 posts)", "HN ask-hn (34 hilos)"],
"what_they_say": ["cita textual 1", "cita textual 2", "cita textual 3"],
"what_they_have_tried": ["herramientas o enfoques alternativos mencionados en los datos"],
"confidence": "alta|media|baja -- basado en el volumen de señales y la diversidad de fuentes"
}
}
Paso 6: Síntesis del Tamaño de Mercado
Imprime las señales de mercado:
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
ms = d['market_signals']
print('Señales de mercado:')
print(f' Proveedores G2: {ms[\"vendor_count_g2\"]}')
print(f' Dirección de tendencias: {ms[\"trends_direction\"]}')
print(f' Señales HN (12 meses): {ms[\"hn_signals_found\"]}')
print(f' Señales Reddit: {ms[\"reddit_signals_found\"]}')
print(f' Principales proveedores G2: {json.dumps(ms.get(\"top_vendors\", []), indent=4)}')
"
Sintetiza una evaluación direccional del tamaño de mercado usando solo estas señales. No estimes una cifra en dólares. Usa lenguaje como:
- "Las señales sugieren un mercado competitivo y en crecimiento" (muchos proveedores + tendencias al alza)
- "Las señales sugieren un mercado temprano" (pocos proveedores + bajo volumen de señales)
- "Las señales sugieren un mercado saturado" (muchos proveedores + tendencias planas o a la baja)
Añade la evaluación a /tmp/mym-clusters.json como una clave market_size.
Paso 7: Marco de Posicionamiento
Usando los grupos (Paso 4), el ICP (Paso 5) y el tamaño de mercado (Paso 6), genera el marco de posicionamiento.
Instrucciones:
- Elige los 3 grupos de dolor principales como los ángulos de posicionamiento primarios
- Para cada ángulo: escribe una declaración de posicionamiento usando lenguaje textual de los datos (no parafraseado)
- Genera 3 titulares de página de aterrizaje que usen las frases exactas que la gente usa en los datos de dolor
- Genera 3 líneas de asunto de email frío basadas en el lenguaje del dolor
- NO uses palabras prohibidas: potente, robusto, sin fisuras, innovador, innovador, optimizar, apalancar, transformar, revolucionar
Escribe el marco de posicionamiento completo en /tmp/mym-output.json:
{
"positioning_angles": [
{
"pain": "nombre del tema",
"statement": "posicionamiento de una línea usando lenguaje de mercado",
"headline": "titular de página de aterrizaje usando lenguaje de dolor textual",
"cold_email_subject": "línea de asunto"
}
],
"icp_card": {
"one_liner": "una oración: quiénes son + qué les importa",
"where_to_find_them": [...],
"how_to_talk_to_them": "notas de tono y vocabulario de los datos"
},
"market_map": [...proveedores principales de G2 con notas de posicionamiento...]
}
Paso 8: Autoverificación (QA) y Guardado de Salida
Ejecuta las comprobaciones de autoverificación:
python3 -c "
import json
# Cargar todas las salidas
with open('/tmp/mym-raw.json') as f:
raw = json.load(f)
with open('/tmp/mym-clusters.json') as f:
clusters = json.load(f)
with open('/tmp/mym-output.json') as f:
output = json.load(f)
raw_texts = set()
for p in raw['raw_pains']:
raw_texts.add(p.get('title', ''))
raw_texts.add(p.get('body_excerpt', ''))
# Comprobación 1: Sin guiones largos
import json as j
full_text = j.dumps(output)
if '—' in full_text:
print('FALLO: se encontró un guión largo en la salida')
else:
print('PASA: sin guiones largos')
# Comprobación 2: Sin palabras prohibidas
banned = ['potente', 'robusto', 'sin fisuras', 'innovador', 'innovador',
'optimizar', 'apalancar', 'transformar', 'revolucionar']
found = [w for w in banned if w.lower() in full_text.lower()]
if found:
print(f'FALLO: palabras prohibidas encontradas: {found}')
else:
print('PASA: sin palabras prohibidas')
# Comprobación 3: Verificación de lenguaje de tamaño de mercado
if 'mil millones' in full_text.lower() or 'billón' in full_text.lower() or 'valorado en \$' in full_text.lower():
print('FALLO: se encontró estimación de tamaño de mercado con cifras concretas -- usa solo lenguaje direccional')
else:
print('PASA: sin estimaciones de tamaño de mercado con cifras concretas')
# Comprobación 4: Los conteos de señales coinciden
total = raw['summary']['total_pain_signals']
print(f'PASA: {total} señales de dolor totales en datos brutos')
print('Autoverificación completada.')
"
Corrige cualquier fallo antes de guardar.
Guarda el informe final:
python3 << 'PYEOF'
import json, re
from datetime import datetime
with open('/tmp/mym-input.json') as f:
inp = json.load(f)
with open('/tmp/mym-raw.json') as f:
raw = json.load(f)
with open('/tmp/mym-clusters.json') as f:
clusters = json.load(f)
with open('/tmp/mym-output.json') as f:
output = json.load(f)
slug = re.sub(r'[^a-z0-9]+', '-', inp['category'].lower()).strip('-')
date = datetime.now().strftime('%Y-%m-%d')
outpath_md = f'docs/market-maps/{slug}-{date}.md'
outpath_json = f'docs/market-maps/{slug}-{date}.json'
# Construir informe en markdown
ms = raw['market_signals']
icp = clusters.get('icp', {})
market_assessment = clusters.get('market_size', {})
angles = output.get('positioning_angles', [])
icp_card = output.get('icp_card', {})
market_map = output.get('market_map', [])
lines = [
f'# Mapa de Mercado: {inp[\'category\'].title()}',
f'Fecha: {date} | Señales analizadas: {raw[\'summary\'][\'total_pain_signals\']} | Fuentes: Reddit ({ms[\'reddit_signals_found\']}) + HN ({ms[\'hn_signals_found\']}) + GitHub Issues ({ms[\'github_issue_signals\']})',
'',
'---',
'',
'## Señales de Tamaño de Mercado',
f'Proveedores en G2: {ms[\'vendor_count_g2\']} | Google Trends: {ms[\'trends_direction\'].upper()} | Etapa del mercado: {market_assessment.get(\'stage\', \'ver señales a continuación\')}',
'',
market_assessment.get('summary', ''),
'',
'---',
'',
'## Tu ICP',
'',
f'**Quiénes son:** {icp.get(\'who_they_are\', \'\')}',
'',
f'**Dónde viven:** {\', \'.join(icp.get(\'where_they_live\', []))}',
'',
'**Qué dicen:**',
]
for q in icp.get('what_they_say', []):
lines.append(f'> \"{q}\"')
lines += ['', '---', '', '## Principales Dolores (clasificados por fuerza de señal)', '']
for i, c in enumerate(clusters.get('clusters', []), 1):
lines.append(f'### Dolor {i}: {c[\'theme\']} [puntuación: {c[\'total_score\']}]')
sources = c.get('sources', {})
source_str = ' + '.join(f'{src} ({cnt})' for src, cnt in sources.items())
lines.append(f'{c[\'signal_count\']} señales | Fuentes: {source_str}')
lines.append(f'Quién tiene este dolor: {c.get(\'who_has_this_pain\', \'\')}')
lines.append('')
lines.append('Textual:')
for q in c.get('verbatim_quotes', [])[:4]:
lines.append(f'> \"{q[\'text\']}\" — {q[\'source\']}')
lines.append('')
lines += ['---', '', '## Ángulos de Posicionamiento', '']
for a in angles:
lines.append(f'**Dolor:** {a[\'pain\']}')
lines.append(f'*Declaración:* {a[\'statement\']}')
lines.append(f'*Titular:* {a[\'headline\']}')
lines.append(f'*Asunto de email frío:* {a[\'cold_email_subject\']}')
lines.append('')
lines += ['---', '', '## Tarjeta de ICP', '']
lines.append(f'**En una línea:** {icp_card.get(\'one_liner\', \'\')}')
lines.append(f'**Dónde encontrarlos:** {\', \'.join(icp_card.get(\'where_to_find_them\', []))}')
lines.append(f'**Cómo hablarles:** {icp_card.get(\'how_to_talk_to_them\', \'\')}')
lines.append('')
lines += ['---', '', '## Mapa de Competidores', '']
for v in market_map:
lines.append(f'- **{v.get(\'name\', \'\')}** — {v.get(\'positioning_note\', \'\')}')
report_md = '\n'.join(lines)
with open(outpath_md, 'w') as f:
f.write(report_md)
with open(outpath_json, 'w') as f:
json.dump(output, f, indent=2)
print(f'Informe guardado en {outpath_md}')
print(f'Datos guardados en {outpath_json}')
PYEOF
Flujo de Trabajo del Usuario
Ejecución típica desde cero:
- El usuario dice "mapea mi mercado" o "¿quién es mi ICP?"
- Recopila descripción de producto, palabras clave de categoría o nombres de competidores
- Sigue los Pasos 1-8 anteriores
- Devuelve el informe como salida de lectura con enlace al archivo guardado en docs/market-maps/