Los 3 Problemas Que Más Tiempo Te Van a Quitar al Construir un SaaS

Después de construir 35+ aplicaciones SaaS en iAmanos, puedo decirte con certeza que el 80% del sufrimiento técnico viene de solo 3 áreas: autenticación, pagos y multitenant.

No es la UI. No es la base de datos. No es el deployment. Esas cosas son trabajo, pero son trabajo predecible. Auth, pagos y multitenant son los que te despiertan a las 3am con un bug que no entiendes, un edge case que no consideraste, o una decisión arquitectónica que tomaste en el día 1 y que ahora te persigue.

Este artículo es el compilado de TODO lo que aprendimos resolviendo estos 3 retos en producción. Con código real, gotchas documentadas, y las decisiones exactas que tomamos en cada app. No teoría — trinchera pura.

Reto 1: Autenticación — NextAuth v5 en Next.js 16 (El Gotcha del proxy.ts)

La autenticación parece simple hasta que la implementas en producción. Entonces descubres que hay 47 formas de hacerlo mal y 2 de hacerlo bien.

Nuestra decisión: NextAuth v5 con credentials provider

Usamos NextAuth v5 en todas nuestras apps. ¿Por qué?

  • Estándar de la industria: La biblioteca de auth más usada para Next.js, con documentación robusta
  • Flexible: Soporta credentials (email/password), OAuth (Google, GitHub), magic links
  • JWT sessions: Stateless, no necesitas Redis ni session store
  • Integración con Prisma: Adapter oficial para persistir usuarios y sesiones

El gotcha de Next.js 16: proxy.ts, no middleware.ts

Este es el error más común y el que más tiempo nos costó descubrir. En Next.js 16, el archivo de middleware cambió de nombre:

  • Next.js 14-15: middleware.ts en la raíz del proyecto
  • Next.js 16: proxy.ts en la raíz del proyecto

Si creas un middleware.ts en Next.js 16, no se ejecuta. No da error, no da warning — simplemente se ignora silenciosamente. Tu auth parece funcionar en desarrollo pero en producción cualquiera puede acceder a rutas protegidas.

// proxy.ts (Next.js 16) — NO middleware.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';

export default auth((req) => {
  const isLoggedIn = !!req.auth;
  const isPublicRoute = [
    '/login',
    '/register',
    '/api/auth',
  ].some(route => req.nextUrl.pathname.startsWith(route));

  if (!isLoggedIn && !isPublicRoute) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  return NextResponse.next();
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Otro gotcha: cookies() y searchParams son Promises

En Next.js 16, cookies() y searchParams son ahora Promises. Si los usas sin await, obtienes [object Promise] en lugar del valor. Esto rompe la autenticación de formas sutiles y difíciles de debuggear.

// INCORRECTO (Next.js 16)
const session = cookies().get('session'); // undefined!

// CORRECTO (Next.js 16)
const cookieStore = await cookies();
const session = cookieStore.get('session'); // funciona

RBAC (Role-Based Access Control)

Para apps con múltiples roles (admin, asesor, cliente), implementamos RBAC con un campo role en el modelo User de Prisma y validación en el proxy.ts:

// En el JWT callback de NextAuth
callbacks: {
  jwt({ token, user }) {
    if (user) {
      token.role = user.role; // 'admin' | 'user' | 'asesor'
    }
    return token;
  },
  session({ session, token }) {
    session.user.role = token.role;
    return session;
  },
}

Y luego en cada ruta de API o Server Action verificamos el rol:

const session = await auth();
if (session?.user?.role !== 'admin') {
  return NextResponse.json({ error: 'No autorizado' }, { status: 403 });
}

Simple, efectivo, y funciona en producción sin problemas.

Reto 2: Pagos — Stripe vs MercadoPago y el Problema de la Facturación en México

Los pagos son el reto que convierte un “proyecto” en un “negocio”. Y en México tiene una capa de complejidad que no existe en EE.UU.: el CFDI (Comprobante Fiscal Digital por Internet).

Stripe vs MercadoPago: nuestra decisión por app

No es una decisión de “uno u otro” — depende del tipo de app y del cliente:

Usamos Stripe cuando:

  • El cliente necesita suscripciones recurrentes (SaaS mensual)
  • Acepta pagos con tarjeta de crédito/débito internacional
  • Necesitas webhooks robustos para actualizar estado de suscripción
  • El flujo de checkout debe ser embebido en la app (Stripe Elements)

Stripe cobra 3.6% + $3 MXN por transacción en México. La API es la mejor de la industria: documentación impecable, SDKs en todos los lenguajes, sandbox que replica producción al 100%.

Usamos MercadoPago cuando:

  • El cliente necesita aceptar pagos en OXXO (crítico en México — millones de personas no tienen tarjeta bancaria)
  • Necesitas transferencia SPEI como método de pago
  • El mercado objetivo son PYMEs mexicanas que usan MercadoPago como wallet
  • Necesitas split payments para marketplaces

MercadoPago cobra 3.49% + $4 MXN por transacción. La API es funcional pero menos pulida que Stripe. El sandbox tiene quirks que no existen en producción y viceversa.

El problema del CFDI

En México, toda venta debe generar un CFDI (factura electrónica) si el cliente lo solicita. Esto no es opcional — es ley fiscal. Ni Stripe ni MercadoPago generan CFDIs directamente. Opciones:

  • Facturama API: Servicio mexicano que genera CFDIs. Se integra con webhook post-pago. ~$1,500 MXN/mes plan básico.
  • Factura.com: Similar a Facturama, interfaz más simple. Tiene API REST.
  • SAT directo: Puedes generar CFDIs con el web service del SAT, pero la complejidad es enorme. No recomendado a menos que tengas un equipo dedicado.

Nuestro approach: integrar Facturama vía API. Cuando el pago se confirma (webhook de Stripe/MercadoPago), automáticamente se genera el CFDI con los datos fiscales del cliente.

Suscripción vs compra única

Decisión que afecta TODO tu modelo de negocio:

  • Suscripción mensual: Ingreso recurrente predecible. Más fácil de vender (“$377/mes” suena accesible). Requiere gestión de cancelaciones, upgrades, downgrades, cobros fallidos.
  • Compra única: Cash flow inmediato. Sin churn. Pero necesitas vender constantemente nuevos clientes. WouWou ofrece ambos: $377/mes o $11,999 una vez.

Reto 3: Multitenant — Base de Datos por Tenant vs Tenant ID Compartido

El multitenancy es la decisión arquitectónica que más impacta la complejidad de tu SaaS. Hay 3 modelos principales:

Modelo A: Base de datos por tenant (nuestro favorito)

Cada cliente tiene su propia base de datos PostgreSQL en un contenedor Docker aislado.

  • Pros: Aislamiento total de datos, backups por cliente, performance predecible, fácil de migrar/eliminar un tenant
  • Contras: Más contenedores (más RAM), migraciones deben aplicarse a todas las DBs, no puedes hacer queries cross-tenant fácilmente
  • Lo usamos en: WouWou, GlamBook, FisioCore — apps donde cada clínica/salón es un mundo independiente

Modelo B: Tenant ID compartido (schema compartido)

Todos los tenants comparten la misma base de datos. Cada tabla tiene una columna tenant_id que filtra los datos.

  • Pros: Una sola DB (menos recursos), migraciones únicas, queries cross-tenant triviales, más fácil para analytics
  • Contras: Riesgo de data leak si olvidas filtrar por tenant_id, performance puede degradarse con muchos tenants, backups son all-or-nothing
  • Lo usamos en: Lead Desk, el Cotizador — apps donde los datos se cruzan entre tenants (ej: pipeline de ventas compartido)

Modelo C: Schema por tenant (PostgreSQL schemas)

Una base de datos, pero cada tenant tiene su propio schema de PostgreSQL.

  • Pros: Balance entre aislamiento y eficiencia de recursos
  • Contras: Prisma 7 no soporta cambio dinámico de schema fácilmente. Complejidad de management.
  • Lo usamos en: Ninguna app actualmente. El Modelo A o B cubre todos nuestros casos.

Nuestra regla de decisión

Si los datos del cliente son sensibles y nunca se cruzan entre tenants → DB por tenant. Si los datos necesitan visión global o los tenants interactúan → tenant_id compartido.

8 Gotchas de Producción Que Nos Costaron Horas

1. Prisma 7 CLI falla en Docker build

El CLI de Prisma 7 intenta conectarse a la base de datos durante prisma generate en el build de Docker. Solución: crear las tablas vía psql directo en vez de prisma migrate deploy dentro del Dockerfile.

2. NextAuth v5 no refresca el token JWT automáticamente

Si cambias el rol de un usuario en la base de datos, su JWT sigue teniendo el rol viejo hasta que expire. Solución: implementar refresh de token en el callback de session, o usar session maxAge corto (1 hora).

3. Webhooks de Stripe no llegan en localhost

Necesitas Stripe CLI con stripe listen --forward-to localhost:3000/api/webhooks/stripe para testing local. Sin esto, no puedes probar el flujo completo de pago + activación.

4. MercadoPago sandbox es inconsistente

El sandbox de MercadoPago a veces devuelve errores que no existen en producción, y a veces acepta datos que producción rechaza. Siempre prueba en producción con montos pequeños ($1 MXN) antes de ir live.

5. CFDI requiere datos fiscales completos

RFC, razón social, régimen fiscal, código postal fiscal, uso CFDI. Si no capturas todo esto antes del pago, no puedes generar la factura después. Agrega el formulario fiscal ANTES del checkout.

6. Rate limiting en auth es obligatorio

Sin rate limiting, un bot puede intentar 10,000 combinaciones de email/password por minuto en tu endpoint de login. Implementa al menos 5 intentos por IP por minuto.

7. Cookies secure en producción

NextAuth necesita NEXTAUTH_URL con https:// en producción para que las cookies secure funcionen. Si tienes http://, el login funciona pero la sesión no persiste entre requests.

8. PostgreSQL connections por tenant

Cada DB por tenant usa un pool de conexiones. Con 20 tenants y Prisma default (5 connections/pool), son 100 conexiones a PostgreSQL. Si tu VPS tiene poca RAM, reduce el pool con connection_limit en el schema.

El Stack Que Recomendamos Para SaaS en México en 2026

Después de 35+ apps, este es el stack que resuelve auth, pagos y multitenant de la forma más eficiente:

  • Auth: NextAuth v5 + credentials provider + JWT + proxy.ts (Next.js 16)
  • Pagos suscripción: Stripe Checkout + Stripe Billing + webhooks
  • Pagos OXXO/SPEI: MercadoPago Checkout Pro
  • Facturación: Facturama API para CFDI automático
  • Multitenant: DB por tenant (Docker PostgreSQL aislado) para datos sensibles, tenant_id compartido para datos operativos
  • ORM: Prisma 7 con PrismaPg adapter + tipos generados
  • Validación: Zod en frontend y backend — nunca confíes en datos del cliente

Este stack está probado en producción con usuarios reales en México. No es teórico — es lo que corre hoy en todas nuestras apps.

Los 5 Errores Más Caros Que Cometimos en Auth y Pagos

Error 1: Sesiones infinitas sin expiración

En las primeras apps, los JWT tokens no expiraban. Un token robado daba acceso permanente. Ahora todos nuestros tokens tienen maxAge de 24 horas y refresh automático en cada request. Si el usuario no interactúa en 24h, necesita loguearse de nuevo.

Error 2: No validar webhooks de pago

En testing, aceptábamos cualquier POST al endpoint de webhook sin verificar la firma de Stripe. En producción, esto significa que cualquiera podría enviar un webhook falso y activar una suscripción sin pagar. La verificación con stripe.webhooks.constructEvent() es OBLIGATORIA.

Error 3: CORS abierto en rutas de auth

Tuvimos una app donde las rutas de API de autenticación tenían Access-Control-Allow-Origin: *. Esto significa que cualquier sitio web podría intentar autenticarse con nuestros endpoints. Ahora restringimos CORS al dominio específico de cada app.

Error 4: Password en logs de servidor

Un console.log de debugging quedó en producción y registraba el body completo de los requests de login, incluyendo passwords en texto plano. Regla absoluta: NUNCA loguear el body de requests de auth. Y revisar que no haya logs de debug antes de deploy.

Error 5: No tener plan de downgrade

¿Qué pasa cuando un cliente cancela su suscripción? En las primeras apps, simplemente se le bloqueaba el acceso inmediatamente. Los clientes se enojaban porque tenían datos que necesitaban exportar. Ahora damos un período de gracia de 15 días post-cancelación con acceso read-only.

Matriz de Decisión: Qué Implementar Según Tu Tipo de SaaS

SaaS B2B (vendes a empresas)

  • Auth: NextAuth v5 + RBAC (admin, manager, user) + SSO opcional (Google, Microsoft)
  • Pagos: Stripe para suscripciones + Facturama para CFDI (las empresas SIEMPRE piden factura)
  • Multitenant: DB por tenant si los datos son sensibles, tenant_id si necesitas reportes cross-tenant

SaaS B2C (vendes a personas)

  • Auth: NextAuth v5 + magic links o Google OAuth (reduce fricción de registro)
  • Pagos: MercadoPago (OXXO es crítico para B2C en México) + Stripe para tarjetas internacionales
  • Multitenant: Generalmente tenant_id compartido (los datos de usuarios individuales rara vez necesitan aislamiento total)

SaaS Marketplace (múltiples vendedores y compradores)

  • Auth: NextAuth v5 + roles (vendedor, comprador, admin) + verificación de identidad para vendedores
  • Pagos: Stripe Connect o MercadoPago Split Payments (para dividir el pago entre marketplace y vendedor)
  • Multitenant: Tenant_id compartido obligatorio (necesitas queries cross-tenant para búsquedas, rankings, reportes)

SaaS Freemium (plan gratuito + planes de pago)

  • Auth: NextAuth v5 + registro simple (email/password) + upgrade flow
  • Pagos: Stripe Billing con múltiples plans + feature flags para limitar funcionalidad por plan
  • Multitenant: Tenant_id compartido (los usuarios free y de pago comparten la misma infraestructura, solo cambian los límites)

La decisión correcta depende de tu modelo de negocio, no de preferencias técnicas. En cada app que construimos en iAmanos, evaluamos estas opciones en los primeros 30 minutos del sprint y documentamos la decisión en el CLAUDE.md.

Checklist Completo: Auth + Pagos + Multitenant Para tu Primer SaaS

Si estás por construir tu primer SaaS en México, aquí está el checklist que seguimos en iAmanos para cada proyecto nuevo:

Antes de empezar

  • Definir modelo de negocio: ¿suscripción, licencia, freemium?
  • Definir roles de usuario: ¿admin, staff, cliente? ¿Cuántos niveles?
  • Definir si los datos son sensibles (¿necesitan aislamiento por tenant?)
  • Investigar requisitos fiscales: ¿los clientes van a pedir factura CFDI?
  • Definir método de pago principal: ¿tarjeta, OXXO, transferencia?

Auth (primeras 4 horas)

  • Instalar NextAuth v5 con adapter de Prisma
  • Configurar credentials provider (email + password hashed con bcrypt)
  • Crear proxy.ts (NO middleware.ts en Next.js 16)
  • Configurar JWT callback con role del usuario
  • Crear páginas de login y registro con validación Zod
  • Verificar que cookies funcionen en HTTPS (NEXTAUTH_URL con https://)
  • Implementar rate limiting en endpoint de login (máximo 5 intentos/min por IP)

Pagos (día 2-3)

  • Crear cuenta en Stripe y/o MercadoPago
  • Configurar precios/planes en el dashboard del procesador
  • Implementar checkout flow (Stripe Checkout o MercadoPago Checkout Pro)
  • Configurar webhook endpoint para recibir eventos de pago
  • Implementar lógica de activación/desactivación basada en estado de pago
  • Agregar manejo de cobros fallidos y cancelaciones
  • Si B2B: integrar Facturama para CFDI + capturar datos fiscales pre-pago
  • Probar flujo completo en sandbox antes de ir a producción

Multitenant (decisión arquitectónica)

  • Datos sensibles entre tenants → DB por tenant (Docker PostgreSQL aislado)
  • Datos que se cruzan → tenant_id compartido con filtro obligatorio en cada query
  • Si tenant_id: agregar validación automática en middleware de Prisma para nunca devolver datos de otro tenant
  • Crear script de onboarding que provisione el nuevo tenant (crear DB, seed datos iniciales, configurar DNS si aplica)

Este checklist nos ha ahorrado innumerables horas de reescritura. Las decisiones que tomas en los primeros 2 días determinan la arquitectura de los próximos 2 años.

Una nota final sobre la evolución de estos tres retos: la autenticación se resolvió hace años (NextAuth es maduro y estable). El multitenancy tiene patrones bien documentados. Los pagos en México son el área con más fricción porque el ecosistema está fragmentado (Stripe + MercadoPago + CFDI + OXXO = 4 integraciones para lo que en EE.UU. son 1-2). Invierte el tiempo extra en pagos — es donde los clientes mexicanos son más exigentes y donde la experiencia de usuario marca la diferencia entre un SaaS que convierte y uno que pierde clientes en el checkout.

En nuestra experiencia con las apps de iAmanos, la integración de auth toma un día, la de pagos toma tres, y la decisión de multitenancy se toma en los primeros 30 minutos pero sus consecuencias duran años. Si pudiéramos volver al inicio con todo lo que sabemos hoy, la única cosa que cambiaríamos es: implementar pagos desde la primera semana en vez de dejarlo para “después”. Los clientes que ven un botón de pago funcional confían más en tu producto que los que ven un “próximamente”.

Si necesitas un SaaS con auth robusta, pagos integrados y arquitectura multitenant, cotiza tu proyecto en iAmanos.

Preguntas Frecuentes

¿NextAuth v5 es suficiente para auth de producción?

Sí. NextAuth v5 maneja autenticación, sesiones JWT, refresh tokens, y RBAC. Es la solución más usada para Next.js y está probada en miles de apps en producción. Para casos enterprise, considera agregar 2FA con TOTP.

¿Es obligatorio aceptar pagos en OXXO para SaaS en México?

No obligatorio, pero altamente recomendado. Millones de mexicanos no tienen tarjeta bancaria y pagan todo en OXXO. Si tu SaaS apunta a PYMEs o consumidores finales en México, no aceptar OXXO te cierra una parte significativa del mercado.

¿Cuánto cuesta integrar facturación electrónica (CFDI)?

Facturama cobra desde $1,500 MXN/mes por su API. La integración técnica toma 1-2 días con Claude Code. El reto principal no es técnico sino operativo: necesitas los datos fiscales del cliente antes de generar el CFDI.

¿Base de datos por tenant no consume mucha RAM?

Cada contenedor PostgreSQL consume ~50-100 MB de RAM. Con 20 tenants son ~1-2 GB. En un VPS de 8 GB es manejable. Si necesitas 100+ tenants, mejor usar tenant_id compartido para reducir overhead.

¿Stripe funciona legalmente en México?

Sí, Stripe tiene operación legal en México desde 2019. Puedes cobrar en pesos mexicanos, recibir depósitos en cuenta bancaria mexicana, y cumplir con regulaciones locales. La configuración toma ~30 minutos incluyendo verificación de identidad.