Saltar al contenido principal

Construir un SaaS en 2026: Lo que Nadie te Cuenta Antes de Empezar

He lanzado dos SaaS en el último año. Aquí van las decisiones técnicas, los errores caros y lo que haría diferente: arquitectura, autenticación, multitenancy, pricing, despliegue y lo que nadie menciona.

Fran Cobos 8 min de lectura 1489 palabras

Tabla de contenidos

🎯 Lo que aprenderás en este artículo

  • El multitenancy es una decisión de arquitectura que no puedes añadir después sin reescribir
  • El auth no es un módulo de 2 horas: JWT + roles + refresh tokens bien hechos son 2-3 días
  • Empieza con un solo servidor, no con microservicios
  • El pricing y los planes de suscripción son tan complejos de implementar como el producto
  • Deploy en VPS propio con Coolify reduce costes un 70% frente a Vercel/Railway en producción

He lanzado dos SaaS en el último año: Atrapaclientes (NestJS + React, gestión de campañas multitenant) y iECO (FastAPI + Next.js, SaaS de IA para reuniones). Ambos están en producción con clientes reales.

Esto es lo que me habría ahorrado tiempo saber antes de empezar.


1. La arquitectura que no puedes cambiar después

Hay dos decisiones que si las tomas mal te obligan a reescribir el proyecto entero:

Multitenancy desde el día 1

Si tu SaaS tiene múltiples empresas o usuarios con datos separados, el company_id (o tenant_id) tiene que estar en todas las tablas desde el principio. No “cuando lo necesite”. Ahora.

-- MAL: añadir tenant_id después
ALTER TABLE meetings ADD COLUMN company_id UUID;
-- Ahora tienes que hacer data migration, actualizar todos los queries,
-- añadir índices, revisar todos los endpoints... 2-3 días de trabajo.

-- BIEN: desde el diseño inicial
CREATE TABLE meetings (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id UUID NOT NULL REFERENCES companies(id),
  title VARCHAR(255) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_meetings_company_id ON meetings(company_id);

Y lo más importante: nunca filtres por company_id en el controller. Hazlo en una capa de middleware que lo inyecte automáticamente:

// NestJS: Guard que inyecta el tenant en cada request
@Injectable()
export class TenantGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest();
    req.companyId = req.user.companyId; // del JWT
    return true;
  }
}

// El service solo ve los datos de su tenant
async findAll(companyId: string): Promise<Meeting[]> {
  return this.repo.find({ where: { companyId } });
}

Si te olvidas de añadir el filtro en un endpoint, tienes un data leak entre clientes. El guard lo hace imposible de olvidar.

Para ver este patrón en acción en un proyecto real, revisa Caso Real: SaaS Multitenant con NestJS y React.

Monolito modular, no microservicios

Todos los tutoriales de SaaS en YouTube muestran microservicios. La realidad:

  • Con microservicios necesitas service discovery, comunicación entre servicios (gRPC/mensajería), distributed tracing, y cada servicio tiene su propia CI/CD, logs y monitoring.
  • Un solo developer o equipo pequeño no tiene tiempo para todo eso.

Lo que funciona en la práctica:

src/
  modules/
    auth/          # JWT, refresh tokens, RBAC
    companies/     # Gestión de tenants
    users/         # Usuarios por tenant
    billing/       # Stripe, suscripciones
    [feature]/     # Módulos de negocio
  common/
    guards/        # TenantGuard, AuthGuard, RolesGuard
    decorators/    # @CurrentUser(), @Roles()
    filters/       # Manejo global de errores

Cada módulo es independiente. Si en el futuro necesitas extraer billing como microservicio, lo puedes hacer. Pero no lo hagas prematuramente.


2. El auth que parece simple y no lo es

JWT de “Hello World” son 30 minutos. JWT de producción en un SaaS son 2-3 días.

Lo que necesitas realmente:

// Lista de lo que necesitas implementar, no un "npm install passport"

// ✅ Access token (15-60 min expiración)
// ✅ Refresh token (7-30 días, rotación en cada uso)
// ✅ Revocación de refresh tokens (tabla en BD o Redis)
// ✅ Logout en todos los dispositivos
// ✅ Rate limiting en /auth/login
// ✅ Bloqueo por intentos fallidos
// ✅ Roles: superadmin / company_admin / company_user
// ✅ Permisos granulares dentro de cada rol
// ✅ Middleware que verifica rol en cada endpoint

El flujo de refresh tokens es lo que más gente omite y luego duele:

@Post('refresh')
async refresh(@Body() dto: RefreshTokenDto) {
  // 1. Verificar que el refresh token existe en BD y no está revocado
  const stored = await this.tokenRepo.findOne({ token: dto.refreshToken });
  if (!stored || stored.revokedAt) throw new UnauthorizedException();

  // 2. Verificar expiración
  if (stored.expiresAt < new Date()) throw new UnauthorizedException();

  // 3. Rotación: revocar el anterior, crear uno nuevo
  await this.tokenRepo.update(stored.id, { revokedAt: new Date() });
  const newRefreshToken = await this.tokenRepo.save({
    userId: stored.userId,
    token: generateSecureToken(),
    expiresAt: addDays(new Date(), 30),
  });

  return {
    accessToken: this.jwt.sign({ sub: stored.userId }),
    refreshToken: newRefreshToken.token,
  };
}

Para una guía completa de JWT en Node.js: Cómo Proteger una API Node.js con JWT.


3. El flujo de registro que nadie piensa

En un SaaS multitenant, el flujo de registro no es solo “email + contraseña”. Necesitas decidir:

¿Registro libre o con aprobación?

  • Registro libre: el usuario se registra y accede inmediatamente. Simple, bueno para B2C.
  • Con aprobación: el usuario solicita acceso, un admin lo aprueba. Necesario cuando el acceso tiene coste o cuando quieres controlar quién usa el producto.

El flujo con aprobación que implementé en iECO:

Usuario rellena formulario de solicitud

Estado: PENDING (no puede hacer login)

Notificación al superadmin

Superadmin aprueba → Estado: ACTIVE → email de confirmación
Superadmin rechaza → Estado: REJECTED → email de rechazo

Usuario activo puede hacer login

En la BD:

CREATE TYPE user_status AS ENUM ('pending', 'active', 'rejected', 'suspended');

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  status user_status DEFAULT 'pending',
  company_id UUID REFERENCES companies(id),
  role VARCHAR(50) DEFAULT 'company_user',
  approved_at TIMESTAMPTZ,
  approved_by UUID REFERENCES users(id)
);

4. El panel de admin que necesitarás aunque no lo planifiques

Todo SaaS acaba necesitando un panel de administración. Si no lo planificas, lo construirás reactivamente cuando un cliente llame diciendo que no puede entrar.

Lo mínimo viable:

  • Gestión de usuarios: listar, suspender, cambiar rol, resetear contraseña
  • Gestión de empresas (si es multitenant): crear, editar, suspender tenant
  • Solicitudes pendientes: aprobar/rechazar registros
  • Logs básicos: últimos accesos, errores críticos

Un truco: separa claramente los endpoints de admin con un prefijo y un guard dedicado:

@Controller('admin')
@UseGuards(AuthGuard, RolesGuard)
@Roles('superadmin')
export class AdminController {
  // Solo superadmin puede acceder aquí
}

5. El despliegue: self-hosted es la opción más inteligente a escala pequeña

Los costes de Vercel/Railway/Render escalan rápido. Para un SaaS small con <50.000 visitas/mes:

OpciónCoste/mesControlSetup
Vercel Pro$20+BajoMínimo
Railway$5-30+MedioFácil
Hetzner VPS + Coolify~5€Total2-3h inicial

Con Coolify en un VPS tienes SSL automático, deploys desde GitHub, gestión de variables de entorno y todas las apps que quieras en el mismo servidor.

Guía completa: Coolify: Despliega tus Apps sin ser DevOps y el Análisis de costes Vercel vs VPS.


6. Lo que nadie menciona: el tiempo no técnico

El código es la parte fácil. Lo que no verás en tutoriales:

Stripe y los impuestos: integrar Stripe Billing para suscripciones recurrentes lleva 1 semana. Gestionar IVA para clientes europeos, facturas, créditos y upgrades/downgrades: otra semana más.

Los emails transaccionales: registro, bienvenida, aprobación, factura, aviso de pago fallido, recordatorio de trial… fácilmente 15-20 emails distintos. Cada uno con HTML que funcione en Outlook y en Gmail. No los subestimes.

El onboarding: el usuario que se registra a las 11 de la noche y no entiende cómo funciona el producto va a abrir un ticket. El onboarding (guía inicial, tooltips, email de día 1, email de día 7) reduce el churn más que cualquier feature nueva.

El GDPR: para clientes europeos necesitas política de privacidad, aviso de cookies, mecanismo de borrado de cuenta y portabilidad de datos. No es opcional.


Stack que volvería a usar

Después de dos SaaS en producción, esto es lo que elegiría hoy:

Backend:   NestJS + TypeORM + PostgreSQL
           (FastAPI si hay mucho procesamiento IA)
Frontend:  Next.js 15 + TypeScript + Tailwind CSS + shadcn/ui
Auth:      JWT propio (access + refresh) o Auth.js para auth social
Pagos:     Stripe Billing
Email:     Resend + React Email
Deploy:    Hetzner VPS + Coolify
Monitoring: Sentry (errores) + Plausible (analytics)

Lo que no volvería a usar para un SaaS pequeño:

  • Microservicios (complejidad innecesaria)
  • Supabase Auth (pierde control sobre los tokens y los roles)
  • MongoDB (las relaciones entre datos en un SaaS son relacionales por naturaleza)

Por dónde empezar

Si vas a construir un SaaS hoy:

  1. Define los roles y el multitenancy antes de escribir código
  2. Diseña el schema de BD con tenant_id desde el día 1
  3. Implementa auth completo (access + refresh + roles) en la primera semana
  4. Construye el panel de admin básico en paralelo con el producto
  5. Despliega en VPS desde el inicio, no esperes a tener clientes

El orden importa. El multitenancy y el auth son los únicos elementos que no puedes añadir sin reescribir. Todo lo demás (features, UI, integraciones) se puede añadir después.


Recursos relacionados

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.