Architecture · SaaS|14 min de lectura|

Arquitectura SaaS Empresarial: Decisiones que Definen el Negocio

Construir un producto SaaS es diferente a construir software empresarial a medida. Las decisiones de arquitectura en un SaaS tienen consecuencias de negocio directas: el modelo de multi-tenancy determina tus costos operacionales, tu postura de seguridad y qué tan fácil es onboardear un cliente enterprise. Esta guía documenta las decisiones de arquitectura que los equipos SaaS toman (o deberían tomar) en las primeras semanas de desarrollo, antes de que sean difíciles de cambiar.

Multi-tenancy: la decisión arquitectónica central de todo SaaS

Multi-tenancy es cómo tu sistema sirve a múltiples clientes (tenants) desde la misma infraestructura. Los tres modelos principales tienen tradeoffs muy distintos en costo, seguridad y complejidad operacional.

Silo Model: infraestructura dedicada por tenant

Cada tenant tiene su propia instancia de la aplicación y su propia base de datos. El aislamiento es total: un bug o breach en el tenant A no afecta al tenant B. El costo operacional es alto — cada tenant nuevo requiere provisionar infraestructura. Modelo correcto para clientes enterprise con requerimientos de compliance (datos en su propia VPC, control total de la base de datos).

Pool Model: base de datos compartida con tenant_id

Todos los tenants comparten la misma base de datos. El aislamiento se gestiona a nivel de aplicación con un tenant_id en cada tabla. El costo operacional es bajo — onboardear un tenant nuevo es insertar un registro. El riesgo: un bug de tenancy leak (query que olvida el filtro por tenant_id) puede exponer datos de un tenant a otro.

Bridge Model: schema por tenant en la misma instancia de base de datos

Cada tenant tiene su propio schema dentro de la misma instancia de PostgreSQL (o database en la misma instancia de MySQL). Mejor aislamiento que Pool sin el costo de instancias separadas. La complejidad: gestionar migraciones de schema a través de todos los tenants activos.

typescript
// Middleware de multi-tenancy con tenant_id en Pool Model
// El tenant_id se propaga en el contexto de cada request

import { AsyncLocalStorage } from 'node:async_hooks';

const tenantStorage = new AsyncLocalStorage<string>();

// Middleware: extraer tenant del JWT y establecer el contexto
export const tenantMiddleware = async (req: Request, res: Response, next: NextFunction) => {
  const tenantId = req.auth?.tenantId;
  if (!tenantId) return res.status(401).json({ error: 'Tenant no identificado' });

  tenantStorage.run(tenantId, () => next());
};

// Prisma extension: añadir tenant_id automáticamente a todas las queries
export const tenantPrismaExtension = Prisma.defineExtension({
  query: {
    $allModels: {
      async findMany({ args, query }) {
        const tenantId = tenantStorage.getStore();
        if (!tenantId) throw new Error('Tenant context no establecido');
        args.where = { ...args.where, tenant_id: tenantId };
        return query(args);
      },
      async create({ args, query }) {
        const tenantId = tenantStorage.getStore();
        if (!tenantId) throw new Error('Tenant context no establecido');
        args.data = { ...args.data, tenant_id: tenantId };
        return query(args);
      },
    },
  },
});
El Prisma extension (o equivalente en tu ORM) que añade automáticamente el tenant_id a todas las queries es la protección más importante contra tenancy leaks. Sin esto, un developer que olvida el filtro manualmente puede exponer datos entre tenants — y ese bug es muy difícil de detectar en code review.

Billing por uso: el engine financiero del SaaS

El billing de SaaS tiene tres modelos principales:

  • Flat rate: precio fijo mensual. Simple de implementar, predecible para el cliente, pero puede penalizar a los clientes que usan poco y subsidia a los que usan mucho.
  • Per seat: precio por usuario activo. El más común en SaaS B2B. Incentivo natural para que el cliente tenga solo los usuarios que necesita.
  • Usage-based (consumo): precio por unidad de uso (API calls, GB almacenados, transacciones procesadas). Menor barrera de entrada, pero ingresos impredecibles. Tendencia creciente en SaaS de infraestructura y AI.
typescript
// Registro de uso para billing por consumo con Stripe Metered Billing
async function recordApiUsage(tenantId: string, count: number): Promise<void> {
  const subscription = await db.subscription.findUnique({
    where: { tenantId },
    select: { stripeSubscriptionItemId: true }
  });

  if (!subscription?.stripeSubscriptionItemId) return;

  // Stripe acumula el uso y lo factura al final del período
  await stripe.subscriptionItems.createUsageRecord(
    subscription.stripeSubscriptionItemId,
    {
      quantity: count,
      timestamp: Math.floor(Date.now() / 1000),
      action: 'increment',  // acumular, no sobrescribir
    }
  );
}

// Llamar después de cada operación facturable
await recordApiUsage(tenantId, requestCount);

Onboarding automatizado: el flujo que determina la retención

En SaaS B2B, el tiempo-a-valor (time-to-value) es el metric más correlacionado con retención. Un cliente que llega al primer valor concreto en menos de 24 horas tiene 3x más probabilidad de convertir de trial a pago que uno que tarda una semana. El onboarding técnico debe ser completamente automatizado.

typescript
// Pipeline de onboarding de tenant nuevo — completamente automatizado
async function onboardNewTenant(signupData: SignupDto): Promise<Tenant> {
  return db.transaction(async (trx) => {
    // 1. Crear tenant y usuario admin
    const tenant = await Tenant.create({
      name: signupData.companyName,
      plan: 'trial',
      trialEndsAt: addDays(new Date(), 14),
    }, { transaction: trx });

    const adminUser = await User.create({
      tenantId: tenant.id,
      email: signupData.email,
      role: 'admin',
    }, { transaction: trx });

    // 2. Provisionar datos de ejemplo para reducir tiempo-a-valor
    await seedDemoData(tenant.id, trx);

    // 3. Crear customer en Stripe para futura conversión
    const stripeCustomer = await stripe.customers.create({
      email: signupData.email,
      name: signupData.companyName,
      metadata: { tenantId: tenant.id },
    });
    await tenant.update({ stripeCustomerId: stripeCustomer.id }, { transaction: trx });

    // 4. Enviar email de bienvenida con instrucciones
    await emailQueue.add('welcome', {
      to: signupData.email,
      tenantId: tenant.id,
      adminUserId: adminUser.id,
    });

    return tenant;
  });
}

Feature flags y planes: el control de acceso del SaaS

En un SaaS con múltiples planes (Starter, Professional, Enterprise), el control de acceso a features no debe estar hardcodeado como condiciones en el código — eso se convierte en una pesadilla de mantenimiento. El patrón correcto: un sistema de entitlements que mapea plan → features disponibles.

typescript
// Sistema de entitlements para planes SaaS
const PLAN_FEATURES = {
  starter: ['api_access', 'basic_reports', 'email_support'],
  professional: ['api_access', 'advanced_reports', 'webhooks', 'priority_support', 'sso'],
  enterprise: ['api_access', 'advanced_reports', 'webhooks', 'priority_support', 'sso',
               'custom_domain', 'audit_logs', 'dedicated_support', 'sla_guarantee'],
} as const;

async function hasFeature(tenantId: string, feature: string): Promise<boolean> {
  const tenant = await getTenantWithPlan(tenantId);  // cached
  const planFeatures = PLAN_FEATURES[tenant.plan as keyof typeof PLAN_FEATURES] ?? [];
  return (planFeatures as string[]).includes(feature);
}

// En el handler de API:
if (!await hasFeature(tenantId, 'webhooks')) {
  return res.status(402).json({
    error: 'Esta función requiere el plan Professional o superior',
    upgradeUrl: '/settings/billing'
  });
}

Observabilidad SaaS: métricas de negocio como métricas técnicas

En un SaaS, las métricas de negocio deben ser tan visibles como las métricas de infraestructura. MRR (Monthly Recurring Revenue), churn, trial-to-paid conversion, y daily active tenants son métricas que el equipo de ingeniería debe poder leer en el mismo dashboard que la latencia de API.

  • Alertar cuando el número de tenants activos en las últimas 24h baja un 20% vs. el promedio semanal (señal de posible problema en el login o en features críticas)
  • Trackear feature adoption por plan (qué % de tenants Professional usa SSO) para informar decisiones de roadmap
  • Monitorear tiempo promedio de onboarding completion por cohorte de signup

Preguntas frecuentes

¿Cuál modelo de multi-tenancy debo elegir para un SaaS B2B nuevo?
Pool Model (base de datos compartida con tenant_id) para empezar. Es el más simple de implementar, tiene el menor costo operacional, y te permite validar el negocio rápidamente. Si consigues clientes enterprise con requerimientos de aislamiento de datos, puedes ofrecer el Silo Model como una opción premium a mayor precio, manteniendo el Pool Model para el segmento mid-market.
¿Cómo manejo la seguridad de datos entre tenants en el Pool Model?
Las tres capas de defensa: (1) ORM extension que añade tenant_id automáticamente a todas las queries (el más importante), (2) Row Level Security de PostgreSQL como segunda capa de verificación independiente de la aplicación, y (3) tests automáticos de tenancy isolation que verifican que los endpoints no retornan datos de otros tenants. Sin las tres capas, el Pool Model tiene riesgo real de tenancy leak.
¿Stripe o un sistema de billing propio para un SaaS?
Stripe para prácticamente todos los SaaS hasta ARR de varios millones de USD. El costo de Stripe (~2.9% + $0.30/transacción) es insignificante comparado con el costo de implementar y mantener un sistema de billing propio con las capacidades equivalentes: dunning management, múltiples métodos de pago, metered billing, compliance fiscal. Implementar billing propio tiene sentido solo a escala extrema o con requerimientos regulatorios muy específicos.
¿Cómo diseño la arquitectura para soportar SSO/SAML que requieren los clientes enterprise?
Los clientes enterprise típicamente requieren SAML 2.0 o OIDC para SSO con su IdP corporativo (Okta, Azure AD, Google Workspace). La implementación correcta: usar una biblioteca especializada (passport-saml para Node.js, python-saml para Python) o un servicio dedicado (Auth0, WorkOS) que abstrae la complejidad del protocolo. WorkOS específicamente está diseñado para añadir SSO enterprise a un SaaS existente en días, no semanas.
¿Cómo gestiono las migraciones de base de datos en un SaaS con muchos tenants en Bridge Model?
El Bridge Model requiere ejecutar la migración en todos los schemas de todos los tenants. El patrón correcto: migraciones versionadas (Flyway, Liquibase) ejecutadas como parte del deploy, con un script que itera todos los tenants activos y aplica la migración en cada schema. Las migraciones deben ser idempotentes y backward compatible para soportar rollback. Para tenants con schemas muy grandes, ejecutar las migraciones en paralelo con concurrencia controlada.

¿Estás construyendo un producto SaaS y quieres tomar las decisiones de arquitectura correctas desde el inicio? Nuestro equipo puede guiarte en el diseño.

Habla con el equipo

Artículos relacionados

IQS

Equipo de Ingeniería — IQS

Ingenieros de software, cloud y DevOps con experiencia en proyectos empresariales.