Resumen

Implementa un chat confiable con Firestore en Angular: guarda mensajes y conversaciones, reemplaza mocks por un servicio real y ajusta reglas de seguridad. Aquí verás, paso a paso, cómo usar addDoc, query con where, suscripciones con onSnapshot y manejo de Observable para obtener un flujo reactivo y ordenado por fecha desde el frontend.

¿Cómo integrar FirestoreService para guardar mensajes y conversaciones?

Para centralizar el guardado y la lectura, se crea un FirestoreService que inyecta Firestore y expone métodos claros. Se validan datos mínimos del modelo de mensaje de chat: usuario, contenido y tipo. Si falta algo, se rechaza la promesa y se muestra el error en el chat. Cuando todo está ok, se toma la colección de mensajes y se usa addDoc con la estructura que espera Firebase.

  • Validaciones obligatorias: usuario, contenido y tipo.
  • Colecciones: mensajes y conversaciones.
  • Errores: se capturan con catch y se propagan al componente.
  • Ordenamiento: por fecha en el frontend, tras recibir los documentos.

¿Qué valida guardar mensaje?

Se confirma que el mensaje tenga los campos esenciales. Luego se mapea al formato esperado por Firebase y se almacena con addDoc en la colección de mensajes.

// FirestoreService
async guardarMensaje(mensaje: MensajeChat): Promise<void> {
  if (!mensaje.usuario || !mensaje.contenido || !mensaje.tipo) {
    throw new Error('Datos de mensaje incompletos');
  }
  const ref = collection(this.firestore, 'mensajes');
  const data = {
    usuario: mensaje.usuario,
    contenido: mensaje.contenido,
    tipo: mensaje.tipo, // 'usuario' | 'asistente' | 'error'
    fecha: mensaje.fecha,
  };
  await addDoc(ref, data);
}

¿Cómo obtener mensajes por usuario con observable?

Se construye una query con where para filtrar por el ID de usuario. Se usa onSnapshot para escuchar cambios en tiempo real y emitirlos a través de un Observable. Los mensajes se mapean al modelo de chat y se ordenan por fecha en el frontend para asegurar consistencia.

obtenerMensajesDeUsuario(usuarioId: string): Observable<MensajeChat[]> {
  return new Observable((observer) => {
    const ref = collection(this.firestore, 'mensajes');
    const q = query(ref, where('usuario', '==', usuarioId));

    const unsubscribe = onSnapshot(q, (snap) => {
      const mensajes = snap.docs.map(d => d.data() as MensajeChat)
        .sort((a, b) => a.fecha - b.fecha);
      observer.next(mensajes);
    }, (error) => {
      observer.error(error);
    });

    return () => unsubscribe();
  });
}

¿Cómo se guardan conversaciones completas?

Además de los mensajes individuales, se guardan conversaciones con sus metadatos: usuario, título y los mensajes incluidos. Se usa la misma mecánica: referencia a la colección y addDoc, gestionando errores en el catch.

async guardarConversacion(conv: Conversacion): Promise<void> {
  const ref = collection(this.firestore, 'conversaciones');
  await addDoc(ref, {
    usuario: conv.usuario,
    titulo: conv.titulo,
    mensajes: conv.mensajes,
    fecha: conv.fecha,
  });
}

¿Cómo reemplazar el mock por FirestoreService en el chat?

El ChatService pasa de datos falsos a datos reales. Se inyecta el FirestoreService y se reemplazan, uno por uno, los mocks en inicialización y envío de mensajes. Al suscribirse al observable de mensajes por usuario, cada cambio ejecuta las acciones en los componentes que dependen de ese flujo.

  • Inicializar chat: suscripción a obtener mensajes por usuario.
  • mensajeSubject: emite el array recibido para actualizar la UI.
  • carga de historial: se marca en false cuando llegan los datos.
  • Errores: se muestran y se emite un array vacío.
// ChatService (fragmentos)
async inicializarChat(usuarioId: string) {
  this.firestoreService.obtenerMensajesDeUsuario(usuarioId).subscribe({
    next: (mensajes) => {
      this.mensajeSubject.next(mensajes);
      this.cargandoHistorial = false;
    },
    error: (e) => {
      console.error(e);
      this.mensajeSubject.next([]);
    }
  });
}

async enviarMensaje(mensajeUsuario: MensajeChat) {
  await this.firestoreService.guardarMensaje(mensajeUsuario); // tipo: 'usuario'
  // Al recibir respuesta del asistente o un error, también se guarda.
  await this.firestoreService.guardarMensaje(mensajeAsistente); // tipo: 'asistente'
  // En caso de fallo de Gemini, guardar mensaje de 'error'.
}
  • Tipos de mensaje: usuario, asistente y error.
  • Sustitución total del mock: se activa la inyección, se importan dependencias y se borran líneas simuladas.

¿Cómo ajustar reglas de Firestore y validar el flujo end-to-end?

En la consola de Firebase, en Firestore Database > rules, se pegan reglas que permiten acceder a documentos y modificar mensajes para usuarios autenticados en modo test. Se guarda con Publish o con control S. Luego se levanta la app con npm start, se inicia sesión con Google y se verifica la creación de la colección de mensajes y la persistencia.

  • Autenticación con Google: la app toma la imagen y el ID del usuario.
  • Persistencia: tras refrescar, se recuperan los mensajes por coincidencia de usuario en el where.
  • Tipos visibles en la data: 'usuario' para el emisor y 'asistente' para la respuesta.
  • Colecciones generadas: mensajes y, cuando corresponda, conversaciones.

Buenas prácticas reforzadas en el proceso: - Uso de Observable para reactividad en la UI. - onSnapshot para escuchar cambios y unsubscribe para limpiar suscripciones. - Ordenamiento por fecha en frontend para consistencia de la conversación. - Manejo de errores consistente con promesas y catch en componentes.

¿Te gustaría ver el patrón para paginar mensajes o mejorar el ordenamiento con índices? Comenta qué parte quieres profundizar y qué retos encuentras al integrar Firestore en tu chat.