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.
// 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.
// 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.
// 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.
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: BajoPara 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
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.
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)
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 minutosAquí 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.shque 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
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
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úlCompliance 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
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 proyectoPatró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.
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.