Estás armando un SaaS B2B en Firebase y llegaste al punto donde el segundo cliente quiere onboarding. La pregunta que define los siguientes 18 meses de tu arquitectura: ¿meto a los dos clientes en el mismo proyecto Firebase, o le doy un proyecto separado a cada uno? No hay respuesta universal. Hay tres patrones, cada uno con tradeoffs específicos. Este artículo los compara en seis rounds y explica por qué KEYON Access eligió el menos popular (1 proyecto Firebase por escuela) y dónde duele esa decisión.

Los tres patrones

Patrón A: Single project + tenant flag

Un solo proyecto Firebase, todos los clientes en el mismo Firestore. Cada documento tiene un campo tenantId y las security rules filtran por ese campo.

javascript
// Estructura
/clientes/{tenantId}: { nombre, plan, ... }
/usuarios/{userId}: { tenantId, rol, ... }
/eventos/{eventId}: { tenantId, data, ... }

// Security rule
match /eventos/{eventId} {
  allow read: if request.auth != null
    && request.auth.token.tenantId == resource.data.tenantId;
}

Patrón B: Single project + collections separadas

Un proyecto, pero cada tenant tiene sus propias subcollections.

javascript
// Estructura
/tenants/{tenantId}/usuarios/{userId}: { rol, ... }
/tenants/{tenantId}/eventos/{eventId}: { data, ... }

// Security rule
match /tenants/{tenantId}/{document=**} {
  allow read: if request.auth.token.tenantId == tenantId;
}

Patrón C: N proyectos Firebase (uno por tenant)

Cada cliente tiene su propio proyecto GCP completo: Firestore, Auth, Functions, Hosting separados. Total aislamiento.

javascript
// Tenant 1: cbtis-001 (proyecto: keyon-cbtis-001)
//   /alumnos/...
//   /eventos/...
//
// Tenant 2: cbtis-002 (proyecto: keyon-cbtis-002)
//   /alumnos/...
//   /eventos/...
//
// Cada uno con su propio firebase.json, sus propias rules, su propio
// dominio (cbtis-001.web.app, cbtis-002.web.app).

Round 1: Aislamiento de datos

Qué tan seguro es que un cliente no vea datos de otro.

markdown
PATRÓN A (single + tenant flag)
  • Aislamiento depende 100% de tus security rules
  • Un bug en rules = leak de datos cross-tenant
  • Firestore queries que olviden filtrar = leak
  • Auditoría: difícil (todos los datos en un solo lugar)
  • Riesgo: Alto

PATRÓN B (single + collections separadas)
  • Aislamiento estructural en el path del documento
  • Aún depende de rules pero con menos puntos de falla
  • Imposible "olvidar" tenantId si está en el path
  • Auditoría: media (logs por subcollection)
  • Riesgo: Medio

PATRÓN C (N proyectos)
  • Aislamiento físico — proyectos GCP separados
  • Imposible cross-tenant leak por arquitectura
  • Cada proyecto su Auth, Storage, Functions
  • Auditoría: trivial (proyecto = tenant)
  • Riesgo: Bajo

Para verticales sensibles (salud, educación con menores, banca), Patrón C es el único que un compliance officer aprueba sin discusión. Patrón A es el que más rompe en producción cuando un desarrollador junior se olvida de filtrar.

Round 2: Costo

markdown
COSTO POR TENANT (1,000 usuarios activos / tenant)

PATRÓN A — single project
  Firestore reads/writes:    Pago por uso, escala lineal
  Cloud Functions:            Free tier 2M invocations/mes (compartido)
  Hosting:                    Free tier 10 GB transfer (compartido)
  Auth:                       Free tier 50k MAU (compartido)
  Costo marginal por tenant:  ~$15-25 USD/mes a partir del primer free tier

PATRÓN B — single project (subcollections)
  Idéntico al patrón A en costo
  Storage por tenant:         Idéntico

PATRÓN C — N proyectos
  Cada proyecto Firebase:     Su propio free tier
  Hasta el "umbral" del free tier de cada tenant: $0
  Costo marginal por tenant:  $0 - $5 USD/mes para tenants chicos

EJEMPLO 50 TENANTS DE 200 ALUMNOS C/U:
  Patrón A: ~$200-400 USD/mes total (un proyecto que paga todo)
  Patrón C: ~$50-150 USD/mes total (50 proyectos, cada uno chico)

Counter-intuitivo pero real: N proyectos puede salir más barato que single project si cada tenant es chico, porque cada uno aprovecha su propio free tier. KEYON Access opera 1 proyecto por escuela y la mayoría de escuelas pagan $0-$5 USD/mes.

El catch del free tier multiplicado

Google tiene un quota de 30 proyectos por billing account por default. Pasarlo requiere solicitud formal (la pedimos para KEYON al llegar a 9 proyectos activos + varios en pending deletion). Aprobaron a 50 proyectos en 2-3 días. Es un proceso, pero existe.

Round 3: Operacional (deploys, monitoring)

markdown
PATRÓN A — single project
  Deploy:                     Un solo "firebase deploy"
  Monitoring:                 Un solo dashboard, métricas agregadas
  Cloud Functions logs:       Un solo lugar
  Rollback:                   Atómico, instant
  Hot patch:                  Aplica a todos los tenants a la vez
  Tiempo total deploy:        2-4 minutos

PATRÓN B — single project (subcollections)
  Idéntico al A

PATRÓN C — N proyectos
  Deploy:                     Script bash que itera "firebase use {id} && deploy"
  Monitoring:                 N dashboards, agregar requiere BigQuery export
  Cloud Functions logs:       Distribuidos, requiere agregación manual
  Rollback:                   Por tenant
  Hot patch:                  Itera sobre todos los proyectos
  Tiempo total deploy 50 ten: 30-50 minutos

Aquí Patrón A gana sin discusión. Operación con N proyectos es más trabajo continuo. La compensación: cada deploy es por tenant — puedes rolloutar un cambio gradualmente y abortar si rompe sin afectar a los demás.

Patrón C te obliga a construir tu propia herramienta de deploy orquestado. KEYON tiene un script Bash scripts/deploy-escuela.sh que recibe el ID de la escuela y deploya solo ese proyecto. Si quieres deployar a todos: un loop de 30 minutos.

Round 4: Onboarding de tenant nuevo

markdown
PATRÓN A — single project
  Pasos:
    1. Crear documento /tenants/{nuevoId}
    2. Asociar usuarios al tenantId vía custom claims
  Tiempo total:                ~5 minutos
  Riesgo de error humano:      Bajo (puro Firestore)

PATRÓN B — single project (subcollections)
  Idéntico al A
  Bonus: setup inicial estructural automático
  Tiempo total:                ~5 minutos

PATRÓN C — N proyectos
  Pasos:
    1. Crear nuevo proyecto Firebase
    2. Habilitar Auth, Firestore, Functions, Hosting
    3. Aplicar configuración Firebase del template
    4. Deploy inicial: rules, indexes, functions, hosting
    5. Configurar custom domain (opcional)
    6. Crear primer admin
  Tiempo total:                15-25 minutos (con script automatizado)
  Riesgo de error humano:      Medio (varios pasos manuales)

Patrón A/B onboardea en minutos. Patrón C requiere infraestructura de provisión más sofisticada. KEYON resolvió esto con un script (scripts/nueva-escuela.sh) que automatiza el setup completo de un proyecto nuevo en ~10 minutos. Pero alguien tiene que mantener ese script — no es gratis.

Round 5: Compliance, data residency, auditoría

markdown
COMPLIANCE / DATA RESIDENCY

PATRÓN A — single project
  Region única:                Todos los datos en una región (ej. us-central1)
  Data residency por tenant:   ❌ Imposible
  LFPDPPP por tenant:          ❌ Difícil — un solo aviso de privacidad
  Auditoría tenant-isolated:   ❌ Requiere export filtrado
  Eliminación tenant:          Difícil — borrar docs distribuidos
  GDPR right to be forgotten:  Riesgo de leak en backups

PATRÓN B
  Igual a A pero con paths más limpios

PATRÓN C — N proyectos
  Region única:                Por proyecto (ej. tenant 1 en us-central, tenant 2 en us-west)
  Data residency por tenant:   ✅ Sí (LATAM tiene gcp-mexico1 desde 2023)
  LFPDPPP por tenant:          ✅ Aviso por escuela, encargado por proyecto
  Auditoría tenant-isolated:   ✅ Trivial — billing y logs por proyecto
  Eliminación tenant:          Trivial — eliminar el proyecto
  GDPR right to be forgotten:  Limpio — proyecto entero al baúl

Compliance es donde Patrón C empieza a justificar su complejidad operativa. Para KEYON Access, donde cada escuela es un "encargado de tratamiento" diferente bajo LFPDPPP, tener un proyecto por escuela hace que el aviso de privacidad sea claro: los datos del plantel X solo están en el proyecto X. Un INAI que audita pide ver una sola consola.

Round 6: Migración futura

markdown
SI EN EL FUTURO QUIERES HACER ALGO CON UN TENANT:

Quiero exportar a BigQuery solo este tenant
  Patrón A: ❌ Requiere export filtrado, no nativo
  Patrón B: ⚠️  Posible vía export de subcollection
  Patrón C: ✅ Export nativo del proyecto entero

Quiero darle a este cliente acceso a su Firestore directo
  Patrón A: ❌ Imposible (les daría a TODOS los datos)
  Patrón B: ❌ Mismo problema
  Patrón C: ✅ Compartir el proyecto Firebase (IAM)

Quiero migrar este tenant a su propio backend (sale del SaaS)
  Patrón A: ⚠️  Export selectivo + limpieza
  Patrón B: ✅ Export de la subcollection
  Patrón C: ✅ Transferir el proyecto al cliente (1 click)

Quiero hacer A/B testing de schema entre tenants
  Patrón A: ❌ Imposible cleanly
  Patrón B: ❌ Mismo
  Patrón C: ✅ Deploy distinto schema por proyecto

Patrón C ofrece una opción muy real para SaaS de venta consultiva (escuelas, hospitales, despachos legales): "si decides que ya no quieres KEYON Access en el futuro, te transferimos el proyecto Firebase a tu nombre y el sistema sigue funcionando con tu equipo de TI". Esto es un argumento de venta poderoso vs los competidores SaaS lock-in.

Cuándo elegir cada uno

Elige Patrón A si...

Tu producto es B2C o B2B-self-serve con muchos tenants chicos (Notion-style: cualquiera crea un workspace en 1 minuto). Operas a escala donde 10,000+ tenants harían inviable un proyecto por cada uno. Compliance no es vertical-crítico (no manejas datos biométricos de menores ni datos médicos). Tu equipo es chico y no quieres orquestación de N proyectos.

Elige Patrón B si...

Lo mismo que A, pero quieres mejor estructura para auditorías y futuras migraciones. Es un mejor default que A en general — el path con tenantId reduce errores de developers.

Elige Patrón C si...

Tu venta es consultiva (cada cliente firma contrato, hay onboarding asistido). Tienes pocos tenants pero cada uno valioso ($25k+ MXN/año). El vertical es regulado (educación, salud, finanzas). Ofreces el "proyecto te lo transferimos si te vas" como argumento de venta. Necesitas data residency por tenant (GDPR/LFPDPPP estricto). Tienes presupuesto para construir tooling de provisión y deploy orquestado.

Por qué KEYON eligió Patrón C

La decisión arquitectónica de KEYON fue Patrón C — un proyecto Firebase por escuela. Las razones específicas:

1. Cada escuela es un encargado de tratamiento distinto

Bajo LFPDPPP, cada plantel es responsable de los datos biométricos de SUS alumnos. Un único proyecto Firebase con todas las escuelas mezclaría aviso de privacidad y bitácoras de auditoría INAI. Imposible defender legalmente.

2. La venta es consultiva, no self-serve

KEYON no se vende solo. Cada plantel firma contrato, hay visita técnica, capacitación, integración con su sistema interno (DGETI, SEP). El onboarding de 15-25 minutos del proyecto Firebase es ruido — los otros pasos institucionales toman semanas.

3. La transferibilidad como diferencial competitivo

Hikvision/ZKTeco son lock-in: si dejas de pagarles, te quedas sin sistema. KEYON ofrece "si te vas, te transferimos el proyecto a tu cuenta GCP y el código está en GitHub bajo licencia abierta". Esto cierra ventas con planteles que quemaron antes con proveedores que cerraron operaciones o subieron precios.

4. Free tier por tenant aprovecha la economía a nuestra escala

Cada escuela de 700 alumnos consume aproximadamente 60-70% de un free tier de Firebase. Operacionalmente: la mayoría de tenants nos cuestan menos de $5 USD/mes en infraestructura. Un single project con 50 escuelas mezcladas cobraría más en absoluto que la suma de los free tiers individuales.

Dónde duele Patrón C en producción

Para no idealizar, los tres dolores reales de operar 10+ proyectos Firebase:

1. Deploy de hot-fix urgente

Si descubres un bug crítico en una Cloud Function, deployar a 50 proyectos serial toma 30-45 minutos. Implementación: deploy en paralelo con limit de concurrencia (no más de 5 a la vez para no golpear quotas). Aún así, algunos deploys toman 4-5 minutos mientras el del último apenas empezó.

2. Monitoring agregado

Quieres ver "cuántos eventos hubo hoy en TODA la plataforma". Patrón A: una query. Patrón C: agregar via BigQuery exports o llamar a 50 endpoints de stats. KEYON construyó un dashboard centralizado que cada noche pulsa cada proyecto y agrega.

3. Schema migrations

Si cambias el schema de Firestore (ej. renombrar un campo en todos los documentos), tienes que correr la migración en cada proyecto. Si una migración falla a la mitad, queda en estado mixto. Hace falta tooling con resume y rollback.

Mitigación clave

Mantener una sola base de código que se deploya a N proyectos.Nunca permitir customización por proyecto — si la escuela X pide un feature custom, debe ser flag en el config, no rama de código separada. Esto es la regla más importante cuando operas N proyectos: el código es uno, los datos son N.

Cierre

No hay un patrón ganador universal. Patrón A para B2C self-serve a escala, Patrón B como default sano, Patrón C para SaaS B2B consultivo en verticales regulados. KEYON eligió C y la decisión ha valido la pena —compliance limpio, costos bajos a nuestra escala, transferibilidad como diferenciador. El precio es tooling de operación que tuvimos que construir nosotros.

Si estás arrancando un SaaS B2B y dudas qué patrón te conviene, escríbenos en exara.uk/contacto con la descripción del producto, número esperado de tenants año 1, y vertical (¿hay regulación específica?). Respondemos en menos de 24 horas con la recomendación honesta y los scripts de provisión que usamos en KEYON si decides Patrón C.