Saltar a contenido

Firebase - Google Backend-as-a-Service Platform

"Clase de la hoja"

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada

Firebase representa La plataforma integral Backend-as-a-Service (BaaS) de Google, diseñada para acelerar el desarrollo de aplicaciones proporcionando un conjunto de herramientas y servicios basados en la nube. Originalmente desarrollado por Firebase Inc. y adquirido por Google en 2014, Firebase ha evolucionado en una plataforma de desarrollo completa que maneja infraestructura de backend, sincronización de datos en tiempo real, autenticación, hosting, analítica, y mucho más. Firebase ofrece dos soluciones primarias de base de datos: la base de datos original en tiempo real y la tienda más avanzada de Cloud Firestore, ambas diseñadas para apoyar aplicaciones en tiempo real con capacidades offline y sincronización perfecta entre múltiples clientes.

Comienzo con Firebase

Configuración y configuración del proyecto

// Install Firebase CLI
npm install -g firebase-tools

// Login to Firebase
firebase login

// Initialize Firebase project
firebase init

// Install Firebase SDK for web
npm install firebase

// Initialize Firebase in your application
import \\\\{ initializeApp \\\\} from 'firebase/app';
import \\\\{ getFirestore \\\\} from 'firebase/firestore';
import \\\\{ getAuth \\\\} from 'firebase/auth';
import \\\\{ getStorage \\\\} from 'firebase/storage';

const firebaseConfig = \\\\{
  apiKey: "your-api-key",
  authDomain: "your-project.firebaseapp.com",
  databaseURL: "https://your-project-default-rtdb.firebaseio.com",
  projectId: "your-project-id",
  storageBucket: "your-project.appspot.com",
  messagingSenderId: "123456789",
  appId: "your-app-id"
\\\\};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

// Initialize services
const db = getFirestore(app);
const auth = getAuth(app);
const storage = getStorage(app);

export \\\\{ db, auth, storage \\\\};

Firebase Console and Project Management

// Firebase project structure
// - Authentication: User management and sign-in
// - Firestore Database: NoSQL document database
// - Realtime Database: JSON tree database
// - Storage: File storage service
// - Hosting: Web hosting service
// - Functions: Serverless functions
// - Analytics: App usage analytics
// - Performance: App performance monitoring

// Environment configuration
// Development
const devConfig = \\\\{
  apiKey: "dev-api-key",
  authDomain: "dev-project.firebaseapp.com",
  projectId: "dev-project-id"
\\\\};

// Production
const prodConfig = \\\\{
  apiKey: "prod-api-key",
  authDomain: "prod-project.firebaseapp.com",
  projectId: "prod-project-id"
\\\\};

const config = process.env.NODE_ENV === 'production' ? prodConfig : devConfig;
const app = initializeApp(config);

Cloud Firestore (Recomendado)

Estructura y colecciones de bases de datos

// Firestore data model: Collections -> Documents -> Fields
// Collections contain documents
// Documents contain fields and subcollections
// Documents are identified by unique IDs

import \\\\{
  collection,
  doc,
  addDoc,
  setDoc,
  getDoc,
  getDocs,
  updateDoc,
  deleteDoc,
  query,
  where,
  orderBy,
  limit,
  onSnapshot
\\\\} from 'firebase/firestore';

// Reference to a collection
const usersRef = collection(db, 'users');

// Reference to a specific document
const userRef = doc(db, 'users', 'user123');

// Reference to a subcollection
const postsRef = collection(db, 'users', 'user123', 'posts');

// Create document with auto-generated ID
const createUser = async (userData) => \\\\{
  try \\\\{
    const docRef = await addDoc(collection(db, 'users'), \\\\{
      name: userData.name,
      email: userData.email,
      createdAt: new Date(),
      isActive: true,
      profile: \\\\{
        bio: userData.bio||'',
        avatar: userData.avatar||'',
        preferences: \\\\{
          theme: 'light',
          notifications: true
        \\\\}
      \\\\}
    \\\\});
    console.log('Document written with ID: ', docRef.id);
    return docRef.id;
  \\\\} catch (error) \\\\{
    console.error('Error adding document: ', error);
    throw error;
  \\\\}
\\\\};

// Create document with custom ID
const createUserWithId = async (userId, userData) => \\\\{
  try \\\\{
    await setDoc(doc(db, 'users', userId), \\\\{
      name: userData.name,
      email: userData.email,
      createdAt: new Date(),
      isActive: true
    \\\\});
    console.log('Document created with custom ID: ', userId);
  \\\\} catch (error) \\\\{
    console.error('Error creating document: ', error);
    throw error;
  \\\\}
\\\\};

CRUD Operaciones

// CREATE - Add documents
const addPost = async (userId, postData) => \\\\{
  try \\\\{
    const postRef = await addDoc(collection(db, 'users', userId, 'posts'), \\\\{
      title: postData.title,
      content: postData.content,
      tags: postData.tags||[],
      publishedAt: new Date(),
      isPublished: false,
      likes: 0,
      comments: []
    \\\\});
    return postRef.id;
  \\\\} catch (error) \\\\{
    console.error('Error adding post: ', error);
    throw error;
  \\\\}
\\\\};

// READ - Get single document
const getUser = async (userId) => \\\\{
  try \\\\{
    const docSnap = await getDoc(doc(db, 'users', userId));

    if (docSnap.exists()) \\\\{
      return \\\\{ id: docSnap.id, ...docSnap.data() \\\\};
    \\\\} else \\\\{
      console.log('No such document!');
      return null;
    \\\\}
  \\\\} catch (error) \\\\{
    console.error('Error getting document: ', error);
    throw error;
  \\\\}
\\\\};

// READ - Get multiple documents
const getAllUsers = async () => \\\\{
  try \\\\{
    const querySnapshot = await getDocs(collection(db, 'users'));
    const users = [];

    querySnapshot.forEach((doc) => \\\\{
      users.push(\\\\{ id: doc.id, ...doc.data() \\\\});
    \\\\});

    return users;
  \\\\} catch (error) \\\\{
    console.error('Error getting documents: ', error);
    throw error;
  \\\\}
\\\\};

// UPDATE - Update document
const updateUser = async (userId, updates) => \\\\{
  try \\\\{
    const userRef = doc(db, 'users', userId);
    await updateDoc(userRef, \\\\{
      ...updates,
      updatedAt: new Date()
    \\\\});
    console.log('Document updated successfully');
  \\\\} catch (error) \\\\{
    console.error('Error updating document: ', error);
    throw error;
  \\\\}
\\\\};

// UPDATE - Update nested fields
const updateUserProfile = async (userId, profileUpdates) => \\\\{
  try \\\\{
    const userRef = doc(db, 'users', userId);
    await updateDoc(userRef, \\\\{
      'profile.bio': profileUpdates.bio,
      'profile.avatar': profileUpdates.avatar,
      'profile.preferences.theme': profileUpdates.theme,
      updatedAt: new Date()
    \\\\});
  \\\\} catch (error) \\\\{
    console.error('Error updating profile: ', error);
    throw error;
  \\\\}
\\\\};

// DELETE - Delete document
const deleteUser = async (userId) => \\\\{
  try \\\\{
    await deleteDoc(doc(db, 'users', userId));
    console.log('Document deleted successfully');
  \\\\} catch (error) \\\\{
    console.error('Error deleting document: ', error);
    throw error;
  \\\\}
\\\\};

Consultas y Filtros

import \\\\{
  query,
  where,
  orderBy,
  limit,
  startAt,
  endAt,
  startAfter,
  endBefore
\\\\} from 'firebase/firestore';

// Simple queries
const getActiveUsers = async () => \\\\{
  const q = query(
    collection(db, 'users'),
    where('isActive', '==', true)
  );

  const querySnapshot = await getDocs(q);
  return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};

// Compound queries
const getRecentActivePosts = async () => \\\\{
  const q = query(
    collection(db, 'posts'),
    where('isPublished', '==', true),
    where('publishedAt', '>', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)),
    orderBy('publishedAt', 'desc'),
    limit(10)
  );

  const querySnapshot = await getDocs(q);
  return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};

// Array queries
const getPostsByTags = async (tags) => \\\\{
  const q = query(
    collection(db, 'posts'),
    where('tags', 'array-contains-any', tags)
  );

  const querySnapshot = await getDocs(q);
  return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};

// Range queries
const getUsersByAgeRange = async (minAge, maxAge) => \\\\{
  const q = query(
    collection(db, 'users'),
    where('age', '>=', minAge),
    where('age', '<=', maxAge),
    orderBy('age')
  );

  const querySnapshot = await getDocs(q);
  return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};

// Pagination
const getPaginatedUsers = async (pageSize = 10, lastDoc = null) => \\\\{
  let q = query(
    collection(db, 'users'),
    orderBy('createdAt', 'desc'),
    limit(pageSize)
  );

  if (lastDoc) \\\\{
    q = query(
      collection(db, 'users'),
      orderBy('createdAt', 'desc'),
      startAfter(lastDoc),
      limit(pageSize)
    );
  \\\\}

  const querySnapshot = await getDocs(q);
  const users = querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
  const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];

  return \\\\{ users, lastDoc: lastVisible, hasMore: users.length === pageSize \\\\};
\\\\};

// Text search (limited - consider Algolia for full-text search)
const searchUsersByName = async (searchTerm) => \\\\{
  const q = query(
    collection(db, 'users'),
    where('name', '>=', searchTerm),
    where('name', '<=', searchTerm + '\uf8ff'),
    orderBy('name'),
    limit(10)
  );

  const querySnapshot = await getDocs(q);
  return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};

Escuchadores en tiempo real

// Real-time listener for single document
const subscribeToUser = (userId, callback) => \\\\{
  const userRef = doc(db, 'users', userId);

  const unsubscribe = onSnapshot(userRef, (doc) => \\\\{
    if (doc.exists()) \\\\{
      callback(\\\\{ id: doc.id, ...doc.data() \\\\});
    \\\\} else \\\\{
      callback(null);
    \\\\}
  \\\\}, (error) => \\\\{
    console.error('Error listening to user: ', error);
  \\\\});

  return unsubscribe; // Call this function to stop listening
\\\\};

// Real-time listener for collection
const subscribeToActiveUsers = (callback) => \\\\{
  const q = query(
    collection(db, 'users'),
    where('isActive', '==', true),
    orderBy('createdAt', 'desc')
  );

  const unsubscribe = onSnapshot(q, (querySnapshot) => \\\\{
    const users = [];
    querySnapshot.forEach((doc) => \\\\{
      users.push(\\\\{ id: doc.id, ...doc.data() \\\\});
    \\\\});
    callback(users);
  \\\\}, (error) => \\\\{
    console.error('Error listening to users: ', error);
  \\\\});

  return unsubscribe;
\\\\};

// Listen to document changes with metadata
const subscribeToUserWithMetadata = (userId, callback) => \\\\{
  const userRef = doc(db, 'users', userId);

  const unsubscribe = onSnapshot(userRef, \\\\{ includeMetadataChanges: true \\\\}, (doc) => \\\\{
    const source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
    const data = doc.exists() ? \\\\{ id: doc.id, ...doc.data() \\\\} : null;

    callback(\\\\{ data, source, fromCache: doc.metadata.fromCache \\\\});
  \\\\});

  return unsubscribe;
\\\\};

// React hook example for real-time data
const useUser = (userId) => \\\\{
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => \\\\{
    if (!userId) \\\\{
      setUser(null);
      setLoading(false);
      return;
    \\\\}

    const unsubscribe = subscribeToUser(userId, (userData) => \\\\{
      setUser(userData);
      setLoading(false);
      setError(null);
    \\\\});

    return () => unsubscribe();
  \\\\}, [userId]);

  return \\\\{ user, loading, error \\\\};
\\\\};

Transacciones y operaciones de bastón

import \\\\{
  runTransaction,
  writeBatch,
  increment,
  arrayUnion,
  arrayRemove,
  serverTimestamp
\\\\} from 'firebase/firestore';

// Transaction example - Transfer points between users
const transferPoints = async (fromUserId, toUserId, points) => \\\\{
  try \\\\{
    await runTransaction(db, async (transaction) => \\\\{
      const fromUserRef = doc(db, 'users', fromUserId);
      const toUserRef = doc(db, 'users', toUserId);

      const fromUserDoc = await transaction.get(fromUserRef);
      const toUserDoc = await transaction.get(toUserRef);

      if (!fromUserDoc.exists()||!toUserDoc.exists()) \\\\{
        throw new Error('One or both users do not exist');
      \\\\}

      const fromUserPoints = fromUserDoc.data().points||0;

      if (fromUserPoints < points) \\\\{
        throw new Error('Insufficient points');
      \\\\}

      transaction.update(fromUserRef, \\\\{
        points: fromUserPoints - points,
        updatedAt: serverTimestamp()
      \\\\});

      transaction.update(toUserRef, \\\\{
        points: increment(points),
        updatedAt: serverTimestamp()
      \\\\});
    \\\\});

    console.log('Points transferred successfully');
  \\\\} catch (error) \\\\{
    console.error('Transaction failed: ', error);
    throw error;
  \\\\}
\\\\};

// Batch operations
const batchUpdateUsers = async (userUpdates) => \\\\{
  const batch = writeBatch(db);

  userUpdates.forEach((\\\\{ userId, updates \\\\}) => \\\\{
    const userRef = doc(db, 'users', userId);
    batch.update(userRef, \\\\{
      ...updates,
      updatedAt: serverTimestamp()
    \\\\});
  \\\\});

  try \\\\{
    await batch.commit();
    console.log('Batch update completed');
  \\\\} catch (error) \\\\{
    console.error('Batch update failed: ', error);
    throw error;
  \\\\}
\\\\};

// Array operations
const addTagToPost = async (postId, tag) => \\\\{
  const postRef = doc(db, 'posts', postId);
  await updateDoc(postRef, \\\\{
    tags: arrayUnion(tag),
    updatedAt: serverTimestamp()
  \\\\});
\\\\};

const removeTagFromPost = async (postId, tag) => \\\\{
  const postRef = doc(db, 'posts', postId);
  await updateDoc(postRef, \\\\{
    tags: arrayRemove(tag),
    updatedAt: serverTimestamp()
  \\\\});
\\\\};

// Increment/decrement operations
const likePost = async (postId) => \\\\{
  const postRef = doc(db, 'posts', postId);
  await updateDoc(postRef, \\\\{
    likes: increment(1),
    updatedAt: serverTimestamp()
  \\\\});
\\\\};

Base de datos en tiempo real

Estructura de bases de datos y referencias

import \\\\{
  getDatabase,
  ref,
  set,
  get,
  push,
  update,
  remove,
  on,
  off,
  child,
  orderByChild,
  orderByKey,
  orderByValue,
  limitToFirst,
  limitToLast,
  startAt,
  endAt,
  equalTo
\\\\} from 'firebase/database';

// Initialize Realtime Database
const rtdb = getDatabase();

// Database structure (JSON tree)
// \\\\{
//   "users": \\\\{
//     "user1": \\\\{
//       "name": "John Doe",
//       "email": "john@example.com",
//       "posts": \\\\{
//         "post1": true,
//         "post2": true
//       \\\\}
//     \\\\}
//   \\\\},
//   "posts": \\\\{
//     "post1": \\\\{
//       "title": "First Post",
//       "content": "Hello World",
//       "author": "user1",
//       "timestamp": 1642694400000
//     \\\\}
//   \\\\}
// \\\\}

// Create references
const usersRef = ref(rtdb, 'users');
const userRef = ref(rtdb, 'users/user1');
const postsRef = ref(rtdb, 'posts');

CRUD Operaciones en la base de datos en tiempo real

// CREATE/UPDATE - Set data
const createUser = async (userId, userData) => \\\\{
  try \\\\{
    await set(ref(rtdb, `users/$\\{userId\\}`), \\\\{
      name: userData.name,
      email: userData.email,
      createdAt: Date.now(),
      isActive: true
    \\\\});
    console.log('User created successfully');
  \\\\} catch (error) \\\\{
    console.error('Error creating user: ', error);
    throw error;
  \\\\}
\\\\};

// CREATE - Push data (auto-generated key)
const addPost = async (postData) => \\\\{
  try \\\\{
    const newPostRef = push(ref(rtdb, 'posts'));
    await set(newPostRef, \\\\{
      title: postData.title,
      content: postData.content,
      author: postData.author,
      timestamp: Date.now(),
      likes: 0
    \\\\});
    return newPostRef.key;
  \\\\} catch (error) \\\\{
    console.error('Error adding post: ', error);
    throw error;
  \\\\}
\\\\};

// READ - Get data once
const getUser = async (userId) => \\\\{
  try \\\\{
    const snapshot = await get(ref(rtdb, `users/$\\{userId\\}`));
    if (snapshot.exists()) \\\\{
      return \\\\{ id: userId, ...snapshot.val() \\\\};
    \\\\} else \\\\{
      console.log('No data available');
      return null;
    \\\\}
  \\\\} catch (error) \\\\{
    console.error('Error getting user: ', error);
    throw error;
  \\\\}
\\\\};

// READ - Get all users
const getAllUsers = async () => \\\\{
  try \\\\{
    const snapshot = await get(ref(rtdb, 'users'));
    if (snapshot.exists()) \\\\{
      const users = [];
      snapshot.forEach((childSnapshot) => \\\\{
        users.push(\\\\{
          id: childSnapshot.key,
          ...childSnapshot.val()
        \\\\});
      \\\\});
      return users;
    \\\\} else \\\\{
      return [];
    \\\\}
  \\\\} catch (error) \\\\{
    console.error('Error getting users: ', error);
    throw error;
  \\\\}
\\\\};

// UPDATE - Update specific fields
const updateUser = async (userId, updates) => \\\\{
  try \\\\{
    await update(ref(rtdb, `users/$\\{userId\\}`), \\\\{
      ...updates,
      updatedAt: Date.now()
    \\\\});
    console.log('User updated successfully');
  \\\\} catch (error) \\\\{
    console.error('Error updating user: ', error);
    throw error;
  \\\\}
\\\\};

// DELETE - Remove data
const deleteUser = async (userId) => \\\\{
  try \\\\{
    await remove(ref(rtdb, `users/$\\{userId\\}`));
    console.log('User deleted successfully');
  \\\\} catch (error) \\\\{
    console.error('Error deleting user: ', error);
    throw error;
  \\\\}
\\\\};

Escuchadores y consultas en tiempo real

// Real-time listener
const subscribeToUser = (userId, callback) => \\\\{
  const userRef = ref(rtdb, `users/$\\{userId\\}`);

  const unsubscribe = on(userRef, 'value', (snapshot) => \\\\{
    const data = snapshot.exists() ? \\\\{ id: userId, ...snapshot.val() \\\\} : null;
    callback(data);
  \\\\}, (error) => \\\\{
    console.error('Error listening to user: ', error);
  \\\\});

  return () => off(userRef, 'value', unsubscribe);
\\\\};

// Listen to child events
const subscribeToUserPosts = (userId, callbacks) => \\\\{
  const userPostsRef = ref(rtdb, `users/$\\{userId\\}/posts`);

  const childAddedListener = on(userPostsRef, 'child_added', callbacks.onAdded);
  const childChangedListener = on(userPostsRef, 'child_changed', callbacks.onChanged);
  const childRemovedListener = on(userPostsRef, 'child_removed', callbacks.onRemoved);

  return () => \\\\{
    off(userPostsRef, 'child_added', childAddedListener);
    off(userPostsRef, 'child_changed', childChangedListener);
    off(userPostsRef, 'child_removed', childRemovedListener);
  \\\\};
\\\\};

// Queries with ordering and filtering
const getTopPosts = async (limit = 10) => \\\\{
  try \\\\{
    const topPostsQuery = query(
      ref(rtdb, 'posts'),
      orderByChild('likes'),
      limitToLast(limit)
    );

    const snapshot = await get(topPostsQuery);
    const posts = [];

    snapshot.forEach((childSnapshot) => \\\\{
      posts.unshift(\\\\{
        id: childSnapshot.key,
        ...childSnapshot.val()
      \\\\});
    \\\\});

    return posts;
  \\\\} catch (error) \\\\{
    console.error('Error getting top posts: ', error);
    throw error;
  \\\\}
\\\\};

// Range queries
const getPostsByDateRange = async (startDate, endDate) => \\\\{
  try \\\\{
    const dateRangeQuery = query(
      ref(rtdb, 'posts'),
      orderByChild('timestamp'),
      startAt(startDate),
      endAt(endDate)
    );

    const snapshot = await get(dateRangeQuery);
    const posts = [];

    snapshot.forEach((childSnapshot) => \\\\{
      posts.push(\\\\{
        id: childSnapshot.key,
        ...childSnapshot.val()
      \\\\});
    \\\\});

    return posts;
  \\\\} catch (error) \\\\{
    console.error('Error getting posts by date range: ', error);
    throw error;
  \\\\}
\\\\};

// Equal to query
const getUsersByStatus = async (status) => \\\\{
  try \\\\{
    const statusQuery = query(
      ref(rtdb, 'users'),
      orderByChild('status'),
      equalTo(status)
    );

    const snapshot = await get(statusQuery);
    const users = [];

    snapshot.forEach((childSnapshot) => \\\\{
      users.push(\\\\{
        id: childSnapshot.key,
        ...childSnapshot.val()
      \\\\});
    \\\\});

    return users;
  \\\\} catch (error) \\\\{
    console.error('Error getting users by status: ', error);
    throw error;
  \\\\}
\\\\};

Firebase Authentication

Configuración de autenticación y métodos

import \\\\{
  getAuth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signInWithPopup,
  GoogleAuthProvider,
  FacebookAuthProvider,
  TwitterAuthProvider,
  signOut,
  onAuthStateChanged,
  updateProfile,
  updatePassword,
  sendPasswordResetEmail,
  deleteUser
\\\\} from 'firebase/auth';

const auth = getAuth();

// Email/Password Authentication
const signUpWithEmail = async (email, password, displayName) => \\\\{
  try \\\\{
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    const user = userCredential.user;

    // Update user profile
    await updateProfile(user, \\\\{
      displayName: displayName
    \\\\});

    // Create user document in Firestore
    await setDoc(doc(db, 'users', user.uid), \\\\{
      email: user.email,
      displayName: displayName,
      createdAt: new Date(),
      isActive: true
    \\\\});

    return user;
  \\\\} catch (error) \\\\{
    console.error('Error signing up: ', error);
    throw error;
  \\\\}
\\\\};

const signInWithEmail = async (email, password) => \\\\{
  try \\\\{
    const userCredential = await signInWithEmailAndPassword(auth, email, password);
    return userCredential.user;
  \\\\} catch (error) \\\\{
    console.error('Error signing in: ', error);
    throw error;
  \\\\}
\\\\};

// Social Authentication
const signInWithGoogle = async () => \\\\{
  try \\\\{
    const provider = new GoogleAuthProvider();
    provider.addScope('profile');
    provider.addScope('email');

    const result = await signInWithPopup(auth, provider);
    const user = result.user;

    // Check if user document exists, create if not
    const userDoc = await getDoc(doc(db, 'users', user.uid));
    if (!userDoc.exists()) \\\\{
      await setDoc(doc(db, 'users', user.uid), \\\\{
        email: user.email,
        displayName: user.displayName,
        photoURL: user.photoURL,
        provider: 'google',
        createdAt: new Date(),
        isActive: true
      \\\\});
    \\\\}

    return user;
  \\\\} catch (error) \\\\{
    console.error('Error signing in with Google: ', error);
    throw error;
  \\\\}
\\\\};

// Authentication state observer
const useAuthState = () => \\\\{
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => \\\\{
    const unsubscribe = onAuthStateChanged(auth, (user) => \\\\{
      setUser(user);
      setLoading(false);
    \\\\});

    return () => unsubscribe();
  \\\\}, []);

  return \\\\{ user, loading \\\\};
\\\\};

// Sign out
const signOutUser = async () => \\\\{
  try \\\\{
    await signOut(auth);
    console.log('User signed out successfully');
  \\\\} catch (error) \\\\{
    console.error('Error signing out: ', error);
    throw error;
  \\\\}
\\\\};

// Password reset
const resetPassword = async (email) => \\\\{
  try \\\\{
    await sendPasswordResetEmail(auth, email);
    console.log('Password reset email sent');
  \\\\} catch (error) \\\\{
    console.error('Error sending password reset email: ', error);
    throw error;
  \\\\}
\\\\};

// Update user profile
const updateUserProfile = async (updates) => \\\\{
  try \\\\{
    const user = auth.currentUser;
    if (user) \\\\{
      await updateProfile(user, updates);

      // Also update Firestore document
      await updateDoc(doc(db, 'users', user.uid), \\\\{
        ...updates,
        updatedAt: new Date()
      \\\\});
    \\\\}
  \\\\} catch (error) \\\\{
    console.error('Error updating profile: ', error);
    throw error;
  \\\\}
\\\\};

Normas de seguridad

// Firestore Security Rules
// rules_version = '2';
// service cloud.firestore \\\\{
//   match /databases/\\\\{database\\\\}/documents \\\\{
//     // Users can read and write their own data
//     match /users/\\\\{userId\\\\} \\\\{
//       allow read, write: if request.auth != null && request.auth.uid == userId;
//     \\\\}
//
//     // Posts are readable by all authenticated users
//     // Only the author can write/update/delete
//     match /posts/\\\\{postId\\\\} \\\\{
//       allow read: if request.auth != null;
//       allow create: if request.auth != null &&
//                     request.auth.uid == resource.data.authorId;
//       allow update, delete: if request.auth != null &&
//                             request.auth.uid == resource.data.authorId;
//     \\\\}
//
//     // Comments can be created by authenticated users
//     // Only the author can update/delete their comments
//     match /posts/\\\\{postId\\\\}/comments/\\\\{commentId\\\\} \\\\{
//       allow read: if request.auth != null;
//       allow create: if request.auth != null;
//       allow update, delete: if request.auth != null &&
//                             request.auth.uid == resource.data.authorId;
//     \\\\}
//   \\\\}
// \\\\}

// Realtime Database Security Rules
// \\\\{
//   "rules": \\\\{
//     "users": \\\\{
//       "$uid": \\\\{
//         ".read": "$uid === auth.uid",
//         ".write": "$uid === auth.uid"
//       \\\\}
//     \\\\},
//     "posts": \\\\{
//       ".read": "auth != null",
//       "$postId": \\\\{
//         ".write": "auth != null && (
//           !data.exists()||
//           data.child('authorId').val() === auth.uid
//         )"
//       \\\\}
//     \\\\}
//   \\\\}
// \\\\}

// Custom claims for role-based access
const setUserRole = async (uid, role) => \\\\{
  // This would be done in a Cloud Function with Admin SDK
  // admin.auth().setCustomUserClaims(uid, \\\\{ role: role \\\\});
\\\\};

// Check user role in security rules
// allow write: if request.auth.token.role == 'admin';

Almacenamiento de bomberos

Carga de archivos y administración

import \\\\{
  getStorage,
  ref as storageRef,
  uploadBytes,
  uploadBytesResumable,
  getDownloadURL,
  deleteObject,
  listAll,
  getMetadata,
  updateMetadata
\\\\} from 'firebase/storage';

const storage = getStorage();

// Upload file
const uploadFile = async (file, path) => \\\\{
  try \\\\{
    const fileRef = storageRef(storage, path);
    const snapshot = await uploadBytes(fileRef, file);
    const downloadURL = await getDownloadURL(snapshot.ref);

    console.log('File uploaded successfully');
    return \\\\{ downloadURL, fullPath: snapshot.ref.fullPath \\\\};
  \\\\} catch (error) \\\\{
    console.error('Error uploading file: ', error);
    throw error;
  \\\\}
\\\\};

// Upload with progress tracking
const uploadFileWithProgress = (file, path, onProgress) => \\\\{
  return new Promise((resolve, reject) => \\\\{
    const fileRef = storageRef(storage, path);
    const uploadTask = uploadBytesResumable(fileRef, file);

    uploadTask.on('state_changed',
      (snapshot) => \\\\{
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress(progress);
      \\\\},
      (error) => \\\\{
        console.error('Upload error: ', error);
        reject(error);
      \\\\},
      async () => \\\\{
        try \\\\{
          const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
          resolve(\\\\{ downloadURL, fullPath: uploadTask.snapshot.ref.fullPath \\\\});
        \\\\} catch (error) \\\\{
          reject(error);
        \\\\}
      \\\\}
    );
  \\\\});
\\\\};

// Upload user avatar
const uploadUserAvatar = async (userId, file) => \\\\{
  try \\\\{
    const path = `avatars/$\\{userId\\}/$\\{file.name\\}`;
    const result = await uploadFile(file, path);

    // Update user profile with avatar URL
    await updateDoc(doc(db, 'users', userId), \\\\{
      photoURL: result.downloadURL,
      updatedAt: new Date()
    \\\\});

    return result.downloadURL;
  \\\\} catch (error) \\\\{
    console.error('Error uploading avatar: ', error);
    throw error;
  \\\\}
\\\\};

// Delete file
const deleteFile = async (filePath) => \\\\{
  try \\\\{
    const fileRef = storageRef(storage, filePath);
    await deleteObject(fileRef);
    console.log('File deleted successfully');
  \\\\} catch (error) \\\\{
    console.error('Error deleting file: ', error);
    throw error;
  \\\\}
\\\\};

// List files in directory
const listFiles = async (path) => \\\\{
  try \\\\{
    const listRef = storageRef(storage, path);
    const result = await listAll(listRef);

    const files = await Promise.all(
      result.items.map(async (itemRef) => \\\\{
        const url = await getDownloadURL(itemRef);
        const metadata = await getMetadata(itemRef);

        return \\\\{
          name: itemRef.name,
          fullPath: itemRef.fullPath,
          downloadURL: url,
          size: metadata.size,
          contentType: metadata.contentType,
          timeCreated: metadata.timeCreated
        \\\\};
      \\\\})
    );

    return files;
  \\\\} catch (error) \\\\{
    console.error('Error listing files: ', error);
    throw error;
  \\\\}
\\\\};

Funciones de la nube de fuego

Desarrollo de funciones y despliegue

// Install Firebase Functions
// npm install -g firebase-tools
// firebase init functions

// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

// HTTP Cloud Function
exports.helloWorld = functions.https.onRequest((request, response) => \\\\{
  response.send('Hello from Firebase!');
\\\\});

// Firestore Trigger
exports.onUserCreate = functions.firestore
  .document('users/\\\\{userId\\\\}')
  .onCreate(async (snap, context) => \\\\{
    const userData = snap.data();
    const userId = context.params.userId;

    // Send welcome email
    console.log(`New user created: $\\{userData.email\\}`);

    // Create user stats document
    await admin.firestore().collection('userStats').doc(userId).set(\\\\{
      postsCount: 0,
      likesReceived: 0,
      joinedAt: admin.firestore.FieldValue.serverTimestamp()
    \\\\});
  \\\\});

// Scheduled Function
exports.dailyCleanup = functions.pubsub
  .schedule('0 2 * * *') // Every day at 2 AM
  .timeZone('America/New_York')
  .onRun(async (context) => \\\\{
    // Clean up old data
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - 30);

    const oldPosts = await admin.firestore()
      .collection('posts')
      .where('createdAt', '<', cutoffDate)
      .where('isDeleted', '==', true)
      .get();

    const batch = admin.firestore().batch();
    oldPosts.docs.forEach(doc => \\\\{
      batch.delete(doc.ref);
    \\\\});

    await batch.commit();
    console.log(`Deleted $\\{oldPosts.size\\} old posts`);
  \\\\});

// Callable Function
exports.addAdminRole = functions.https.onCall(async (data, context) => \\\\{
  // Check if user is authenticated and is admin
  if (!context.auth) \\\\{
    throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
  \\\\}

  if (!context.auth.token.admin) \\\\{
    throw new functions.https.HttpsError('permission-denied', 'User must be admin');
  \\\\}

  try \\\\{
    await admin.auth().setCustomUserClaims(data.uid, \\\\{ admin: true \\\\});
    return \\\\{ message: 'Admin role added successfully' \\\\};
  \\\\} catch (error) \\\\{
    throw new functions.https.HttpsError('internal', 'Error adding admin role');
  \\\\}
\\\\});

// Deploy functions
// firebase deploy --only functions

Optimización del rendimiento y mejores prácticas

Modelado de datos Buenas prácticas

// Firestore data modeling principles

// 1. Denormalization for read efficiency
const createPostWithAuthor = async (postData, authorData) => \\\\{
  const postRef = await addDoc(collection(db, 'posts'), \\\\{
    title: postData.title,
    content: postData.content,
    // Denormalize author data for efficient reads
    author: \\\\{
      id: authorData.id,
      name: authorData.name,
      avatar: authorData.avatar
    \\\\},
    createdAt: new Date(),
    likes: 0,
    commentsCount: 0
  \\\\});

  return postRef.id;
\\\\};

// 2. Use subcollections for one-to-many relationships
const addCommentToPost = async (postId, commentData) => \\\\{
  const commentRef = await addDoc(
    collection(db, 'posts', postId, 'comments'),
    \\\\{
      content: commentData.content,
      author: commentData.author,
      createdAt: new Date(),
      likes: 0
    \\\\}
  );

  // Update comment count in parent post
  await updateDoc(doc(db, 'posts', postId), \\\\{
    commentsCount: increment(1)
  \\\\});

  return commentRef.id;
\\\\};

// 3. Use arrays for small lists, subcollections for large lists
const addTagToPost = async (postId, tag) => \\\\{
  await updateDoc(doc(db, 'posts', postId), \\\\{
    tags: arrayUnion(tag)
  \\\\});
\\\\};

// 4. Optimize for your queries
// Create composite indexes for compound queries
// Index: collection: posts, fields: isPublished ASC, createdAt DESC

const getPublishedPosts = async () => \\\\{
  const q = query(
    collection(db, 'posts'),
    where('isPublished', '==', true),
    orderBy('createdAt', 'desc'),
    limit(20)
  );

  return await getDocs(q);
\\\\};

Caching y Offline Support

import \\\\{ enableNetwork, disableNetwork \\\\} from 'firebase/firestore';

// Enable offline persistence
import \\\\{ initializeFirestore, persistentLocalCache \\\\} from 'firebase/firestore';

const db = initializeFirestore(app, \\\\{
  localCache: persistentLocalCache()
\\\\});

// Handle online/offline state
const handleNetworkChange = async (isOnline) => \\\\{
  try \\\\{
    if (isOnline) \\\\{
      await enableNetwork(db);
      console.log('Network enabled');
    \\\\} else \\\\{
      await disableNetwork(db);
      console.log('Network disabled');
    \\\\}
  \\\\} catch (error) \\\\{
    console.error('Error changing network state: ', error);
  \\\\}
\\\\};

// Listen for network changes
window.addEventListener('online', () => handleNetworkChange(true));
window.addEventListener('offline', () => handleNetworkChange(false));

// Cache management
const getCachedData = async (docRef) => \\\\{
  try \\\\{
    // Try to get from cache first
    const cachedDoc = await getDoc(docRef, \\\\{ source: 'cache' \\\\});
    if (cachedDoc.exists()) \\\\{
      return \\\\{ data: cachedDoc.data(), fromCache: true \\\\};
    \\\\}
  \\\\} catch (error) \\\\{
    console.log('No cached data available');
  \\\\}

  // Fallback to server
  const serverDoc = await getDoc(docRef, \\\\{ source: 'server' \\\\});
  return \\\\{ data: serverDoc.exists() ? serverDoc.data() : null, fromCache: false \\\\};
\\\\};

Vigilancia de la seguridad y el desempeño

// Performance monitoring
import \\\\{ getPerformance, trace \\\\} from 'firebase/performance';

const perf = getPerformance(app);

// Custom trace
const measureDatabaseOperation = async (operation) => \\\\{
  const customTrace = trace(perf, 'database_operation');
  customTrace.start();

  try \\\\{
    const result = await operation();
    customTrace.putAttribute('success', 'true');
    return result;
  \\\\} catch (error) \\\\{
    customTrace.putAttribute('success', 'false');
    customTrace.putAttribute('error', error.message);
    throw error;
  \\\\} finally \\\\{
    customTrace.stop();
  \\\\}
\\\\};

// Usage
const getUserWithTrace = async (userId) => \\\\{
  return await measureDatabaseOperation(async () => \\\\{
    return await getDoc(doc(db, 'users', userId));
  \\\\});
\\\\};

// Analytics
import \\\\{ getAnalytics, logEvent \\\\} from 'firebase/analytics';

const analytics = getAnalytics(app);

// Log custom events
const logUserAction = (action, parameters = \\\\{\\\\}) => \\\\{
  logEvent(analytics, action, \\\\{
    ...parameters,
    timestamp: Date.now()
  \\\\});
\\\\};

// Usage
logUserAction('post_created', \\\\{
  post_id: 'post123',
  category: 'technology'
\\\\});

La amplia gama de servicios de Firebase ofrece a los desarrolladores una poderosa plataforma para construir aplicaciones modernas y escalables con capacidades en tiempo real, autenticación robusta y soporte offline sin problemas. Su integración con Google Cloud Platform y el amplio soporte SDK en múltiples plataformas hace que sea una excelente opción para el desarrollo rápido de aplicaciones, desde prototipos simples a aplicaciones complejas y listas de producción que sirven a millones de usuarios.