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:

  1. App de salud con datos de pacientes en EU
  2. Hackathon de 48 horas
  3. 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:

  1. Preguntar al usuario por cada dimensión (performance, costo, ops, SDK, comunidad, compliance)
  2. Traducir las respuestas a criterios técnicos con umbrales (usando la función translate_business_to_technical)
  3. Generar el RequirementsDocument automáticamente
  4. 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

  1. Decision Matrix Analysis (MindTools) — Framework general de matrices de decisión
  2. MoSCoW Prioritization — Método de priorización por categorías
  3. ANN Benchmarks — Benchmarks de performance para vector databases
  4. Pinecone Documentation — Ejemplo de docs de un managed provider
  5. ChromaDB Documentation — Ejemplo de docs de un open-source provider
  6. Qdrant Documentation — Ejemplo de docs de un hybrid provider
  7. GDPR Requirements for Data Processors — Requisitos de GDPR para procesadores de datos
  8. SOC 2 Compliance Guide — Guía práctica de SOC 2

Tiempo estimado: 25-35 minutos Siguiente: 03-pesos-scoring-matriz.md