Docs SalesMind
09
Motor Core — SalesMind
Motor de elasticidad de precio, scoring de leads y demand multi-store
Versión
v1.0
Fecha
Mayo 2026
Audiencia
Engineering · Data Science
Estado
DRAFT
SALESMIND
El cerebro y las manos de tu venta.
Módulo #2 — Cheryx Suite

[CONSEJO — Doc 09] Contrarian: Tres motores estadísticos coordinados en V1.0 es demasiado ambicioso para un equipo de 4-6 personas con 24 meses de build. La tentación de implementar todo en el primer lanzamiento es real — y peligrosa. Priorizar: Motor de elasticidad de precio (diferenciador más visible) en V1.0. Scoring de leads y demand multi-store pueden ir en V1.1. First Principles: El motor de elasticidad de precio es el feature que ningún competidor POS LATAM tiene. Es el diferenciador que justifica el ARPU de $449–$1,299. Si ese motor falla o da malas sugerencias en los primeros 30 días, el cliente lo desactiva y nunca más lo usa. Executor: El cold start del motor de elasticidad requiere ≥90 días de historial de ventas a diferentes precios. Para clientes que instalan SalesMind sin historial, el motor debe ser honesto: "No tenemos suficientes datos para estimar la elasticidad. El motor se activa cuando haya ≥90 días de ventas registradas." Security Auditor: Los datos de precios y ventas por SKU son comercialmente sensibles. El motor de elasticidad no puede usar datos de un tenant para estimar elasticidades de otro tenant. El modelo es per-tenant, no global.


1. Los tres motores de SalesMind

1.1 Motor 1 — Elasticidad de precio (V1.0)

Propósito: estimar la elasticidad-precio por SKU y sugerir el precio óptimo que maximiza revenue o margen, dado el stock disponible y el contexto de mercado.

Metodología: regresión log-log (elasticidad constante) con variables adicionales:

log(quantity_sold) = α + β × log(price) + γ × log(stock_level) 
                   + δ × season_index + ε × competitor_price_index 
                   + η × promo_flag + error

# β es la elasticidad-precio (típicamente negativa: -0.5 a -2.5 para bienes de consumo)
# Un β = -1.0 significa que un 1% de aumento de precio → 1% caída en demanda (elasticidad unitaria)
# Un β = -0.3 significa inelástico: 1% subida de precio → solo 0.3% caída en demanda

Precio óptimo (máximo de revenue):

Para maximizar revenue: P* = P_actual × (1 + 1/(1+β))
Para maximizar margen: P* ajustado por cost_per_unit

Input del motor: - sl_sale_items: historial de ventas (fecha, precio, cantidad) - sm_skus.stock_current: nivel de stock actual (afecta urgencia de venta) - sl_pricing_history: historial de precios anteriores aplicados

Output: sl_pricing_suggestions con precio sugerido + banda de confianza + explicación HTML.

Cold start protocol: - Si <90 días de historial: no generar sugerencia. Status: "Activación en {N} días — acumulando datos." - Si historial sin variación de precio (precio constante): no se puede estimar elasticidad. Status: "Variación de precio necesaria — ajusta manualmente el precio 1-2 veces para calibrar el motor."

Guardrails de pricing: - G1: nunca sugerir precio <costo unitario (margen negativo) - G2: máximo variación permitida por sugerencia: ±20% del precio actual (configurable por tenant) - G3: si el cliente configura precio mínimo/máximo por SKU, la sugerencia respeta esos límites - G4: precio sugerido siempre requiere aprobación del gerente (nunca se aplica automáticamente)

1.2 Motor 2 — Scoring de leads (V1.1)

Propósito: estimar la probabilidad de cierre de cada oportunidad en el pipeline CRM.

Metodología: logistic regression + gradient boosting (scikit-learn), con features:

features = [
    # Features del cliente
    'customer_sector',         # ferretería, distribuidora, etc.
    'customer_purchase_count', # cuántas veces ha comprado antes
    'customer_ltv',            # LTV histórico
    'days_since_last_purchase',

    # Features de la oportunidad
    'lead_value',              # MRR estimado
    'days_in_current_stage',   # cuántos días lleva en esta etapa
    'activity_count_30d',      # número de actividades en los últimos 30 días
    'has_demo_done',           # booleano
    'has_proposal_sent',       # booleano

    # Features del rep
    'rep_close_rate_90d',      # tasa de cierre histórica del rep
    'rep_avg_cycle_days',      # ciclo de venta promedio del rep
]

# Output: probability_close (0.0 a 1.0)
# Threshold para "hot lead": >0.70
# Threshold para "at risk": <0.30 en stage ≥ 'demo'

Cold start: si el tenant tiene <20 oportunidades históricas cerradas, usar modelo de benchmark de industria (pesos genéricos calibrados con datos del sector retail LATAM). Se activa el modelo tenant-específico cuando hay ≥20 oportunidades con resultado conocido.

1.3 Motor 3 — Demand multi-store (V1.1)

Propósito: para clientes con múltiples tiendas, proyectar la demanda por tienda × SKU × semana para optimizar el reabastecimiento entre tiendas.

Metodología: hierarchical forecasting con reconciliación MinT (Hyndman et al. 2011):

Nivel total → Nivel tienda → Nivel SKU×tienda
Reconciliación top-down + bottom-up con método MinT (Minimum Trace)
Reutiliza el motor statsforecast de StockMind con extensión jerárquica

Output: tabla de transferencias recomendadas entre tiendas para las próximas 4 semanas.


2. Arquitectura técnica de los motores

Celery Worker: pricing_worker (diario 4am)
   │
   ├── Lee sl_sale_items + sl_pricing_history (últimos 180 días)
   ├── Ejecuta regresión log-log por SKU (scikit-learn)
   ├── Calcula precio óptimo + banda de confianza
   ├── Aplica guardrails G1-G4
   ├── Genera explicación HTML (Jinja2)
   └── Escribe en sl_pricing_suggestions

Celery Worker: scoring_worker (cada 6h)
   │
   ├── Lee sl_leads con stage en ['prospect', 'contacted', 'demo', 'proposal']
   ├── Extrae features por oportunidad
   ├── Aplica modelo logistic regression
   └── Actualiza sl_leads.score

Celery Worker: demand_worker (diario 3am, V1.1)
   │
   ├── Lee sl_sale_items por tienda × SKU × semana
   ├── Ejecuta statsforecast con reconciliación jerárquica
   └── Genera recomendaciones de transferencia entre tiendas

3. Validación y calidad del motor

3.1 Tests del motor de elasticidad

def test_elasticity_negative_for_normal_goods():
    """La elasticidad-precio debe ser negativa para bienes normales."""
    # Fixture: serie temporal donde mayor precio → menor cantidad
    result = estimate_elasticity(PRICE_DOWN_DEMAND_UP_SERIES)
    assert result.beta < 0

def test_optimal_price_never_below_cost():
    """El precio óptimo nunca puede ser menor al costo unitario."""
    result = suggest_optimal_price(
        elasticity=-0.8,
        current_price=100,
        unit_cost=80
    )
    assert result.suggested_price >= 80  # Guardrail G1

def test_cold_start_returns_no_suggestion():
    """Con <90 días de historial, el motor no genera sugerencia."""
    result = run_pricing_motor(
        sales_history=SERIES_WITH_60_DAYS,
        sku_id='test-sku'
    )
    assert result.status == 'cold_start'
    assert result.suggested_price is None

3.2 Métricas de calidad del motor de pricing

Métrica Meta Alarma
% SKUs con elasticidad estimada ≥60% del catálogo activo <40% → datos insuficientes
Tasa de aprobación de sugerencias ≥50% <30% → sugerencias malas
% sugerencias dentro del ±20% del precio actual ≥90% <80% → guardrail inefectivo
Revenue lift post-aprobación vs. control ≥2% <0% → motor no aporta valor

Ver también: Doc 07 (SRD Backend — workers que ejecutan estos motores) · Doc 08 (Modelo de Datos — tablas de input/output de cada motor) · Doc 06 (SRD Frontend — cómo se presentan las sugerencias al usuario) · wiki/05-motor-estadistico.md SalesMind (spec técnica extendida)