Docs MindStack Suite
07
SRD Backend — MindStack
Auth, billing, subscription management, portal multi-tenant y eventos cross-módulo
Versión
v1.0
Fecha
Mayo 2026
Audiencia
Dev
Estado
DRAFT
MINDSTACK
El sistema operativo de tu PyME. Todo en uno. Sin complicaciones.
Portal — Cheryx Suite

[CONSEJO — Doc 07] Contrarian: El backend de MindStack tiene un riesgo no obvio: es el sistema de billing real de Cheryx. Un bug en el cálculo de una factura puede cobrar de más a un cliente, o no cobrar y perder revenue. La precisión del motor de suscripciones tiene que ser tratada con la misma rigurosidad que el motor de planilla de PayMind — determinístico, con audit log, e inmutable una vez procesada. Executor: En el MVP, ONVO Pay maneja el estado del pago externamente (webhooks). No construir lógica de pagos propia — solo escuchar los webhooks de ONVO y actualizar el estado de la suscripción en la DB. Eso es el 80% del trabajo. El 20% restante (proration, upgrades, créditos) se construye cuando hay clientes reales que los necesitan.


1. Arquitectura

┌─────────────────────────────────────────────────────────────┐
│                    MindStack Backend                         │
│                                                              │
│  FastAPI + Python 3.12                                       │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Auth Service │  │  Billing     │  │ Subscription     │  │
│  │              │  │  Service     │  │ Engine           │  │
│  │ JWT + MFA    │  │              │  │                  │  │
│  │ OAuth Google │  │ ONVO Pay     │  │ Plans, Trials    │  │
│  │              │  │ webhooks     │  │ Metering, Expand │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Tenant       │  │ Partner      │  │ Notification     │  │
│  │ Service      │  │ Service      │  │ Service          │  │
│  │              │  │              │  │                  │  │
│  │ Multi-tenant │  │ CPA referrals│  │ Email (Resend)   │  │
│  │ isolation    │  │ commissions  │  │ WhatsApp         │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
│                                                              │
│  PostgreSQL 16 · Redis · Celery workers                     │
└─────────────────────────────────────────────────────────────┘
                         │
                         │ Redis Events
                         ▼
         ┌───────────────────────────────┐
         │  Módulos MindStack Suite         │
         │  StockMind · SalesMind        │
         │  BookMind · PayMind           │
         └───────────────────────────────┘

2. API endpoints principales

Auth

POST /auth/register          # Registro nuevo usuario
POST /auth/login             # Login email+password
POST /auth/google            # OAuth Google
POST /auth/refresh           # Refresh JWT
POST /auth/mfa/setup         # Configurar TOTP
POST /auth/mfa/verify        # Verificar TOTP
POST /auth/logout

Subscriptions

GET  /subscriptions/plans              # Planes disponibles por módulo
GET  /subscriptions/my                 # Suscripciones activas del tenant
POST /subscriptions/trial              # Iniciar trial de un módulo
POST /subscriptions/checkout           # Crear sesión de checkout ONVO Pay
POST /subscriptions/upgrade            # Cambiar de plan
POST /subscriptions/cancel             # Cancelar suscripción
GET  /subscriptions/invoices           # Historial de facturas
POST /subscriptions/apply-credit       # Aplicar crédito de referidos

Billing webhooks

POST /webhooks/onvo/payment_success    # Pago exitoso → activar suscripción
POST /webhooks/onvo/payment_failed     # Pago fallido → notificar + grace period
POST /webhooks/onvo/subscription_end   # Fin de período → renovar o cancelar
POST /webhooks/tilopay/payment_success # Backup payment processor

Partners

POST /partners/register               # Registro de contador como partner
GET  /partners/referrals              # Referidos del partner
GET  /partners/commissions            # Comisiones generadas
POST /partners/generate-link          # Generar link de referido único

Tenant

GET  /tenant/profile                  # Datos del tenant
PUT  /tenant/profile                  # Actualizar perfil
GET  /tenant/usage                    # Uso por módulo (para metering)
GET  /tenant/modules/status           # Estado de cada módulo activo

3. Flujo de checkout — ONVO Pay

class CheckoutService:
    async def crear_sesion_checkout(
        self,
        tenant_id: UUID,
        plan_id: str,
        modulo: str,
        periodo: Literal['monthly', 'annual']
    ) -> CheckoutSession:

        plan = await self.plans.get(plan_id, modulo)
        precio = plan.precio_anual if periodo == 'annual' else plan.precio_mensual

        # Crear intención de pago en ONVO Pay
        session = await self.onvo.crear_sesion({
            'amount': precio,
            'currency': 'USD',
            'description': f'MindStack {modulo} {plan.nombre} - {periodo}',
            'metadata': {
                'tenant_id': str(tenant_id),
                'plan_id': plan_id,
                'modulo': modulo,
                'periodo': periodo
            },
            'success_url': f'https://mindstack.cheryx.tech/checkout/success?session_id={{CHECKOUT_SESSION_ID}}',
            'cancel_url': f'https://mindstack.cheryx.tech/precios',
        })

        # Guardar sesión pendiente (idempotencia)
        await self.db.execute(
            "INSERT INTO ms_checkout_sessions VALUES (:id, :tenant_id, :plan_id, 'pending', NOW())",
            {'id': session.id, 'tenant_id': tenant_id, 'plan_id': plan_id}
        )
        return session

4. Webhook handler — activación de suscripción

@router.post("/webhooks/onvo/payment_success")
async def onvo_payment_success(payload: ONVOWebhookPayload, db: AsyncSession):
    # Verificar firma HMAC del webhook
    if not verificar_firma_onvo(payload, request.headers.get('X-ONVO-Signature')):
        raise HTTPException(403)

    meta = payload.metadata
    tenant_id = UUID(meta['tenant_id'])
    modulo = meta['modulo']
    plan_id = meta['plan_id']
    periodo = meta['periodo']

    # Activar o renovar suscripción
    subs = await db.execute(
        "SELECT id FROM ms_subscriptions WHERE tenant_id=:tid AND modulo=:mod",
        {'tid': tenant_id, 'mod': modulo}
    )

    if subs.scalar():
        # Renovación
        await db.execute(
            "UPDATE ms_subscriptions SET status='active', period_end=:end WHERE tenant_id=:tid AND modulo=:mod",
            {'end': calcular_period_end(periodo), 'tid': tenant_id, 'mod': modulo}
        )
    else:
        # Nueva suscripción
        await db.execute(
            "INSERT INTO ms_subscriptions (tenant_id, modulo, plan_id, status, period_start, period_end) "
            "VALUES (:tid, :mod, :plan, 'active', NOW(), :end)",
            {'tid': tenant_id, 'mod': modulo, 'plan': plan_id, 'end': calcular_period_end(periodo)}
        )

    # Notificar al módulo vía Redis
    await redis.publish(f'ms:{modulo}:subscription_activated', {
        'tenant_id': str(tenant_id),
        'plan_id': plan_id,
        'period_end': str(calcular_period_end(periodo))
    })

    # Registrar en audit log
    await audit_log.registrar('ms_subscription_activated', tenant_id, {
        'modulo': modulo, 'plan_id': plan_id, 'onvo_payment_id': payload.payment_id
    })

    # Email de bienvenida / renovación
    await notification_service.enviar_bienvenida(tenant_id, modulo)

5. Expansion MRR — triggers automáticos

class ExpansionTriggerService:
    """Monitorea uso y dispara ofertas de cross-sell en el momento correcto."""

    TRIGGERS = {
        'stockmind_to_salesmind': {
            'condicion': lambda uso: uso.get('oc_generadas_30d', 0) >= 50,
            'mensaje': 'Ya sabes qué comprar. ¿Sabes qué más puedes vender?',
            'delay_dias': 30,  # No mostrar antes del día 30
        },
        'salesmind_to_bookmind': {
            'condicion': lambda uso: uso.get('pipeline_valor_usd', 0) >= 50000,
            'mensaje': 'Tu pipeline predice tu tesorería. ¿La estás viendo?',
            'delay_dias': 30,
        },
        'bookmind_to_paymind': {
            'condicion': lambda uso: uso.get('empleados_en_fe', 0) >= 15,
            'mensaje': 'Con 15+ personas, la planilla manual ya no escala.',
            'delay_dias': 30,
        },
    }

    async def evaluar_triggers(self, tenant_id: UUID):
        uso = await self.metering.get_uso_actual(tenant_id)
        subs_activas = await self.subscriptions.get_activas(tenant_id)

        for trigger_key, config in self.TRIGGERS.items():
            modulo_origen, _, modulo_destino = trigger_key.split('_to_')
            if modulo_origen not in subs_activas:
                continue
            if modulo_destino in subs_activas:
                continue  # Ya tiene el módulo
            if not config['condicion'](uso):
                continue

            # Verificar delay mínimo
            suscripcion = subs_activas[modulo_origen]
            dias_activo = (datetime.now() - suscripcion.period_start).days
            if dias_activo < config['delay_dias']:
                continue

            # Disparar oferta in-app (una sola vez)
            await self.ofertas.crear_si_no_existe(tenant_id, modulo_destino, config['mensaje'])

6. Workers de background

Worker Función Frecuencia
billing_worker Procesar renovaciones próximas a vencer Diario 6am
expansion_worker Evaluar triggers de cross-sell por tenant Diario
commission_worker Calcular y acreditar comisiones de partners Mensual
churn_risk_worker Detectar tenants sin login >7 días Diario
email_drip_worker Ejecutar secuencias de onboarding por día Cada hora
usage_aggregate_worker Agregar métricas de uso por módulo Cada 6 horas

Ver también: Doc 08 (Modelo de Datos — tablas ms_) · Doc 09 (Motor Core — subscription engine) · Doc 10 (Seguridad — auth y PCI)*