Integración de Firestore en Angular para guardar mensajes de chat
Clase 17 de 21 • Curso de Firebase con Angular 20
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.