Estás construyendo una aplicación. Los usuarios necesitan iniciar sesión. Los datos deben sincronizarse en tiempo real. Los archivos necesitan almacenamiento. Podrías levantar servidores, configurar bases de datos y gestionar la infraestructura durante semanas. O podrías usar Firebase.
Firebase potencia más de 1.5 millones de aplicaciones, incluyendo The New York Times, Duolingo y Alibaba. Los desarrolladores lo eligen porque elimina la complejidad del backend. Te enfocas en las características, no en el mantenimiento del servidor. Pero la API de Firebase tiene sus peculiaridades. Los flujos de autenticación confunden a los principiantes. Las reglas de la base de datos complican a los desarrolladores experimentados. Las Cloud Functions parecen mágicas hasta que entiendes los activadores.
He integrado Firebase en aplicaciones de producción que atienden a millones de usuarios. He cometido todos los errores posibles: expuse claves de cuentas de servicio, escribí consultas ineficientes, implementé funciones defectuosas. Esta guía destila esas lecciones.
Aprenderás autenticación, operaciones de base de datos, Cloud Functions y almacenamiento. Verás código funcional, no solo teoría. Evitarás los problemas que causan incendios en producción.
¿Qué es la API de Firebase y por qué es importante?
Firebase no es una única API. Es un conjunto de servicios de backend a los que se accede a través de SDKs unificados y puntos finales REST.
Servicios Principales de Firebase
| Servicio | Propósito | Tipo de API |
|---|---|---|
| Authentication | Inicio de sesión e identidad de usuario | SDK + REST |
| Firestore Database | Base de datos de documentos NoSQL | SDK + REST |
| Realtime Database | Sincronización en tiempo real de JSON | SDK + REST |
| Cloud Storage | Almacenamiento de archivos y CDN | SDK + REST |
| Cloud Functions | Cómputo sin servidor | CLI de despliegue |
| Hosting | Alojamiento web estático | CLI de despliegue |
| Cloud Messaging | Notificaciones push | HTTP v1 API |
Cuándo Firebase Tiene Sentido
Firebase resuelve bien problemas específicos:
Usa Firebase cuando:
- Necesitas sincronización en tiempo real (chat, colaboración, actualizaciones en vivo)
- Quieres una arquitectura sin servidor (sin gestión de infraestructura)
- Estás construyendo aplicaciones móviles o web (los SDKs manejan las diferencias de plataforma)
- Necesitas soporte sin conexión (los SDKs almacenan datos en caché automáticamente)
- Quieres autenticación integrada (inicio de sesión con Google, Apple, correo electrónico, teléfono)
Omite Firebase cuando:
- Necesitas consultas relacionales complejas (usa PostgreSQL en su lugar)
- Tienes requisitos estrictos de residencia de datos (las regiones de Firebase son limitadas)
- Necesitas capacidades SQL completas (existen limitaciones de consulta en Firestore)
- El costo a escala importa más que la velocidad de desarrollo (autoalojamiento es más barato)
La Arquitectura de la API de Firebase
Firebase utiliza un enfoque híbrido:
┌─────────────────────────────────────────────────────────┐
│ Tu Aplicación │
├─────────────────────────────────────────────────────────┤
│ Firebase SDK (Cliente) │
│ - Maneja automáticamente los tokens de autenticación │
│ - Gestiona la caché sin conexión │
│ - Escuchas en tiempo real │
└─────────────────────────────────────────────────────────┘
│
│ HTTPS + WebSocket
▼
┌─────────────────────────────────────────────────────────┐
│ Backend de Firebase │
├──────────────┬──────────────┬──────────────┬────────────┤
│ Servicio │ Base de │ Servicio │ Tiempo de │
│ de Autent. │ Datos │ de Almac. │ Ejecución │
│ │ Firestore │ │ de Func. │
└──────────────┴──────────────┴──────────────┴────────────┘
Los SDK de cliente abstraen la capa HTTP. Bajo el capó, cada operación se traduce en llamadas a la API REST con autenticación JWT.
Autenticación de Firebase: Configuración Completa
La autenticación es tu primera integración de Firebase. Si lo haces mal, todo lo demás falla.
Paso 1: Crear Proyecto de Firebase
- Ve a la Consola de Firebase

Haz clic en "Agregar proyecto" e ingresa el nombre del proyecto (sin espacios)

Habilita Google Analytics (opcional pero recomendado)

Haz clic en "Crear proyecto"

Espera 30 segundos para el aprovisionamiento. Verás el panel del proyecto.
Paso 2: Registrar Tu Aplicación
Para Aplicaciones Web:
// En la Consola de Firebase > Configuración del Proyecto > General
// Haz clic en "Agregar aplicación" > Icono web
// Registrar aplicación web
const firebaseConfig = {
apiKey: "AIzaSyDxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "tu-app.firebaseapp.com",
projectId: "tu-app",
storageBucket: "tu-app.appspot.com",
messagingSenderId: "123456789012",
appId: "1:123456789012:web:abc123def456"
};
// Inicializar Firebase
import { initializeApp } from 'firebase/app';
const app = initializeApp(firebaseConfig);
Para Aplicaciones iOS:
Descarga GoogleService-Info.plist y agrégalo al proyecto Xcode. Asegúrate de que "Target Membership" incluya tu aplicación.
Para Aplicaciones Android:
Descarga google-services.json y colócalo en el directorio app/. Agrégalo a build.gradle:
// build.gradle a nivel de proyecto
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.0'
}
}
// build.gradle a nivel de aplicación
plugins {
id 'com.google.gms.google-services'
}
Paso 3: Habilitar Métodos de Autenticación
En la Consola de Firebase > Autenticación > Método de inicio de sesión:
- Correo electrónico/Contraseña: Habilitar para registro tradicional
- Google: Agrega la huella digital de tu certificado SHA-1 (Android) o el ID de paquete (iOS)
- Apple: Requerido para aplicaciones iOS si habilitas cualquier inicio de sesión social
- Teléfono: Habilitar para autenticación por SMS (requiere facturación)
Paso 4: Implementar Flujo de Autenticación
Registro con Correo Electrónico/Contraseña:
import {
createUserWithEmailAndPassword,
getAuth,
updateProfile
} from 'firebase/auth';
const auth = getAuth(app);
async function signUp(email, password, displayName) {
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// Establecer nombre de visualización
await updateProfile(userCredential.user, {
displayName: displayName
});
console.log('Usuario creado:', userCredential.user.uid);
return userCredential.user;
} catch (error) {
// Manejar códigos de error específicos
switch (error.code) {
case 'auth/email-already-in-use':
throw new Error('Este correo electrónico ya está registrado');
case 'auth/weak-password':
throw new Error('La contraseña debe tener al menos 6 caracteres');
case 'auth/invalid-email':
throw new Error('Dirección de correo electrónico inválida');
default:
throw new Error('Fallo al registrarse: ' + error.message);
}
}
}
Inicio de Sesión con Correo Electrónico/Contraseña:
import {
signInWithEmailAndPassword,
signOut
} from 'firebase/auth';
async function signIn(email, password) {
try {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
// Obtener token de ID para llamadas a la API
const idToken = await user.getIdToken();
console.log('Token de autenticación:', idToken);
return user;
} catch (error) {
switch (error.code) {
case 'auth/user-not-found':
throw new Error('No existe cuenta con este correo electrónico');
case 'auth/wrong-password':
throw new Error('Contraseña incorrecta');
case 'auth/too-many-requests':
throw new Error('Demasiados intentos. Inténtalo de nuevo más tarde');
default:
throw new Error('Fallo al iniciar sesión');
}
}
}
async function logOut() {
await signOut(auth);
console.log('Usuario cerró sesión');
}
Inicio de Sesión con Google (Web):
import {
GoogleAuthProvider,
signInWithPopup
} from 'firebase/auth';
async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
// Solicitar ámbitos adicionales
provider.addScope('email');
provider.addScope('profile');
try {
const result = await signInWithPopup(auth, provider);
const user = result.user;
// Acceder al token de OAuth de Google
const credential = GoogleAuthProvider.credentialFromResult(result);
const googleAccessToken = credential.accessToken;
return user;
} catch (error) {
if (error.code === 'auth/popup-closed-by-user') {
throw new Error('Inicio de sesión cancelado');
}
throw new Error('Fallo en el inicio de sesión con Google');
}
}
Paso 5: Proteger Rutas con el Estado de Autenticación
import { onAuthStateChanged } from 'firebase/auth';
// Suscribirse a cambios en el estado de autenticación
onAuthStateChanged(auth, (user) => {
if (user) {
// El usuario ha iniciado sesión
console.log('Usuario:', user.email);
// Redirigir al panel de control
window.location.href = '/dashboard';
} else {
// El usuario ha cerrado sesión
console.log('Ningún usuario');
// Redirigir al inicio de sesión
window.location.href = '/login';
}
});
Errores Comunes de Autenticación
Error 1: No manejar la actualización del token
El SDK de Firebase actualiza automáticamente los tokens. Pero si almacenas los tokens en caché en el servidor, expiran después de 1 hora. Siempre verifica los tokens en cada solicitud o implementa una lógica de actualización.
Error 2: Exponer credenciales de administrador en el código del cliente
Nunca uses claves de cuenta de servicio en aplicaciones cliente. Las cuentas de servicio omiten las reglas de seguridad. Úsalas solo en entornos de servidor confiables.
Error 3: Omitir la verificación de correo electrónico
import { sendEmailVerification } from 'firebase/auth';
async function sendVerificationEmail(user) {
await sendEmailVerification(user);
console.log('Correo electrónico de verificación enviado');
}
// Verificar estado de verificación
if (!auth.currentUser.emailVerified) {
console.log('Correo electrónico no verificado');
// Restringir acceso
}
Base de Datos Firestore: Operaciones y Consultas
Firestore es la base de datos NoSQL de Firebase. Los documentos se organizan en colecciones. Las consultas se escalan automáticamente.
Estructura de Datos
tu-proyecto (raíz)
└── users (colección)
├── userId123 (documento)
│ ├── name: "John"
│ ├── email: "john@example.com"
│ └── posts (subcolección)
│ ├── postId1 (documento)
│ └── postId2 (documento)
└── userId456 (documento)
Inicializar Firestore
import { getFirestore } from 'firebase/firestore';
const db = getFirestore(app);
Crear Documentos
import {
collection,
addDoc,
setDoc,
doc
} from 'firebase/firestore';
// Opción 1: ID autogenerado
async function createUser(userData) {
const docRef = await addDoc(collection(db, 'users'), userData);
console.log('Documento escrito con ID:', docRef.id);
return docRef.id;
}
// Opción 2: ID personalizado
async function createUserWithId(userId, userData) {
await setDoc(doc(db, 'users', userId), userData);
console.log('Documento escrito con ID personalizado:', userId);
}
// Uso
const userId = await createUser({
name: 'Alice',
email: 'alice@example.com',
createdAt: new Date(),
role: 'user'
});
Leer Documentos
import {
getDoc,
getDocs,
query,
where,
orderBy,
limit
} from 'firebase/firestore';
// Obtener un solo documento
async function getUser(userId) {
const docRef = doc(db, 'users', userId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
return docSnap.data();
} else {
throw new Error('Usuario no encontrado');
}
}
// Consultar con filtros
async function getUsersByRole(role) {
const q = query(
collection(db, 'users'),
where('role', '==', role),
orderBy('createdAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
const users = [];
querySnapshot.forEach((doc) => {
users.push({ id: doc.id, ...doc.data() });
});
return users;
}
// Uso
const adminUsers = await getUsersByRole('admin');
console.log('Usuarios administradores:', adminUsers);
Actualizar Documentos
import {
updateDoc,
increment,
arrayUnion,
arrayRemove
} from 'firebase/firestore';
async function updateUser(userId, updates) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, updates);
}
// Operaciones atómicas
await updateUser('userId123', {
loginCount: increment(1),
tags: arrayUnion('premium', 'beta-tester'),
lastLogin: new Date()
});
// Eliminar de una matriz
await updateUser('userId123', {
tags: arrayRemove('beta-tester')
});
Eliminar Documentos
import { deleteDoc } from 'firebase/firestore';
async function deleteUser(userId) {
await deleteDoc(doc(db, 'users', userId));
console.log('Usuario eliminado');
}
Escuchas en Tiempo Real
import { onSnapshot } from 'firebase/firestore';
// Escuchar un solo documento
const unsubscribe = onSnapshot(
doc(db, 'users', userId),
(doc) => {
console.log('Usuario actualizado:', doc.data());
},
(error) => {
console.error('Error de escucha:', error);
}
);
// Escuchar resultados de consulta
const q = query(collection(db, 'posts'), where('published', '==', true));
const unsubscribeQuery = onSnapshot(q, (snapshot) => {
const posts = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
console.log('Publicaciones publicadas:', posts);
});
// Dejar de escuchar
unsubscribe();
unsubscribeQuery();
Reglas de Seguridad de Firestore
Sin reglas adecuadas, cualquiera puede leer tus datos. Establece reglas en la Consola de Firebase > Firestore > Reglas:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Función auxiliar
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
// Colección de usuarios
match /users/{userId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isOwner(userId);
allow update, delete: if isOwner(userId);
}
// Colección de publicaciones
match /posts/{postId} {
allow read: if true; // Lectura pública
allow create: if isAuthenticated();
allow update, delete: if resource.data.authorId == request.auth.uid;
}
// Subcolección privada
match /users/{userId}/private/{document} {
allow read, write: if isOwner(userId);
}
}
}
Limitaciones de Consulta
Firestore tiene restricciones:
- Sin consultas OR (usa
incon array o múltiples consultas) - Sin búsquedas con comodines (usa Algolia o Meilisearch para texto completo)
- Las consultas compuestas necesitan índices (Firestore autocrea campos individuales)
- Límite de 30 disyunciones en consultas
in
Solución para consultas OR:
// En lugar de: where('status', '==', 'active') OR where('status', '==', 'pending')
const activeQuery = query(
collection(db, 'tasks'),
where('status', '==', 'active')
);
const pendingQuery = query(
collection(db, 'tasks'),
where('status', '==', 'pending')
);
const [activeSnap, pendingSnap] = await Promise.all([
getDocs(activeQuery),
getDocs(pendingQuery)
]);
// Fusionar resultados en el cliente
Cloud Functions: Lógica de Backend sin Servidor
Cloud Functions ejecutan código backend sin gestionar servidores. Se activan con cambios en la base de datos, solicitudes HTTP o eventos programados.
Configuración
# Instalar Firebase CLI
npm install -g firebase-tools
# Iniciar sesión
firebase login
# Inicializar funciones en tu proyecto
firebase init functions
# Seleccionar: JavaScript, ESLint sí, Express.js no
Funciones HTTP (Puntos Finales de API)
// functions/index.js
const { onRequest } = require('firebase-functions/v2/https');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
// Punto final público
exports.getPublicData = onRequest(async (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
try {
const snapshot = await db.collection('public').get();
const data = snapshot.docs.map(doc => doc.data());
res.json({ success: true, data });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Punto final protegido (verificar token de autenticación)
exports.getUserProfile = onRequest(async (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
// Obtener token del encabezado Authorization
const authHeader = req.headers.authorization || '';
const token = authHeader.split('Bearer ')[1];
if (!token) {
return res.status(401).json({ error: 'No autorizado' });
}
try {
// Verificar token
const decodedToken = await admin.auth().verifyIdToken(token);
const userId = decodedToken.uid;
// Obtener datos del usuario
const userDoc = await db.collection('users').doc(userId).get();
if (!userDoc.exists) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}
res.json({
success: true,
data: { id: userId, ...userDoc.data() }
});
} catch (error) {
res.status(401).json({ error: 'Token inválido' });
}
});
Desplegar:
firebase deploy --only functions:getUserProfile
Llamar desde el cliente:
async function getUserProfile(token) {
const response = await fetch(
'https://us-central1-tu-app.cloudfunctions.net/getUserProfile',
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const data = await response.json();
return data;
}
Disparadores de Base de Datos
const { onDocumentWritten } = require('firebase-functions/v2/firestore');
// Disparar cuando el documento de usuario cambia
exports.onUserUpdate = onDocumentWritten(
'users/{userId}',
async (event) => {
const userId = event.params.userId;
const before = event.data?.before?.data();
const after = event.data?.after?.data();
// Verificar si el correo electrónico cambió
if (before?.email !== after?.email) {
console.log(`El correo electrónico del usuario ${userId} cambió: ${before?.email} → ${after?.email}`);
// Enviar correo electrónico de notificación
await admin.auth().getUser(userId);
// Agrega tu lógica de correo electrónico aquí
}
}
);
// Disparar en la creación de una nueva publicación
exports.onNewPost = onDocumentWritten(
'posts/{postId}',
async (event) => {
const post = event.data?.after?.data();
if (!post) return; // Documento eliminado
// Verificar si es un nuevo documento
if (!event.data?.before?.exists) {
console.log('Nueva publicación creada:', post.title);
// Notificar a los seguidores
const followersSnap = await admin.firestore()
.collection('users')
.where('following', 'array-contains', post.authorId)
.get();
const notifications = followersSnap.docs.map(doc => ({
userId: doc.id,
postId: event.params.postId,
type: 'new_post',
createdAt: admin.firestore.FieldValue.serverTimestamp()
}));
const batch = admin.firestore().batch();
notifications.forEach(notif => {
const ref = admin.firestore().collection('notifications').doc();
batch.set(ref, notif);
});
await batch.commit();
}
}
);
Funciones Programadas (Trabajos Cron)
const { onSchedule } = require('firebase-functions/v2/scheduler');
// Ejecutar cada día a medianoche UTC
exports.dailyCleanup = onSchedule('every 24 hours', async (event) => {
console.log('Ejecutando limpieza diaria');
// Eliminar notificaciones antiguas (más de 30 días)
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const oldNotifs = await admin.firestore()
.collection('notifications')
.where('createdAt', '<', thirtyDaysAgo)
.get();
const batch = admin.firestore().batch();
oldNotifs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
console.log(`Eliminadas ${oldNotifs.size} notificaciones antiguas`);
});
Configuración de Entorno
# Establecer variables de entorno
firebase functions:config:set \
stripe.secret="sk_test_xxx" \
email.api_key="key_xxx"
# Acceder en funciones
const config = require('firebase-functions/config');
const stripe = require('stripe')(config.stripe.secret);
Cloud Storage: Carga y Gestión de Archivos
Almacena cargas de usuarios, imágenes y archivos con distribución automática de CDN.
Configurar Reglas de Almacenamiento
// Consola de Firebase > Storage > Reglas
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Carpeta de cargas de usuario
match /users/{userId}/{allPaths=**} {
allow read: if true; // Lectura pública
allow write: if request.auth.uid == userId;
allow delete: if request.auth.uid == userId;
}
// Activos públicos
match /public/{allPaths=**} {
allow read: if true;
allow write: if false; // Solo administrador vía Consola de Firebase
}
}
}
Cargar Archivos (Cliente)
import {
getStorage,
ref,
uploadBytesResumable,
getDownloadURL
} from 'firebase/storage';
const storage = getStorage(app);
async function uploadProfileImage(userId, file) {
// Crear referencia de almacenamiento
const storageRef = ref(storage, `users/${userId}/profile/${file.name}`);
// Cargar archivo
const uploadTask = uploadBytesResumable(storageRef, file);
return new Promise((resolve, reject) => {
uploadTask.on(
'state_changed',
(snapshot) => {
// Rastrear progreso
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log(`Carga: ${progress.toFixed(0)}%`);
},
(error) => {
// Manejar errores
switch (error.code) {
case 'storage/unauthorized':
reject(new Error('No tienes permiso'));
break;
case 'storage/canceled':
reject(new Error('Carga cancelada'));
break;
default:
reject(new Error('Fallo la carga'));
}
},
async () => {
// Carga completada
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
console.log('Archivo disponible en:', downloadURL);
resolve(downloadURL);
}
);
});
}
// Uso
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
if (file) {
const imageUrl = await uploadProfileImage(auth.currentUser.uid, file);
// Guardar URL en Firestore
await updateDoc(doc(db, 'users', auth.currentUser.uid), {
profileImage: imageUrl
});
}
Descargar Archivos
import { getDownloadURL } from 'firebase/storage';
async function getProfileImage(userId) {
const imageRef = ref(storage, `users/${userId}/profile/avatar.png`);
try {
const url = await getDownloadURL(imageRef);
return url;
} catch (error) {
if (error.code === 'storage/object-not-found') {
return null; // No hay imagen de perfil
}
throw error;
}
}
Eliminar Archivos
import { deleteObject } from 'firebase/storage';
async function deleteProfileImage(userId) {
const imageRef = ref(storage, `users/${userId}/profile/avatar.png`);
await deleteObject(imageRef);
console.log('Imagen de perfil eliminada');
}
Probando APIs de Firebase con Apidog
Firebase proporciona APIs REST para todos los servicios. Probarlos directamente ayuda a depurar problemas y entender las solicitudes subyacentes.
Importar API REST de Firebase
- Abrir Apidog
- Crear nuevo proyecto: "Firebase API"
- Importar especificación OpenAPI de la documentación de Firebase
- O añadir puntos finales manualmente:
Punto Final REST de Firestore:
POST https://firestore.googleapis.com/v1/projects/{projectId}/databases/(default)/documents
Authorization: Bearer {oauth2_token}
Content-Type: application/json
{
"fields": {
"name": { "stringValue": "John" },
"email": { "stringValue": "john@example.com" },
"age": { "integerValue": 30 }
}
}
Punto Final de Autenticación:
POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={api_key}
Content-Type: application/json
{
"email": "user@example.com",
"password": "secret123",
"returnSecureToken": true
}
Probar Flujo de Autenticación
- Crear solicitud: "Iniciar Sesión"
- Establecer método: POST
- Agregar correo electrónico/contraseña en el cuerpo
- Guardar token de respuesta como variable de entorno
- Usar
{{token}}en solicitudes posteriores
Depurar Reglas de Seguridad
Usa Firebase Emulator Suite para pruebas locales:
# Iniciar emulador
firebase emulators:start
# Probar contra Firestore local
# http://localhost:8080
Mejores Prácticas de Producción
1. Implementar un Manejo Adecuado de Errores
// Lógica de reintento para fallos transitorios
async function firestoreWithRetry(operation, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (
error.code === 'unavailable' ||
error.code === 'deadline-exceeded'
) {
const delay = Math.pow(2, i) * 1000; // Retroceso exponencial
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
2. Optimizar el Rendimiento de las Consultas
Agrega índices compuestos para consultas de múltiples campos:
// Esta consulta necesita un índice compuesto
const q = query(
collection(db, 'posts'),
where('category', '==', 'tech'),
where('views', '>', 1000),
orderBy('views', 'desc')
);
Firestore te indica que crees el índice con un enlace directo cuando ejecutas esta consulta.
3. Operaciones por Lotes
import { writeBatch } from 'firebase/firestore';
async function bulkUpdate(userIds, updates) {
const batch = writeBatch(db);
userIds.forEach(id => {
const ref = doc(db, 'users', id);
batch.update(ref, updates);
});
await batch.commit();
console.log(`Actualizados ${userIds.length} usuarios`);
}
// Máximo 500 operaciones por lote
4. Monitorear Costos
Precios de Firebase:
| Servicio | Nivel Gratuito | De Pago |
|---|---|---|
| Firestore | 50K lecturas/día | $0.036/100K lecturas |
| Storage | 5GB | $0.023/GB |
| Functions | 2M invocaciones | $0.40/1M |
| Auth | 10K/mes | $0.0055/100K |
Configura alertas de presupuesto en la Consola de Google Cloud.
5. Proteger Cuentas de Servicio
// MAL: Nunca hagas esto en el código del cliente
admin.initializeApp({
credential: admin.credential.cert(require('./serviceAccountKey.json'))
});
// CORRECTO: Usar solo en entornos de servidor
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
6. Manejar Escenarios Sin Conexión
// Habilitar persistencia sin conexión (web)
import { enableMultiTabIndexedDbPersistence } from 'firebase/firestore';
enableMultiTabIndexedDbPersistence(db)
.catch((err) => {
if (err.code === 'failed-precondition') {
// Múltiples pestañas abiertas
} else if (err.code === 'unimplemented') {
// El navegador no es compatible
}
});
// Escuchar conectividad
import { onSnapshot } from 'firebase/firestore';
onSnapshot(doc(db, 'status', 'online'), (doc) => {
if (!doc.exists()) {
console.log('Estás sin conexión');
// Mostrar interfaz de usuario sin conexión
}
});
Problemas Comunes de la API de Firebase y Soluciones
Problema 1: Errores de Permiso Denegado
Síntoma: Error: 7 PERMISSION_DENIED
Causa: Las reglas de seguridad bloquean la operación
Solución:
- Verifica las reglas en la Consola de Firebase
- Verifica que
request.auth.uidcoincida con el usuario esperado - Prueba las reglas con el Rules Playground
Problema 2: Expiración del Token
Síntoma: Error: ID token expired
Solución:
// Forzar actualización del token
const user = auth.currentUser;
if (user) {
await user.getIdToken(true); // Forzar actualización
}
Problema 3: Latencia de Inicio en Frío
Síntoma: Las Cloud Functions tardan de 2 a 5 segundos en la primera llamada
Solución:
// Mantener las funciones "calientes" con pings programados
exports.keepWarm = onSchedule('every 60 seconds', async () => {
await fetch('https://tu-funcion.cloudfunctions.net/health');
});
Problema 4: Consulta Devuelve Resultados Vacíos
Síntoma: La consulta debería devolver datos pero devuelve un array vacío
Causa: Índice faltante o orden de campos incorrecto
Solución: Revisa la Consola de Firestore > Índices para los índices compuestos requeridos.
Casos de Uso en el Mundo Real
Aplicación Fintech: Actualizaciones de Transacciones en Tiempo Real
Una startup de pagos utilizó Firebase Firestore para construir notificaciones de transacciones en tiempo real. Cuando se procesa un pago, las Cloud Functions activan actualizaciones en todos los paneles de administración conectados en 200 ms. Resultado: una reducción del 40% en los tickets de soporte sobre transacciones "pendientes".
Comercio Electrónico: Sincronización de Inventario
Un minorista en línea sincroniza el inventario en la web, iOS y Android utilizando escuchas de Firestore. Cuando el stock cambia, todos los clientes se actualizan automáticamente. La persistencia sin conexión garantiza que los trabajadores del almacén puedan escanear artículos sin conectividad, con sincronización automática al reconectarse.
SaaS: Autenticación Multitenant
Una plataforma B2B utiliza Firebase Auth con reclamaciones personalizadas para el acceso basado en roles. Los usuarios administradores obtienen permisos elevados a través de Cloud Functions que validan contra las configuraciones de tenant de Firestore. Una única base de código sirve a más de 500 organizaciones con datos aislados.
Conclusión
La integración de la API de Firebase implica cuatro servicios principales:
- Autenticación: Inicio de sesión con correo electrónico, Google, Apple y tokens JWT
- Firestore: Base de datos NoSQL con escuchas en tiempo real y reglas de seguridad
- Cloud Functions: Backend sin servidor activado por eventos o HTTP
- Storage: Carga de archivos con distribución CDN
Has aprendido flujos de autenticación, operaciones de base de datos, despliegue de funciones y gestión de archivos. Has visto patrones de producción: manejo de errores, procesamiento por lotes, soporte sin conexión y seguridad.
Preguntas Frecuentes
¿Es Firebase de uso gratuito?
Sí, Firebase tiene un generoso nivel gratuito (Plan Spark) que incluye 5 GB de almacenamiento, 50K lecturas de Firestore al día, 2M invocaciones de Cloud Function y 10K usuarios de Auth al mes. Los planes de pago (Blaze) utilizan precios de pago por uso.
¿Puedo usar Firebase con bases de datos existentes?
Sí. Usa Firebase Extensions para sincronizar con PostgreSQL, MySQL o MongoDB. O llama a APIs externas desde Cloud Functions para integrar con sistemas existentes.
¿Cómo migro de Firebase a otra plataforma?
Exporta datos usando las funciones de exportación de Firestore o la CLI de Firebase. Para grandes conjuntos de datos, usa la tubería de exportación de Dataflow. La complejidad de la migración depende de tu estructura de datos.
¿Firebase soporta GraphQL?
No de forma nativa. Usa soluciones de terceros como firestore-graphql o construye una capa GraphQL con Cloud Functions y Apollo Server.
¿Puedo usar Firebase on-premise?
No. Firebase es solo para Google Cloud. Para alternativas autoalojadas, considera Appwrite, Supabase o Nhost.
¿Cómo manejo cargas de archivos de más de 100 MB?
Usa cargas reanudables con fragmentación. El SDK de Firebase lo maneja automáticamente. Para archivos muy grandes, usa Google Cloud Storage directamente con URLs firmadas.
¿Qué sucede si excedo los límites de consulta de Firestore?
Las consultas fallan con el error FAILED_PRECONDITION. Agrega los índices requeridos o reestructura las consultas. Firestore proporciona enlaces directos para crear los índices faltantes en el mensaje de error.
¿Firebase cumple con el GDPR?
Sí, Firebase ofrece procesamiento de datos compatible con el GDPR. Habilita la residencia de datos en regiones específicas, implementa la exportación/eliminación de datos de usuario y firma la Enmienda de Procesamiento de Datos de Google.
