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.