SaaS
Estado del arte en SaaS — multi-tenencia, facturación basada en uso, PLG, integración de funciones de IA, observabilidad moderna, cumplimiento normativo y seguridad para productos SaaS listos para producción.
Instrucciones
Multi-Tenencia
Elige el modelo de aislamiento adecuado para tu caso de uso:
| Modelo | Descripción | Cuándo usarlo |
|---|---|---|
| Esquema compartido | Todos los tenants comparten tablas; las filas se etiquetan con tenant_id | Etapa temprana, sensible al costo, cumplimiento sencillo |
| Esquema por tenant | Esquema de BD separado por tenant | Aislamiento medio, migraciones fáciles por tenant |
| BD por tenant | Base de datos separada por tenant | Empresarial, residencia de datos estricta, alto cumplimiento (SOC 2, HIPAA) |
| Híbrido | Datos centrales compartidos; datos sensibles aislados | Industrias reguladas con ofertas de niveles mixtos |
Reglas para tenencia de esquema compartido:
- Cada consulta debe filtrar por
tenant_id— imponerlo mediante un repositorio base o middleware ORM, no mediante la disciplina del desarrollador - Usa la Seguridad a Nivel de Fila (RLS) de PostgreSQL como segunda capa de defensa:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.current_tenant')::uuid); - Indexa
tenant_iden cada tabla con ámbito de tenant - Escribe pruebas de integración que verifiquen el aislamiento de datos entre tenants
- Define rutas en el almacenamiento de objetos:
s3://bucket/{tenantId}/uploads/{fileId} - Nunca expongas IDs secuenciales predecibles — usa UUIDs (v7 para ordenabilidad)
Suscripción y Facturación
Suscripciones de tarifa plana
- Delega la facturación a un proveedor (Stripe, Paddle, LemonSqueezy) — nunca implementes el procesamiento de pagos internamente
- Almacena solo
customerIdysubscriptionIden tu BD; nunca datos de tarjeta - Sincroniza el estado mediante webhooks — trata tu BD como una réplica de solo lectura eventualmente consistente de la verdad del proveedor de facturación:
customer.subscription.created → activar plan customer.subscription.updated → actualizar / degradar / cambiar asientos customer.subscription.deleted → cancelar + iniciar período de gracia invoice.payment_failed → proceso de cobro: notificar → restringir → cancelar - Usa el
iddel evento webhook como clave de idempotencia — almacénalo antes de procesar; omítelo si ya se ha visto - Almacena los payloads de webhook sin procesar para auditoría y reproducción
Facturación basada en uso / por consumo (predeterminado moderno)
- Rastrea eventos de uso a medida que ocurren (llamadas API, asientos, tokens consumidos, GB de almacenamiento):
await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, { quantity: tokensUsed, timestamp: Math.floor(Date.now() / 1000), action: 'increment', }); - Proporciona un panel de uso en vivo a los clientes — las sorpresas en el día de facturación causan abandono
- Implementa límites suaves (advertir al 80%) y límites duros (bloquear al 100%) por período de facturación
- Considera un nivel gratuito + actualización basada en uso como rampa de entrada para el Crecimiento Impulsado por Producto (PLG)
Crecimiento Impulsado por Producto (PLG)
- Nivel gratuito / freemium — permite a los usuarios experimentar el valor antes de pedir pago; establece límites que hagan que los planes de pago sean obviamente valiosos
- Tiempo hasta el valor (TTV) — mide y minimiza el tiempo desde el registro hasta la primera acción significativa ("momento ajá")
- Indicaciones de actualización en la aplicación — muestra llamadas a la acción de actualización en el momento contextual en que se alcanza un límite del plan, no en una página de configuración genérica
- Clientes potenciales calificados por producto (PQL) — puntúa a los usuarios por señales de uso del producto (no solo solicitudes de demostración) para derivar a ventas
- Funciones virales / colaborativas — invitar a compañeros de equipo, compartir informes, páginas públicas — cada acción es un canal de distribución
Integración de Funciones de IA
Se espera que los productos SaaS modernos incluyan capacidades nativas de IA. Aplica estos patrones:
Asistentes y copilotos de IA:
- Usa respuestas en streaming (SSE /
ReadableStream) para que los usuarios vean los tokens a medida que llegan — nunca los hagas esperar por una respuesta completa - Siempre haz que el resultado de la IA sea editable — los usuarios necesitan corregir, refinar y poseer el resultado
- Muestra señales de confianza y cita fuentes (RAG) para generar confianza
Generación Aumentada por Recuperación (RAG):
- Almacena embeddings vectoriales por tenant en un espacio de nombres aislado (Pinecone, pgvector, Qdrant) — nunca mezcles datos de tenants
- Incrusta contenido en la escritura; consulta en la lectura con similitud de coseno
- Fragmenta documentos de manera inteligente (512–1024 tokens con superposición); re-incrusta cuando el contenido de origen cambie
Controles de uso y costos:
- Rastrea el consumo de tokens LLM por tenant — factura o limita la velocidad en consecuencia
- Almacena en caché prompts deterministas (misma entrada → misma salida) para reducir costos
- Usa modelos más baratos para clasificación/triaje; reserva modelos frontera para tareas de generación
Privacidad de datos:
- Ofrece un anexo de tratamiento de datos (DPA) que indique explícitamente si los datos del tenant se utilizan para el entrenamiento del modelo
- Proporciona una opción de exclusión; los clientes empresariales la exigirán
Feature Flags y Control de Acceso por Plan
- Controla el acceso a funciones en la capa de servicio — la ocultación en el cliente es un adorno de UI, no seguridad
- Centraliza la lógica de elegibilidad en un helper único
can(tenant, feature):function can(tenant: Tenant, feature: Feature): boolean { return tenant.plan.features.includes(feature); } - Separa el despliegue de funciones de su lanzamiento usando un sistema de flags (LaunchDarkly, Unleash, GrowthBook o una tabla simple respaldada por BD)
- Usa flags para: despliegues graduales (% de tenants), programas beta, pruebas A/B, interruptores de apagado
Onboarding
- Minimiza el TTV: guía a los usuarios a su primera acción significativa en < 5 minutos
- Usa un flujo de onboarding basado en checklist con indicadores de progreso claros
- Precarga datos de muestra / demo para que el producto no se sienta vacío en el primer uso
- Recopila la cantidad mínima de datos por adelantado; difiere todo lo demás
- Dispara correos electrónicos de onboarding basados en eventos del producto (no solo en el tiempo): bienvenida al registrarse, recordatorio si no han completado el paso 2 después de 24h, celebración del primer hito
- Ofrece un recorrido interactivo del producto (no un video) para que los usuarios aprendan haciendo
Observabilidad y Operaciones
Registro estructurado — cada línea de log debe incluir:
{ "level": "info", "msg": "...", "tenantId": "...", "userId": "...", "requestId": "...", "timestamp": "..." }
Trazado distribuido con OpenTelemetry:
- Instrumentaliza a nivel del framework (Next.js
instrumentation.ts, middleware Express) - Propaga el contexto de traza a través de los límites del servicio (cabecera
traceparent) - Exporta a tu backend de elección (Jaeger, Honeycomb, Datadog, Grafana Tempo)
Métricas por tenant a seguir:
- Usuarios activos (DAU/MAU), volumen de llamadas API, tasa de error, latencia p50/p95/p99, adopción de funciones, almacenamiento consumido
Alertas:
- Alerta sobre anomalías a nivel de tenant — pico repentino de errores, caída de uso, fallos de facturación
- Define SLO por nivel: gratuito (mejor esfuerzo), pro (99.5%), empresarial (99.9%)
Herramientas de administración (internas):
- Suplantar a un tenant para soporte (auditar cada suplantación)
- Ver registro de auditoría, gestionar suscripciones, anular feature flags, disparar re-onboarding
- Construir un panel de salud: estado por tenant, errores recientes, tendencias de uso
Datos, Analítica y Cumplimiento Normativo
Seguimiento de eventos:
- Emite eventos de producto estructurados (
user.signed_up,feature.used,subscription.upgraded) a un data warehouse (Snowflake, BigQuery) o CDP (Segment, PostHog, RudderStack) - Usa estos eventos para la predicción de abandono, análisis de cohortes y puntuación PLG
GDPR / privacidad:
- Proporciona un endpoint de exportación de datos y un endpoint de eliminación de cuenta — requerido por GDPR y esperado por compradores empresariales
- Documenta tu política de retención de datos; implementa purgas automatizadas
- Usa gestión de consentimiento para cookies de marketing y analítica
SOC 2 / higiene de cumplimiento (preparación empresarial):
- Mantén un registro de auditoría de todas las acciones de usuario que cambian el estado (quién, qué, cuándo, desde qué IP)
- Implementa control de acceso basado en roles (RBAC) con privilegios mínimos por defecto
- Exige MFA para todas las cuentas de administración; ofrece SSO (SAML/OIDC) para tenants empresariales
- Realiza escaneos regulares de vulnerabilidades de dependencias y pruebas de penetración
- Mantén una política de divulgación de vulnerabilidades
Aislamiento de Datos y Seguridad
- Valida
tenant_iden cada llamada API contra la sesión autenticada — nunca confíes en el cuerpo de la solicitud - Limita la velocidad por tenant para evitar efectos de vecino ruidoso
- Hashea las claves API en la BD (bcrypt o Argon2); nunca almacenes texto plano
- Exige HTTPS; establece
Strict-Transport-Securitycon unmax-agelargo - Usa
HttpOnly; Secure; SameSite=Laxpara las cookies de sesión
Ejemplos
// Repositorio con ámbito de tenant y contexto RLS
class TenantRepository<T> {
constructor(
private readonly tenantId: string,
private readonly db: PrismaClient,
) {}
protected async withTenantContext<R>(fn: () => Promise<R>): Promise<R> {
return this.db.$transaction(async (tx) => {
await tx.$executeRaw`SELECT set_config('app.current_tenant', ${this.tenantId}, true)`;
return fn();
});
}
async findById(id: string): Promise<T | null> {
return this.withTenantContext(() =>
(this.db as any).findFirst({ where: { id, tenantId: this.tenantId } })
);
}
}
// Endpoint de streaming con IA y medición de uso por tenant
export async function POST(req: Request) {
const session = await getSession(req);
const { prompt } = await req.json();
const stream = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
stream: true,
});
let tokensUsed = 0;
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content ?? '';
controller.enqueue(new TextEncoder().encode(text));
tokensUsed += chunk.usage?.completion_tokens ?? 0;
}
controller.close();
// medir uso después de que el streaming se complete
await meteringService.record(session.tenantId, 'ai_tokens', tokensUsed);
},
});
return new Response(readable, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
}
// Webhook de Stripe con idempotencia
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body, req.headers['stripe-signature']!, process.env.STRIPE_WEBHOOK_SECRET!
);
const seen = await db.webhookEvent.findUnique({ where: { stripeEventId: event.id } });
if (seen) return res.sendStatus(200);
await db.webhookEvent.create({ data: { stripeEventId: event.id, payload: event } });
switch (event.type) {
case 'customer.subscription.updated':
await subscriptionService.sync(event.data.object);
break;
case 'customer.subscription.deleted':
await subscriptionService.cancel(event.data.object.metadata.tenantId);
break;
}
res.sendStatus(200);
});