Pricing Finder
Proporciona la URL o descripción de tu producto. Encuentra 5 competidores, obtiene sus páginas de precios reales y devuelve un informe completo de inteligencia de precios: modelo dominante en tu espacio, tabla de referencia de precios, análisis de barreras de funciones, mapa de posicionamiento y una recomendación concreta de precios para tu producto.
No requiere API keys. Se ejecuta completamente con dependencias pip gratuitas. Las API keys opcionales mejoran la calidad.
Política de cero alucinaciones: Cada punto de precio, nombre de nivel y barrera de función en la salida debe rastrearse al contenido de la página de precios obtenida o a un fragmento de búsqueda de DuckDuckGo. Esto aplica a:
- Precios de competidores: extraídos textualmente del contenido de la página obtenida únicamente.
- "Contactar con ventas": registrado tal cual, nunca estimado ni reemplazado con un número.
- Nombres de niveles: copiados exactamente de la página, no parafraseados.
- Listas de funciones: extraídas del contenido de la página, no inferidas del conocimiento del producto.
- Observaciones de posicionamiento: derivadas únicamente de los datos de la tabla de referencia.
Errores Comunes
| El agente querrá... | Por qué está mal |
|---|---|
| Rellenar "Contactar con ventas" con un precio estimado | Nunca estimes el precio empresarial. Regístralo exactamente como "Contactar con ventas". |
| Usar conocimiento de entrenamiento para precios de competidores | Cada precio debe rastrearse al contenido de la página obtenida o a un fragmento de búsqueda. |
| Saltar el paso de confirmación de competidores | Muestra siempre los competidores descubiertos y espera la confirmación. Competidores equivocados = referencias equivocadas. |
| Recomendar un precio sin hacer referencia a los datos de referencia | Cada recomendación de precio debe citar un número específico de la tabla de referencia. |
| Marcar una página como de alta calidad cuando el contenido tiene < 500 caracteres | < 500 caracteres significa que la página no se obtuvo -- marca data_quality como 'low' y usa el fragmento de búsqueda como respaldo. |
| Usar guiones largos en la salida | Reemplaza todos los guiones largos por guiones cortos. |
Leer Archivos de Referencia Antes de Cada Ejecución
cat references/pricing-models.md
cat references/extraction-guide.md
cat references/positioning-guide.md
Paso 1: Verificación de Configuración
echo "TAVILY_API_KEY: ${TAVILY_API_KEY:+configurada (calidad de búsqueda mejorada)}${TAVILY_API_KEY:-no configurada, se usará DuckDuckGo (gratuito)}"
echo "FIRECRAWL_API_KEY: ${FIRECRAWL_API_KEY:+configurada (renderizado JS mejorado)}${FIRECRAWL_API_KEY:-no configurada, se usarán requests+BS4 (gratuitos)}"
echo ""
python3 -c "from ddgs import DDGS; import requests, bs4, html2text; print('Dependencias OK')" 2>/dev/null \
|| echo "ERROR: Faltan dependencias. Ejecuta: pip install ddgs requests beautifulsoup4 html2text"
Si faltan dependencias: Detente inmediatamente. Dile al usuario: "Faltan dependencias de Python. Ejecuta esto para instalarlas: pip install ddgs requests beautifulsoup4 html2text -- todo gratuito, no se necesitan cuentas. Luego inténtalo de nuevo."
Si solo faltan API keys: Continúa. DuckDuckGo y requests+BS4 son los valores predeterminados gratuitos.
Derivar slug del producto:
PRODUCT_SLUG=$(python3 -c "
from urllib.parse import urlparse
import sys, re
url = 'URL_AQUI'
if url.startswith('http'):
host = urlparse(url).netloc.replace('www.', '')
print(host.split('.')[0])
else:
print(re.sub(r'[^a-z0-9]', '-', url[:30].lower()).strip('-'))
")
echo "Slug del producto: $PRODUCT_SLUG"
Paso 2: Analizar Entrada
Recopila de la conversación:
product_url: la URL a obtener (requerida, a menos que el usuario pegue directamente una descripción)geography: opcional -- EE.UU. / Europa / India / global. Valor predeterminado: EE.UU.
Si el usuario proporciona solo una descripción pegada (sin URL): Omite los Pasos 3 y 4. Ve directamente al Paso 4 (análisis del producto) usando el texto pegado como product_content. Establece page_source como user_description y anota en data_quality_flags.
Si no hay URL ni descripción: Pregunta: "¿Cuál es la URL de tu producto o startup? O pega una breve descripción: qué hace, para quién es y qué lo hace diferente."
Paso 3: Obtener Página del Producto
Principal: Firecrawl (si FIRECRAWL_API_KEY está configurada)
curl -s -X POST https://api.firecrawl.dev/v1/scrape \
-H "Authorization: Bearer $FIRECRAWL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "URL_AQUI", "formats": ["markdown"], "onlyMainContent": true}' \
| python3 -c "
import sys, json
d = json.load(sys.stdin)
content = d.get('data', {}).get('markdown', '') or d.get('markdown', '')
print(f'Obtenido vía Firecrawl: {len(content)} caracteres')
open('/tmp/pf-product-raw.md', 'w').write(content)
"
Respaldo: requests + BS4 (gratuito, siempre disponible)
python3 << 'PYEOF'
import requests, html2text, random
USER_AGENTS = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
]
headers = {"User-Agent": random.choice(USER_AGENTS), "Accept": "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8"}
resp = requests.get("URL_AQUI", headers=headers, timeout=20, allow_redirects=True)
converter = html2text.HTML2Text()
converter.ignore_images = True
converter.body_width = 0
content = converter.handle(resp.text)[:8000]
print(f'Obtenido vía requests+BS4: {len(content)} caracteres')
open('/tmp/pf-product-raw.md', 'w').write(content)
PYEOF
Punto de control:
python3 -c "
content = open('/tmp/pf-product-raw.md').read()
if len(content) < 200:
print('ERROR: se obtuvieron menos de 200 caracteres -- la página puede estar renderizada con JS')
else:
print(f'Contenido OK: {len(content)} caracteres')
"
Si el contenido tiene < 200 caracteres: Dile al usuario: "La página del producto devolvió muy poco contenido -- es posible que el sitio esté renderizado con JavaScript. Por favor, pega una breve descripción: qué hace tu producto, para quién es y qué lo hace diferente de los competidores."
Paso 4: Análisis del Producto (IA)
Imprimir el contenido de la página:
python3 -c "
content = open('/tmp/pf-product-raw.md').read()[:5000]
print('=== PÁGINA DEL PRODUCTO (primeros 5000 caracteres) ===')
print(content)
"
Instrucciones para la IA: Analiza la página del producto anterior y extrae:
product_name: el nombre del producto o empresaone_line_description: qué hace, para quién, propuesta de valor principal. Menos de 20 palabras. Sin lenguaje de marketing.industry_taxonomy:l1(nivel superior: developer tools / fintech / healthtech / consumer / etc.),l2(sector: devops / payments / hr / etc.),l3(nicho específico: automatización CI/CD / pagos integrados / video asíncrono / etc.)differentiators: exactamente 2-3 cosas específicas que distinguen este producto. Estos alimentan la recomendación -- sé específico. Respuestas genéricas como "fácil de usar" no son aceptables.icp:buyer_persona(cargo),company_type,company_sizegeography_bias: EE.UU. / Europa / India / globalpage_source: "live_page" o "user_description"
Escribir en /tmp/pf-product-analysis.json:
python3 << 'PYEOF'
import json
analysis = {
# COMPLETA con tu análisis anterior
"product_name": "",
"one_line_description": "",
"industry_taxonomy": {"l1": "", "l2": "", "l3": ""},
"differentiators": [],
"icp": {"buyer_persona": "", "company_type": "", "company_size": ""},
"geography_bias": "EE.UU.",
"page_source": "live_page"
}
json.dump(analysis, open('/tmp/pf-product-analysis.json', 'w'), indent=2)
print('Análisis del producto escrito.')
PYEOF
Verificar:
python3 -c "
import json
a = json.load(open('/tmp/pf-product-analysis.json'))
print('Producto:', a['product_name'])
print('Industria:', a['industry_taxonomy']['l1'], '>', a['industry_taxonomy']['l2'], '>', a['industry_taxonomy']['l3'])
print('Diferenciadores:')
for d in a['differentiators']:
print(f' - {d}')
"
Paso 4b: Fase 1 -- Descubrimiento de Competidores
ls scripts/research.py 2>/dev/null && echo "script encontrado" || echo "ERROR: no se encontró scripts/research.py -- no se puede continuar"
python3 scripts/research.py \
--phase discover \
--product-analysis /tmp/pf-product-analysis.json \
--output /tmp/pf-competitors-raw.json
Imprimir resultados para revisión por IA:
python3 -c "
import json
data = json.load(open('/tmp/pf-competitors-raw.json'))
print(f'Búsquedas ejecutadas: {len(data[\"competitor_searches\"])}')
for s in data['competitor_searches']:
print(f'\nConsulta: {s[\"query\"]}')
for r in s.get('results', [])[:6]:
print(f' - {r[\"title\"]} | {r[\"url\"]}')
print(f' {r.get(\"snippet\",\"\")[:150]}')
"
Instrucciones para la IA: Lee los resultados de búsqueda anteriores. Elige exactamente 5 empresas competidoras que:
- Estén nombradas en los títulos o fragmentos de los resultados de búsqueda
- Estén en el mismo nicho L3 que el producto que se está analizando
- Sean productos de software reales (no agencias, artículos de listas o sitios de reseñas)
- Sean distintas entre sí
Para cada competidor escribe: name, url, pricing_url (su página de precios -- infiere como [url]/pricing si no se encuentra en los fragmentos), description (una oración del fragmento), source_url.
Paso 5: Confirmación de Competidores
python3 << 'PYEOF'
import json
analysis = json.load(open('/tmp/pf-product-analysis.json'))
# COMPLETA: 5 competidores de los resultados de búsqueda anteriores
candidates = [
# {"name": str, "url": str, "pricing_url": str, "description": str, "source_url": str}
]
print(f"\nSe encontraron 5 competidores para {analysis['product_name']} en {analysis['industry_taxonomy']['l3']}:\n")
for i, c in enumerate(candidates, 1):
print(f" {i}. {c['name']} -- {c['description']}")
print(f" Producto: {c['url']}")
print(f" Precios: {c['pricing_url']}")
data = json.load(open('/tmp/pf-competitors-raw.json'))
data['competitor_candidates'] = candidates
json.dump(data, open('/tmp/pf-competitors-raw.json', 'w'), indent=2)
PYEOF
Dile al usuario: "Estos son los 5 competidores de los que obtendré datos de precios. Agrega, elimina o cambia cualquiera -- o di 'se ve bien' para continuar."
Espera la confirmación. Si el usuario edita la lista, actualiza los candidatos en consecuencia. Luego escribe la lista confirmada:
python3 << 'PYEOF'
import json
# COMPLETA: lista de competidores confirmada (después de la revisión del usuario)
confirmed = [
# {"name": str, "url": str, "pricing_url": str}
]
json.dump({"confirmed_competitors": confirmed}, open('/tmp/pf-competitors-confirmed.json', 'w'), indent=2)
print(f"Confirmados {len(confirmed)} competidores para la investigación de precios.")
for c in confirmed:
print(f" - {c['name']} | precios: {c['pricing_url']}")
PYEOF
Paso 6: Fase 2 -- Obtener Páginas de Precios
python3 scripts/research.py \
--phase fetch-pricing \
--competitors /tmp/pf-competitors-confirmed.json \
--output /tmp/pf-pricing-raw.json
Esto obtiene la página de precios de cada competidor usando un respaldo de 3 niveles:
- Obtención directa:
requests+beautifulsoup4+html2text - Caché de Google:
webcache.googleusercontent.com/search?q=cache:[url] - Búsqueda en DuckDuckGo:
"[competidor]" planes de precios costo por mes(respaldo de fragmento)
Imprimir resumen de obtención:
python3 -c "
import json
data = json.load(open('/tmp/pf-pricing-raw.json'))
print(f'Competidores obtenidos: {data[\"competitors_fetched\"]}')
print()
for r in data['results']:
quality_label = {'high': 'BUENO', 'medium': 'OK', 'low': 'SOLO FRAGMENTO'}.get(r['data_quality'], r['data_quality'])
print(f' {r[\"name\"]:20} {r[\"source\"]:15} {r[\"content_length\"]:5} caracteres [{quality_label}]')
"
Si un competidor tiene data_quality: low: Esto significa que la página de precios fue bloqueada o está renderizada con JS. El análisis continuará usando fragmentos de búsqueda, pero la confianza para ese competidor se marcará como baja.
Paso 7: Extracción de Precios (IA)
Imprimir todo el contenido de precios sin procesar:
python3 -c "
import json
data = json.load(open('/tmp/pf-pricing-raw.json'))
for r in data['results']:
print(f'\n=== {r[\"name\"]} (fuente: {r[\"source\"]}, calidad: {r[\"data_quality\"]}) ===')
print(f'URL de precios: {r[\"pricing_url\"]}')
print(r['content'][:4000])
print('---')
"
Instrucciones para la IA: Para cada competidor, extrae datos de precios estructurados del contenido anterior. Sigue references/extraction-guide.md para saber cómo identificar niveles, precios, límites y CTAs.
Reglas de cero alucinaciones:
- Extrae precios textualmente solo del contenido. Si un precio no está en el contenido, escribe
null. - Registra "Contactar con ventas" exactamente tal cual. Nunca lo reemplaces con un número estimado.
data_quality: lowsignifica que los datos provienen de fragmentos de búsqueda -- extrae lo que hay pero no llenes vacíos con conocimiento de entrenamiento.- Para cualquier campo no presente en el contenido: escribe
"no encontrado en los datos de la página". - Precios anuales: registra siempre el equivalente mensual junto con el total anual.
Escribir en /tmp/pf-pricing-extracted.json:
python3 << 'PYEOF'
import json
# COMPLETA: un objeto por competidor, siguiendo el esquema siguiente
extracted = [
# {
# "competitor": str,
# "pricing_url": str,
# "data_quality": "high" | "medium" | "low",
# "pricing_model": "per-seat" | "flat-rate" | "usage-based" | "freemium" | "tiered-flat" | "hybrid",
# "billing_cadence": ["monthly"] | ["annual"] | ["monthly", "annual"],
# "annual_discount": str, # ej. "20%" o "no encontrado en los datos de la página"
# "free_tier": true | false,
# "free_trial": true | false,
# "free_trial_days": int | null,
# "tiers": [
# {
# "name": str,
# "price_monthly": float | null, # null si es Contactar con ventas
# "price_annual_monthly": float | null, # equivalente mensual cuando se factura anualmente
# "price_note": str, # "Contactar con ventas", "Gratis" o vacío
# "seats": str, # "por asiento", "ilimitado", "hasta 5", etc.
# "key_limits": [str], # almacenamiento, llamadas API, proyectos, etc.
# "key_features": [str] # las 3-5 funciones principales en este nivel
# }
# ],
# "enterprise_tier": true | false,
# "enterprise_pricing": str, # "Contactar con ventas" o precio real
# "regional_pricing": str | null # ej. "India: ₹999/mes" o null
# }
]
… [truncado para la traducción]
Paso 8: Generar Informe (IA)
Imprimir todos los datos estructurados:
python3 -c "
import json
data = json.load(open('/tmp/pf-pricing-extracted.json'))
print(json.dumps(data, indent=2))
"
Instrucciones para la IA: Con los datos estructurados de todos los 5 competidores, genera un informe completo de inteligencia de precios. Sigue references/pricing-models.md para clasificar los modelos de precios y references/positioning-guide.md para el análisis de posicionamiento.
El informe debe:
- Clasificar el modelo de precios dominante en tu espacio L3
- Tabla de referencia de precios: tabla markdown que muestre a todos los competidores, sus niveles, precios, límites y funciones principales
- Análisis de barreras de funciones: qué funciones separan los niveles (ej. usuarios, almacenamiento, integraciones, etc.)
- Mapa de posicionamiento: diagrama de matriz simple en markdown que ubique a cada competidor en ejes relevantes (precio vs. características, o simplicidad vs. potencia)
- Estrategia de precios recomendada: extraída directamente de los datos de referencia
Formato de salida:
# Inteligencia de Precios: [Nombre del Producto]
## Modelo de Precios Dominante
[1-2 oraciones sobre lo que la mayoría usa (por asiento, basado en uso, precio fijo)]
## Tabla de Referencia de Precios
| Competidor | Nivel | Precio/mes | Facturado Anualmente | Asientos | Límites Clave | Funciones Clave |
|---|---|---|---|---|---|---|
| ... | ... | ... | ... | ... | ... | ... |
## Análisis de Barreras de Funciones
[Qué funciones separan los niveles gratuitos vs. de pago, los niveles de gama baja vs. gama alta. Ej.: "El almacenamiento y los usuarios son las principales barreras: todos los competidores comienzan con 1-2 usuarios y 100 MB de almacenamiento en el nivel gratuito"]
## Mapa de Posicionamiento
[Matriz simple 2x2 en markdown que ubique a cada competidor. Ejes basados en los datos -- ej. Precio (bajo a alto) vs. Características (básico a rico)]
## Estrategia de Precios Recomendada
[Dada tu propuesta de valor única y la tabla de referencia, aquí está el precio de entrada recomendado, dónde establecer la barrera de funciones y qué modelo de precios usar. Cita números específicos de la tabla de referencia.]
Paso 9: Resumen
echo "=== INFORME DE INTELIGENCIA DE PRECIOS: $(python3 -c "import json; print(json.load(open('/tmp/pf-product-analysis.json'))['product_name'])") ==="
echo "Competidores: 5"
echo "Páginas de precios obtenidas: $(python3 -c "
import json
d = json.load(open('/tmp/pf-pricing-raw.json'))
high = sum(1 for r in d['results'] if r['data_quality'] == 'high')
med = sum(1 for r in d['results'] if r['data_quality'] == 'medium')
low = sum(1 for r in d['results'] if r['data_quality'] == 'low')
print(f'BUENO: {high}, OK: {med}, SOLO FRAGMENTO: {low}')
")"
echo "Tiempo de ejecución: ~3-5 minutos"