Módulo 6: Decision Matrix para AI Engineers
Cápsula 02: Requisitos y Criterios de Decisión
🎯 Objetivo de la cápsula
Aprender a convertir necesidades de producto en criterios medibles que alimenten tu matriz de decisión. Sin criterios claros, cualquier comparación entre vector databases es opinión disfrazada de análisis.
Al finalizar esta cápsula:
- ✅ Identificarás las 6 dimensiones críticas para evaluar vector databases
- ✅ Traducirás requisitos de negocio a umbrales técnicos concretos
- ✅ Construirás un Requirements Document reutilizable en Python
- ✅ Evitarás los errores más comunes al definir criterios
Tiempo estimado: 25-35 minutos
Descripción de la cápsula
Antes de abrir la página de pricing de Pinecone o leer el README de Qdrant, necesitas responder una pregunta fundamental: ¿qué necesita tu proyecto? La mayoría de los equipos saltan directamente a comparar features sin haber definido qué features importan para su caso. El resultado es una decisión basada en hype, en el último tweet viral o en la inercia de "usamos lo que ya conocemos".
Esta cápsula te enseña un proceso sistemático para extraer requisitos de tu proyecto y convertirlos en criterios de decisión cuantificables. No se trata de crear un documento burocrático: se trata de tener claridad antes de invertir semanas en una integración que después tendrás que reemplazar.
El framework que construirás aquí tiene 6 dimensiones: rendimiento, costo, complejidad operativa, calidad del SDK, comunidad/ecosistema y compliance. Cada dimensión se descompone en métricas concretas con umbrales mínimos. Al final, tendrás un "Requirements Document" en Python que podrás reutilizar en cualquier decisión de infraestructura.
Las 6 dimensiones de evaluación
Toda decisión de vector database se puede descomponer en estas 6 dimensiones. No todas pesan igual para todos los proyectos — justamente eso es lo que resolverás en esta cápsula.
Dimensión 1: Rendimiento (Performance)
El rendimiento no es solo "ser rápido". Se descompone en métricas específicas:
performance_criteria = {
"latency_p50_ms": {
"description": "Latencia mediana de búsqueda",
"why_matters": "Experiencia base del usuario promedio",
"typical_ranges": {
"mvp": "< 500ms",
"production": "< 200ms",
"real_time": "< 50ms"
}
},
"latency_p95_ms": {
"description": "Latencia en percentil 95",
"why_matters": "Experiencia del peor 5% de requests",
"typical_ranges": {
"mvp": "< 1000ms",
"production": "< 500ms",
"real_time": "< 100ms"
}
},
"throughput_qps": {
"description": "Queries por segundo soportadas",
"why_matters": "Capacidad de tráfico concurrente",
"typical_ranges": {
"mvp": "10-50 QPS",
"production": "100-500 QPS",
"high_traffic": "1000+ QPS"
}
},
"index_build_time": {
"description": "Tiempo de construcción/actualización del índice",
"why_matters": "Velocidad de ingesta de nuevos documentos",
"typical_ranges": {
"batch": "< 1 hora para 1M vectores",
"near_realtime": "< 5 segundos por documento",
"realtime": "< 100ms por documento"
}
},
"recall_at_k": {
"description": "Porcentaje de resultados correctos en top-K",
"why_matters": "Calidad de los resultados de búsqueda",
"typical_ranges": {
"acceptable": "> 90%",
"good": "> 95%",
"excellent": "> 98%"
}
}
}
Pregunta clave: ¿Tu aplicación es un chatbot interno (latencia tolerante) o un buscador en tiempo real (latencia crítica)?
Dimensión 2: Costo
El costo tiene componentes visibles y ocultos. No cometas el error de comparar solo el sticker price:
cost_criteria = {
"monthly_service_cost": {
"description": "Costo mensual del servicio/infraestructura",
"components": [
"Compute (CPU/RAM)",
"Storage (vectores + metadata)",
"Network egress",
"Backups"
]
},
"cost_per_million_vectors": {
"description": "Costo normalizado por millón de vectores",
"why_matters": "Permite comparación directa entre proveedores",
"typical_ranges": {
"free_tier": "$0 (hasta 10K-100K vectores)",
"startup": "$25-100/millón/mes",
"enterprise": "$100-500/millón/mes"
}
},
"cost_predictability": {
"description": "¿El precio es predecible o basado en uso variable?",
"why_matters": "Sorpresas en la factura mensual",
"models": {
"flat": "Precio fijo por tier (predecible)",
"usage_based": "Pago por query/storage (variable)",
"hybrid": "Base fija + variable por uso"
}
},
"scaling_cost_curve": {
"description": "¿Cómo crece el costo al escalar?",
"why_matters": "El tier gratis de hoy puede ser $2K/mes mañana",
"patterns": {
"linear": "Costo crece proporcional al volumen",
"step_function": "Saltos al cambiar de tier",
"logarithmic": "Descuento por volumen"
}
}
}
Pregunta clave: ¿Cuánto puedes gastar HOY y cuánto esperas gastar en 12 meses?
Dimensión 3: Complejidad operativa
Esta dimensión es la más subestimada y la que más impacto tiene en equipos pequeños:
ops_complexity_criteria = {
"setup_time": {
"description": "Tiempo desde cero hasta primer query funcional",
"typical_ranges": {
"trivial": "< 30 minutos (managed, pip install)",
"moderate": "1-4 horas (Docker, config básica)",
"complex": "1-3 días (cluster, networking, security)"
}
},
"maintenance_hours_monthly": {
"description": "Horas mensuales de mantenimiento esperadas",
"components": [
"Monitoreo y alertas",
"Updates y patches",
"Escalado manual",
"Backup y recovery",
"Debugging de issues"
],
"typical_ranges": {
"managed": "1-3 horas/mes",
"self_hosted_simple": "5-10 horas/mes",
"self_hosted_cluster": "15-25 horas/mes"
}
},
"sre_required": {
"description": "¿Necesitas un SRE/DevOps dedicado?",
"options": {
"no": "Developers pueden operarlo como side-task",
"partial": "DevOps dedica 20-30% de su tiempo",
"yes": "Requiere SRE dedicado o equipo de infra"
}
},
"disaster_recovery": {
"description": "¿Qué tan fácil es recuperarse de un fallo?",
"factors": [
"Backups automáticos vs manuales",
"Tiempo de recovery (RTO)",
"Pérdida de datos aceptable (RPO)",
"Documentación de runbooks"
]
}
}
Pregunta clave: ¿Tu equipo tiene capacidad para operar infraestructura, o cada hora de ops es una hora menos de producto?
Dimensión 4: Calidad del SDK y Developer Experience
Un SDK malo puede costarte más que un pricing alto:
sdk_quality_criteria = {
"language_support": {
"description": "¿Soporta tu stack?",
"critical": ["Python (obligatorio para ML/AI)"],
"important": ["JavaScript/TypeScript", "Go", "Rust"],
"nice_to_have": ["Java", "C#", "Ruby"]
},
"api_design": {
"description": "¿Es intuitiva la API?",
"indicators": [
"Operaciones CRUD en < 5 líneas de código",
"Type hints / autocompletado funcional",
"Manejo de errores claro (no excepciones genéricas)",
"Async support nativo"
]
},
"documentation_quality": {
"description": "¿La documentación es completa y actualizada?",
"checklist": [
"Quick start que funciona en < 10 min",
"API reference completa",
"Ejemplos para casos de uso comunes",
"Guías de migración entre versiones",
"Changelog actualizado"
]
},
"testing_support": {
"description": "¿Puedes testear sin infraestructura real?",
"options": {
"excellent": "In-memory mode para tests",
"good": "Docker compose para CI/CD",
"poor": "Requiere instancia real para testear"
}
}
}
Pregunta clave: ¿Cuánto tiempo inviertes en leer docs y debuggear el SDK vs construir features?
Dimensión 5: Comunidad y ecosistema
La comunidad determina la velocidad a la que resuelves problemas:
community_criteria = {
"github_activity": {
"description": "Actividad del repositorio",
"metrics": [
"Stars (proxy de popularidad, no de calidad)",
"Commits en últimos 90 días",
"Issues abiertas vs cerradas (ratio)",
"Tiempo promedio de respuesta a issues"
]
},
"stackoverflow_presence": {
"description": "¿Encuentras respuestas cuando buscas en Google?",
"why_matters": "Si no hay respuestas en StackOverflow, cada bug es un ticket de soporte"
},
"integration_ecosystem": {
"description": "¿Se integra con tu stack existente?",
"key_integrations": [
"LangChain / LlamaIndex",
"OpenAI / Anthropic embeddings",
"Framework web (FastAPI, Django)",
"Monitoring (Datadog, Prometheus)",
"CI/CD (GitHub Actions, GitLab CI)"
]
},
"enterprise_support": {
"description": "¿Hay soporte pago disponible?",
"options": {
"community_only": "Solo GitHub issues y Discord",
"basic_support": "Email support con SLA",
"enterprise": "Dedicated support engineer, SLA garantizado"
}
}
}
Pregunta clave: Cuando tengas un bug a las 2am, ¿encontrarás la respuesta en 15 minutos o en 3 días?
Dimensión 6: Compliance y seguridad
Para muchos proyectos esto es un criterio eliminatorio, no un "nice to have":
compliance_criteria = {
"data_residency": {
"description": "¿Dónde se almacenan los datos?",
"options": {
"any_region": "No hay restricción geográfica",
"specific_regions": "Datos deben estar en US/EU/LATAM",
"on_premise": "Datos no pueden salir del data center"
}
},
"encryption": {
"description": "¿Qué nivel de cifrado necesitas?",
"levels": {
"basic": "Encryption at rest (AES-256)",
"transit": "Encryption in transit (TLS 1.2+)",
"advanced": "Customer-managed encryption keys (CMEK)"
}
},
"audit_logging": {
"description": "¿Necesitas registro de quién accedió a qué datos?",
"levels": {
"none": "No requerido (MVP, proyecto interno)",
"basic": "Logs de acceso por API key",
"full": "Audit trail completo con timestamps y user identity"
}
},
"certifications": {
"description": "¿Qué certificaciones requiere tu industria?",
"common": ["SOC 2 Type II", "GDPR", "HIPAA", "ISO 27001"],
"note": "La ausencia de una certificación requerida es criterio eliminatorio"
},
"data_isolation": {
"description": "¿Cómo se aíslan los datos entre tenants?",
"models": {
"shared": "Todos en la misma instancia (filtros de metadata)",
"namespace": "Separación lógica por namespace",
"dedicated": "Instancia separada por tenant"
}
}
}
Pregunta clave: ¿Tienes obligaciones regulatorias que eliminen proveedores antes de empezar a comparar?
Traducir requisitos de negocio a técnicos
El error más común es evaluar tecnología con lenguaje de negocio. "Debe ser rápido" no es un criterio. Aquí tienes el proceso de traducción:
def translate_business_to_technical(business_requirements: list[dict]) -> list[dict]:
"""Convierte requisitos vagos de negocio en criterios técnicos medibles."""
translations = {
"fast_responses": {
"business": "Los usuarios premium necesitan respuestas rápidas",
"technical": [
{"metric": "latency_p95_ms", "threshold": 250, "unit": "ms"},
{"metric": "error_rate", "threshold": 1.0, "unit": "%"},
{"metric": "availability", "threshold": 99.9, "unit": "%"}
],
"rationale": "Premium implica SLA estricto, no solo 'rápido'"
},
"handle_growth": {
"business": "Esperamos crecer 10x en el próximo año",
"technical": [
{"metric": "max_vectors", "threshold": 10_000_000, "unit": "vectors"},
{"metric": "horizontal_scaling", "threshold": True, "unit": "bool"},
{"metric": "cost_at_10x", "threshold": 5000, "unit": "USD/month"}
],
"rationale": "10x en vectores no significa 10x en costo si escalas bien"
},
"small_team": {
"business": "Somos 3 developers, no tenemos DevOps",
"technical": [
{"metric": "setup_time_hours", "threshold": 4, "unit": "hours"},
{"metric": "maintenance_hours_monthly", "threshold": 5, "unit": "hours"},
{"metric": "sre_required", "threshold": False, "unit": "bool"}
],
"rationale": "Sin DevOps → managed o self-hosted trivial, nunca cluster"
},
"regulated_industry": {
"business": "Trabajamos con datos de salud (HIPAA)",
"technical": [
{"metric": "hipaa_compliant", "threshold": True, "unit": "bool"},
{"metric": "encryption_at_rest", "threshold": True, "unit": "bool"},
{"metric": "audit_logging", "threshold": True, "unit": "bool"},
{"metric": "data_residency", "threshold": "US", "unit": "region"}
],
"rationale": "HIPAA es eliminatorio: si no cumple, no evalúes nada más"
},
"tight_budget": {
"business": "Máximo $200/mes en infraestructura de búsqueda",
"technical": [
{"metric": "monthly_cost_max", "threshold": 200, "unit": "USD"},
{"metric": "free_tier_vectors", "threshold": 50_000, "unit": "vectors"},
{"metric": "cost_model", "threshold": "predictable", "unit": "type"}
],
"rationale": "Presupuesto fijo requiere pricing predecible, no pay-per-query"
}
}
results = []
for req in business_requirements:
key = req.get("type")
if key in translations:
translation = translations[key]
results.append({
"original": translation["business"],
"criteria": translation["technical"],
"rationale": translation["rationale"]
})
return results
# Ejemplo de uso
my_requirements = [
{"type": "fast_responses"},
{"type": "small_team"},
{"type": "tight_budget"}
]
technical_criteria = translate_business_to_technical(my_requirements)
for item in technical_criteria:
print(f"\nNegocio: {item['original']}")
print(f"Razón: {item['rationale']}")
for c in item["criteria"]:
print(f" → {c['metric']}: {c['threshold']} {c['unit']}")
Salida:
Negocio: Los usuarios premium necesitan respuestas rápidas
Razón: Premium implica SLA estricto, no solo 'rápido'
→ latency_p95_ms: 250 ms
→ error_rate: 1.0 %
→ availability: 99.9 %
Negocio: Somos 3 developers, no tenemos DevOps
Razón: Sin DevOps → managed o self-hosted trivial, nunca cluster
→ setup_time_hours: 4 hours
→ maintenance_hours_monthly: 5 hours
→ sre_required: False bool
Negocio: Máximo $200/mes en infraestructura de búsqueda
Razón: Presupuesto fijo requiere pricing predecible, no pay-per-query
→ monthly_cost_max: 200 USD
→ free_tier_vectors: 50000 vectors
→ cost_model: predictable type
Requirements Document Builder
Aquí tienes una clase completa para construir y validar tu documento de requisitos:
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Priority(Enum):
ELIMINATORY = "eliminatory" # Si no cumple, descarta inmediatamente
CRITICAL = "critical" # Peso alto en la matriz (4-5)
IMPORTANT = "important" # Peso medio (2-3)
NICE_TO_HAVE = "nice_to_have" # Peso bajo (1)
@dataclass
class Criterion:
name: str
dimension: str
priority: Priority
threshold_min: Optional[float | str | bool] = None
threshold_ideal: Optional[float | str | bool] = None
unit: str = ""
notes: str = ""
weight: int = 0
def __post_init__(self):
weight_map = {
Priority.ELIMINATORY: 5,
Priority.CRITICAL: 4,
Priority.IMPORTANT: 2,
Priority.NICE_TO_HAVE: 1,
}
if self.weight == 0:
self.weight = weight_map[self.priority]
@dataclass
class RequirementsDocument:
project_name: str
team_size: int
budget_monthly_usd: float
timeline_months: int
current_vector_count: int
projected_vector_count_12m: int
criteria: list[Criterion] = field(default_factory=list)
def add_criterion(self, criterion: Criterion):
self.criteria.append(criterion)
def get_eliminatory(self) -> list[Criterion]:
return [c for c in self.criteria if c.priority == Priority.ELIMINATORY]
def get_weighted_criteria(self) -> list[Criterion]:
return sorted(
[c for c in self.criteria if c.priority != Priority.ELIMINATORY],
key=lambda c: c.weight,
reverse=True
)
def validate(self) -> list[str]:
"""Verifica que el documento esté completo."""
issues = []
if not self.criteria:
issues.append("No hay criterios definidos")
if not self.get_eliminatory():
issues.append("Sin criterios eliminatorios: ¿realmente nada es deal-breaker?")
if self.budget_monthly_usd <= 0:
issues.append("Presupuesto no definido")
if self.projected_vector_count_12m <= self.current_vector_count:
issues.append("Proyección a 12 meses <= actual: ¿seguro que no creces?")
dimensions_covered = set(c.dimension for c in self.criteria)
expected = {"performance", "cost", "ops", "sdk", "community", "compliance"}
missing = expected - dimensions_covered
if missing:
issues.append(f"Dimensiones sin criterios: {missing}")
return issues
def summary(self) -> str:
lines = [
f"=== Requirements Document: {self.project_name} ===",
f"Equipo: {self.team_size} personas",
f"Presupuesto: ${self.budget_monthly_usd}/mes",
f"Vectores: {self.current_vector_count:,} → {self.projected_vector_count_12m:,} (12m)",
f"Timeline: {self.timeline_months} meses",
"",
"--- Criterios eliminatorios ---",
]
for c in self.get_eliminatory():
lines.append(f" ❌ {c.name}: mínimo {c.threshold_min} {c.unit}")
lines.append("")
lines.append("--- Criterios ponderados ---")
for c in self.get_weighted_criteria():
lines.append(f" [{c.weight}] {c.name}: {c.threshold_min} - {c.threshold_ideal} {c.unit}")
issues = self.validate()
if issues:
lines.append("")
lines.append("--- ⚠️ Issues detectados ---")
for issue in issues:
lines.append(f" ⚠️ {issue}")
return "\n".join(lines)
Ejemplo: startup con RAG chatbot
doc = RequirementsDocument(
project_name="RAG Chatbot para SaaS de e-commerce",
team_size=4,
budget_monthly_usd=300,
timeline_months=3,
current_vector_count=50_000,
projected_vector_count_12m=500_000
)
doc.add_criterion(Criterion(
name="Latencia p95",
dimension="performance",
priority=Priority.CRITICAL,
threshold_min=500,
threshold_ideal=200,
unit="ms"
))
doc.add_criterion(Criterion(
name="Costo mensual",
dimension="cost",
priority=Priority.CRITICAL,
threshold_min=500,
threshold_ideal=200,
unit="USD"
))
doc.add_criterion(Criterion(
name="Sin SRE dedicado",
dimension="ops",
priority=Priority.ELIMINATORY,
threshold_min=True,
unit="bool",
notes="No tenemos DevOps, developers deben poder operarlo"
))
doc.add_criterion(Criterion(
name="Python SDK",
dimension="sdk",
priority=Priority.ELIMINATORY,
threshold_min=True,
unit="bool"
))
doc.add_criterion(Criterion(
name="LangChain integration",
dimension="community",
priority=Priority.IMPORTANT,
threshold_min=True,
unit="bool"
))
doc.add_criterion(Criterion(
name="SOC 2",
dimension="compliance",
priority=Priority.NICE_TO_HAVE,
threshold_min=False,
threshold_ideal=True,
unit="bool",
notes="No requerido ahora, pero será requerido en 18 meses"
))
print(doc.summary())
Salida:
=== Requirements Document: RAG Chatbot para SaaS de e-commerce ===
Equipo: 4 personas
Presupuesto: $300/mes
Vectores: 50,000 → 500,000 (12m)
Timeline: 3 meses
--- Criterios eliminatorios ---
❌ Sin SRE dedicado: mínimo True bool
❌ Python SDK: mínimo True bool
--- Criterios ponderados ---
[4] Latencia p95: 500 - 200 ms
[4] Costo mensual: 500 - 200 USD
[2] LangChain integration: True - None bool
[1] SOC 2: False - True bool
Proceso de priorización: MoSCoW adaptado
No todos los criterios pesan igual. Usa esta adaptación del framework MoSCoW para priorizar:
def prioritize_criteria(criteria_list: list[dict]) -> dict:
"""
Clasifica criterios usando MoSCoW adaptado para vector databases.
Must Have → Eliminatorio (si no cumple, descarta)
Should Have → Crítico (peso 4-5 en la matriz)
Could Have → Importante (peso 2-3)
Won't Have → Excluir de esta evaluación
"""
prioritized = {
"must_have": [], # Eliminatorios
"should_have": [], # Críticos para el scoring
"could_have": [], # Diferenciadores secundarios
"wont_have": [], # Fuera de scope
}
decision_rules = {
"must_have": [
"Si no cumple este criterio, ¿descartas la opción?",
"¿Es un requisito legal/regulatorio?",
"¿Tu app no funciona sin esto?"
],
"should_have": [
"¿Impacta directamente la experiencia del usuario?",
"¿Impacta el costo operativo mensual?",
"¿Tiene que ver con la escala de los próximos 12 meses?"
],
"could_have": [
"¿Es un diferenciador entre dos opciones empatadas?",
"¿Lo necesitarás en 18+ meses?",
"¿Es preferencia del equipo más que requisito del producto?"
],
"wont_have": [
"¿Es una feature que no usarás en los próximos 18 meses?",
"¿Es un criterio de otra categoría de producto?",
"¿Agregaría ruido a la evaluación?"
]
}
for criterion in criteria_list:
category = criterion.get("moscow", "could_have")
prioritized[category].append(criterion["name"])
return prioritized
# Ejemplo
my_criteria = [
{"name": "Python SDK disponible", "moscow": "must_have"},
{"name": "Latencia p95 < 300ms", "moscow": "must_have"},
{"name": "Costo < $500/mes", "moscow": "should_have"},
{"name": "Hybrid search nativo", "moscow": "should_have"},
{"name": "GraphQL API", "moscow": "wont_have"},
{"name": "Multi-region replication", "moscow": "could_have"},
{"name": "SOC 2 Type II", "moscow": "could_have"},
{"name": "GPU acceleration", "moscow": "wont_have"},
]
result = prioritize_criteria(my_criteria)
for category, items in result.items():
print(f"\n{category.upper().replace('_', ' ')}:")
for item in items:
print(f" • {item}")
Definir horizontes temporales
Un error frecuente es mezclar requisitos del presente con proyecciones futuras sin distinguirlos. Define tres horizontes:
def define_horizons(
current_vectors: int,
growth_rate_monthly: float,
current_qps: float,
qps_growth_monthly: float
) -> dict:
"""
Proyecta requisitos en tres horizontes temporales.
Args:
current_vectors: Vectores actuales
growth_rate_monthly: Tasa de crecimiento mensual (0.1 = 10%)
current_qps: Queries por segundo actuales
qps_growth_monthly: Crecimiento mensual de QPS
"""
horizons = {}
for label, months in [("now", 0), ("6_months", 6), ("12_months", 12)]:
vectors = int(current_vectors * (1 + growth_rate_monthly) ** months)
qps = current_qps * (1 + qps_growth_monthly) ** months
horizons[label] = {
"vectors": vectors,
"qps": round(qps, 1),
"storage_gb_estimate": round(vectors * 1536 * 4 / 1e9, 2),
}
return horizons
projections = define_horizons(
current_vectors=100_000,
growth_rate_monthly=0.15,
current_qps=20,
qps_growth_monthly=0.10
)
for horizon, data in projections.items():
print(f"\n{horizon}:")
print(f" Vectores: {data['vectors']:,}")
print(f" QPS: {data['qps']}")
print(f" Storage estimado: {data['storage_gb_estimate']} GB")
Salida:
now:
Vectores: 100,000
QPS: 20
Storage estimado: 0.61 GB
6_months:
Vectores: 231,306
QPS: 35.4
Storage estimado: 1.42 GB
12_months:
Vectores: 535,029
QPS: 62.8
Storage estimado: 3.28 GB
Anti-patrón: criterios que parecen buenos pero no sirven
bad_criteria = [
{
"criterion": "Debe ser rápido",
"problem": "No hay número. ¿Rápido es 100ms o 2 segundos?",
"fix": "p95 < 250ms para queries de 1536 dimensiones, top-10"
},
{
"criterion": "Debe escalar",
"problem": "¿Escalar a qué? ¿100K vectores o 100M?",
"fix": "Soportar 2M vectores en 12 meses con latencia < 300ms p95"
},
{
"criterion": "Buena documentación",
"problem": "Subjetivo. ¿Qué es 'buena'?",
"fix": "Quick start funcional en < 15 min, API reference con ejemplos Python"
},
{
"criterion": "Barato",
"problem": "¿Comparado con qué? ¿Incluye costo operativo?",
"fix": "TCO < $400/mes incluyendo 5h/mes de mantenimiento a $60/hr"
},
{
"criterion": "El que use más gente",
"problem": "Popularidad ≠ adecuado para tu caso",
"fix": "Comunidad activa: >50 respuestas en SO, issues cerradas en <7 días"
},
]
for bad in bad_criteria:
print(f"❌ '{bad['criterion']}'")
print(f" Problema: {bad['problem']}")
print(f" ✅ Mejor: '{bad['fix']}'")
print()
🔧 Troubleshooting
Problema 1: "Tengo demasiados criterios y no sé cuáles importan"
Síntoma: Tu lista tiene 15+ criterios y no puedes decidir pesos.
Solución: Aplica la regla del "si no cumple, ¿descartas?" Si la respuesta es sí, es eliminatorio. Si la respuesta es "depende", baja a should/could. Limita tus criterios ponderados a máximo 6 — más de eso diluye la señal.
Problema 2: "El equipo no se pone de acuerdo en las prioridades"
Síntoma: El CTO quiere performance, el PM quiere costo bajo, el developer quiere buen SDK.
Solución: Haz que cada stakeholder asigne pesos de forma independiente (sin ver los de otros). Promedia los pesos y discute solo las diferencias de más de 2 puntos. Esto elimina el sesgo de la persona más vocal.
Problema 3: "No tengo datos para definir umbrales"
Síntoma: No sabes qué latencia necesitas porque nunca mediste.
Solución: Usa benchmarks de referencia de la industria como punto de partida (p95 < 500ms para chatbots, < 100ms para autocompletado). Marca estos umbrales como "provisionales" y actualiza después del primer PoC con datos reales.
Problema 4: "Los requisitos cambian cada semana"
Síntoma: El product manager trae nuevos requisitos constantemente y tu evaluación nunca termina.
Solución: Congela los criterios por ciclo de evaluación (típicamente 2-4 semanas). Documenta nuevos requisitos como "v2" pero no los incorpores a la evaluación en curso. Revalida trimestralmente.
Problema 5: "Compliance me elimina casi todas las opciones"
Síntoma: HIPAA/SOC2/GDPR reduce tus opciones a 1-2 proveedores.
Solución: Esto es correcto — compliance es eliminatorio por diseño. Si solo quedan 1-2 opciones, tu evaluación es más simple: compáralas entre sí o evalúa si self-hosted con compliance propio es viable.
🏋️ Ejercicios
Ejercicio 1: Traduce requisitos de negocio
Tu PM te dice: "Necesitamos un sistema de búsqueda que sea rápido, que no cueste mucho, que lo pueda mantener el equipo y que cumpla con GDPR porque tenemos usuarios en Europa." Traduce cada frase a criterios técnicos con umbral.
Solución
translated = {
"rápido": {
"criteria": [
{"metric": "latency_p95_ms", "threshold": 300},
{"metric": "latency_p50_ms", "threshold": 150},
],
"rationale": "Sin más contexto, 300ms p95 es un buen default para chatbot/search"
},
"no cueste mucho": {
"criteria": [
{"metric": "monthly_tco_usd", "threshold": 500},
{"metric": "cost_model", "threshold": "predictable"},
],
"rationale": "Pedir presupuesto exacto al PM. 'No mucho' = definir número"
},
"lo pueda mantener el equipo": {
"criteria": [
{"metric": "maintenance_hours_monthly", "threshold": 5},
{"metric": "sre_required", "threshold": False},
{"metric": "setup_time_hours", "threshold": 4},
],
"rationale": "Sin DevOps dedicado, mantenimiento debe ser < 5h/mes"
},
"cumpla con GDPR": {
"criteria": [
{"metric": "data_residency_eu", "threshold": True},
{"metric": "data_deletion_api", "threshold": True},
{"metric": "encryption_at_rest", "threshold": True},
{"metric": "dpa_available", "threshold": True},
],
"rationale": "GDPR es eliminatorio: región EU, derecho al olvido, DPA firmable"
}
}
for phrase, data in translated.items():
print(f"\n'{phrase}':")
print(f" Razón: {data['rationale']}")
for c in data["criteria"]:
print(f" → {c['metric']}: {c['threshold']}")
Ejercicio 2: Construye un Requirements Document
Crea un RequirementsDocument completo para este escenario: startup fintech, equipo de 6 developers (1 DevOps part-time), presupuesto de $800/mes, 200K vectores actuales, proyección 2M en 12 meses, regulación SOC 2 requerida.
Solución
doc = RequirementsDocument(
project_name="Fintech RAG - Documentos regulatorios",
team_size=6,
budget_monthly_usd=800,
timeline_months=12,
current_vector_count=200_000,
projected_vector_count_12m=2_000_000
)
doc.add_criterion(Criterion(
name="SOC 2 Type II", dimension="compliance",
priority=Priority.ELIMINATORY,
threshold_min=True, unit="bool",
notes="Requerido por contrato con clientes enterprise"
))
doc.add_criterion(Criterion(
name="Encryption at rest", dimension="compliance",
priority=Priority.ELIMINATORY,
threshold_min=True, unit="bool"
))
doc.add_criterion(Criterion(
name="Soportar 2M vectores", dimension="performance",
priority=Priority.ELIMINATORY,
threshold_min=2_000_000, unit="vectors"
))
doc.add_criterion(Criterion(
name="Latencia p95", dimension="performance",
priority=Priority.CRITICAL,
threshold_min=500, threshold_ideal=200, unit="ms"
))
doc.add_criterion(Criterion(
name="TCO mensual", dimension="cost",
priority=Priority.CRITICAL,
threshold_min=1200, threshold_ideal=600, unit="USD",
notes="$800 servicio + horas DevOps part-time"
))
doc.add_criterion(Criterion(
name="Mantenimiento mensual", dimension="ops",
priority=Priority.IMPORTANT,
threshold_min=15, threshold_ideal=5, unit="hours/month",
notes="DevOps part-time: máximo 15h/mes en este proyecto"
))
doc.add_criterion(Criterion(
name="Python SDK async", dimension="sdk",
priority=Priority.IMPORTANT,
threshold_min=True, unit="bool"
))
doc.add_criterion(Criterion(
name="LangChain integration", dimension="community",
priority=Priority.NICE_TO_HAVE,
threshold_min=True, unit="bool"
))
print(doc.summary())
issues = doc.validate()
print(f"\nValidación: {'✅ Sin issues' if not issues else '⚠️ ' + str(len(issues)) + ' issues'}")
Ejercicio 3: Horizontes temporales
Proyecta los requisitos para un e-commerce con 80K vectores actuales, crecimiento de 20% mensual, 15 QPS actuales con crecimiento de 12% mensual. ¿En qué horizonte temporal superas el free tier típico de 100K vectores?
Solución
import math
current = 80_000
growth = 0.20
months_to_100k = math.log(100_000 / current) / math.log(1 + growth)
print(f"Superas 100K vectores en {months_to_100k:.1f} meses")
months_to_500k = math.log(500_000 / current) / math.log(1 + growth)
print(f"Superas 500K vectores en {months_to_500k:.1f} meses")
months_to_1m = math.log(1_000_000 / current) / math.log(1 + growth)
print(f"Superas 1M vectores en {months_to_1m:.1f} meses")
projections = define_horizons(
current_vectors=80_000,
growth_rate_monthly=0.20,
current_qps=15,
qps_growth_monthly=0.12
)
for horizon, data in projections.items():
print(f"\n{horizon}: {data['vectors']:,} vectores, {data['qps']} QPS")
# Resultado: superas 100K en ~1.2 meses
# A 20% mensual, necesitas planear tier pago desde el inicio
Ejercicio 4: Identifica criterios eliminatorios
Para cada escenario, identifica qué criterios son eliminatorios (Must Have) y justifica:
- App de salud con datos de pacientes en EU
- Hackathon de 48 horas
- SaaS multi-tenant con 50 clientes enterprise
Solución
scenarios = {
"health_app_eu": {
"eliminatory": [
"GDPR compliance (datos de pacientes en EU)",
"HIPAA si incluye datos de salud de US",
"Data residency en EU",
"Encryption at rest y in transit",
"Audit logging completo"
],
"rationale": "Un breach de datos de salud puede costar millones en multas"
},
"hackathon_48h": {
"eliminatory": [
"Setup en < 15 minutos",
"Free tier disponible",
"Python SDK funcional"
],
"rationale": "En un hackathon, time-to-first-query es el único criterio real"
},
"saas_multi_tenant": {
"eliminatory": [
"Data isolation entre tenants",
"Soportar 50+ namespaces/collections",
"API key o auth por tenant",
"Escalado horizontal sin downtime"
],
"rationale": "Un data leak entre tenants destruye la confianza de todos tus clientes"
}
}
for scenario, data in scenarios.items():
print(f"\n=== {scenario} ===")
for criterion in data["eliminatory"]:
print(f" ❌ MUST: {criterion}")
print(f" Razón: {data['rationale']}")
Ejercicio 5: Puntúa la calidad de un SDK
Evalúa el SDK de Python de cualquier vector database que hayas usado (o elige ChromaDB) usando esta rúbrica. Asigna un puntaje de 0 a 1.0 por cada sub-criterio.
Solución (ejemplo con ChromaDB)
chromadb_sdk_evaluation = {
"installation": {
"score": 1.0,
"evidence": "pip install chromadb. Funciona en 30 segundos"
},
"first_query_time": {
"score": 0.9,
"evidence": "Hello world funcional en < 5 minutos con docs"
},
"type_hints": {
"score": 0.7,
"evidence": "Type hints parciales, autocompletado funciona pero no perfecto"
},
"error_messages": {
"score": 0.6,
"evidence": "Algunos errores son crípticos (DimensionalityException sin contexto)"
},
"async_support": {
"score": 0.5,
"evidence": "AsyncClient existe pero no documentado prominentemente"
},
"testing_support": {
"score": 0.9,
"evidence": "EphemeralClient perfecto para tests, sin infra externa"
},
"documentation": {
"score": 0.8,
"evidence": "Docs claros para casos básicos, faltan ejemplos avanzados"
}
}
total = sum(v["score"] for v in chromadb_sdk_evaluation.values())
max_score = len(chromadb_sdk_evaluation)
normalized = total / max_score
print(f"ChromaDB SDK Score: {normalized:.2f} / 1.0")
for criterion, data in chromadb_sdk_evaluation.items():
print(f" {criterion}: {data['score']} — {data['evidence']}")
🔗 Conexión con proyecto: Decision Questionnaire
En tu proyecto final (Decision Questionnaire), los criterios que defines aquí son el input de todo el sistema. Tu cuestionario debe:
- Preguntar al usuario por cada dimensión (performance, costo, ops, SDK, comunidad, compliance)
- Traducir las respuestas a criterios técnicos con umbrales (usando la función
translate_business_to_technical) - Generar el RequirementsDocument automáticamente
- Validar que no falten dimensiones críticas
El código de RequirementsDocument y Criterion de esta cápsula será la base de tu módulo de requisitos.
Resumen
- Define criterios ANTES de evaluar proveedores. Sin criterios claros, cualquier comparación es opinión.
- Las 6 dimensiones cubren todo: performance, costo, ops, SDK, comunidad, compliance.
- Cada criterio necesita un número concreto. "Rápido" no sirve; "p95 < 250ms" sí.
- Los criterios eliminatorios se evalúan primero. Si no pasan, no pierdas tiempo puntuando.
- Distingue horizontes temporales: lo que necesitas hoy vs 6 vs 12 meses.
- La capacidad del equipo es un criterio técnico. No ignores el factor humano.
- Usa MoSCoW para priorizar: Must/Should/Could/Won't evita el "todo es importante".
- Documenta supuestos. Cuando los requisitos cambien, sabrás qué reevaluar.
Recursos adicionales
- Decision Matrix Analysis (MindTools) — Framework general de matrices de decisión
- MoSCoW Prioritization — Método de priorización por categorías
- ANN Benchmarks — Benchmarks de performance para vector databases
- Pinecone Documentation — Ejemplo de docs de un managed provider
- ChromaDB Documentation — Ejemplo de docs de un open-source provider
- Qdrant Documentation — Ejemplo de docs de un hybrid provider
- GDPR Requirements for Data Processors — Requisitos de GDPR para procesadores de datos
- SOC 2 Compliance Guide — Guía práctica de SOC 2
Tiempo estimado: 25-35 minutos
Siguiente: 03-pesos-scoring-matriz.md