Overview
Thechats collection stores customer support conversations, typically integrated with WhatsApp for real-time messaging.
Collection Path
chats/{chatId}
Document Schema
Show Participant Information
Show Participant Information
Show Chat Status
Show Chat Status
Show Assignment
Show Assignment
Show Conversation Data
Show Conversation Data
Show Messages
Show Messages
Array of message objects
[
{
id: 'msg-123',
sender: 'customer', // or 'agent', 'system'
senderName: 'Juan Pérez',
text: 'Hola, ¿cuándo llega mi pedido?',
timestamp: Timestamp,
mediaUrl: null, // Optional: image/file URL
read: true
}
]
Copy of the most recent message for quick access
Show Timestamps
Show Timestamps
Show Metrics
Show Metrics
Example Document
{
"userId": "firebase-uid-123",
"customerName": "Juan Pérez",
"customerPhone": "573001234567",
"customerEmail": "[email protected]",
"status": "OPEN",
"priority": "medium",
"assignedTo": "agent-uid-456",
"assignedAt": "2026-03-05T10:15:00Z",
"subject": "Consulta sobre pedido #ABC12345",
"orderId": "order-abc12345",
"category": "order_inquiry",
"messages": [
{
"id": "msg-001",
"sender": "customer",
"senderName": "Juan Pérez",
"text": "Hola, ¿cuándo llega mi pedido #ABC12345?",
"timestamp": "2026-03-05T10:00:00Z",
"mediaUrl": null,
"read": true
},
{
"id": "msg-002",
"sender": "agent",
"senderName": "Soporte PixelTech",
"text": "Hola Juan, tu pedido está en camino. Número de guía: 1234567890",
"timestamp": "2026-03-05T10:05:00Z",
"mediaUrl": null,
"read": true
}
],
"lastMessage": {
"text": "Hola Juan, tu pedido está en camino...",
"timestamp": "2026-03-05T10:05:00Z",
"sender": "agent"
},
"createdAt": "2026-03-05T10:00:00Z",
"updatedAt": "2026-03-05T10:05:00Z",
"closedAt": null,
"unreadCount": 0,
"responseTime": 300,
"whatsappConversationId": "wa-conv-789",
"channel": "whatsapp"
}
Queries
Get Active Chats
const snapshot = await db.collection('chats')
.where('status', 'in', ['OPEN', 'PENDING'])
.orderBy('updatedAt', 'desc')
.get();
Get Chats by Agent
const snapshot = await db.collection('chats')
.where('assignedTo', '==', agentId)
.where('status', '!=', 'CLOSED')
.get();
Get Customer’s Chat History
const snapshot = await db.collection('chats')
.where('userId', '==', customerId)
.orderBy('createdAt', 'desc')
.get();
Get Unassigned Chats
const snapshot = await db.collection('chats')
.where('assignedTo', '==', null)
.where('status', '==', 'OPEN')
.orderBy('createdAt', 'asc')
.get();
Real-Time Listeners
Listen to Chat Updates
const unsubscribe = db.collection('chats').doc(chatId)
.onSnapshot((snapshot) => {
const chatData = snapshot.data();
renderMessages(chatData.messages);
updateUnreadCount(chatData.unreadCount);
});
Listen to New Messages
db.collection('chats').doc(chatId)
.onSnapshot((snapshot) => {
const newMessages = snapshot.data().messages;
const lastMsg = newMessages[newMessages.length - 1];
if (lastMsg.sender === 'customer') {
playNotificationSound();
showToast(`Nuevo mensaje de ${chatData.customerName}`);
}
});
Chat Operations
Create New Chat
const newChatRef = db.collection('chats').doc();
await newChatRef.set({
userId: auth.currentUser?.uid || null,
customerName: userName,
customerPhone: userPhone,
customerEmail: userEmail,
status: 'OPEN',
priority: 'medium',
subject: subject,
messages: [],
createdAt: admin.firestore.FieldValue.serverTimestamp(),
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
unreadCount: 0,
channel: 'web'
});
Send Message
const newMessage = {
id: `msg-${Date.now()}`,
sender: isAgent ? 'agent' : 'customer',
senderName: senderName,
text: messageText,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
mediaUrl: null,
read: false
};
await db.collection('chats').doc(chatId).update({
messages: admin.firestore.FieldValue.arrayUnion(newMessage),
lastMessage: newMessage,
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
unreadCount: admin.firestore.FieldValue.increment(1)
});
Assign to Agent
await db.collection('chats').doc(chatId).update({
assignedTo: agentId,
assignedAt: admin.firestore.FieldValue.serverTimestamp(),
status: 'OPEN'
});
Mark as Read
await db.collection('chats').doc(chatId).update({
unreadCount: 0,
'messages': messages.map(msg => ({ ...msg, read: true }))
});
Close Chat
await db.collection('chats').doc(chatId).update({
status: 'CLOSED',
closedAt: admin.firestore.FieldValue.serverTimestamp()
});
WhatsApp Integration
Fromfunctions/whatsapp.js, incoming WhatsApp messages are stored in the chats collection:
exports.webhook = async (req, res) => {
const message = req.body.message;
const from = message.from; // Phone number
const text = message.text.body;
// Find or create chat
let chatRef = await db.collection('chats')
.where('customerPhone', '==', from)
.where('status', '!=', 'CLOSED')
.limit(1)
.get();
if (chatRef.empty) {
// Create new chat
chatRef = db.collection('chats').doc();
await chatRef.set({
customerPhone: from,
status: 'OPEN',
channel: 'whatsapp',
messages: [],
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
}
// Add message
await chatRef.update({
messages: admin.firestore.FieldValue.arrayUnion({
id: message.id,
sender: 'customer',
text: text,
timestamp: admin.firestore.FieldValue.serverTimestamp()
})
});
};
Security Rules
match /chats/{chatId} {
// Customers can read their own chats
allow read: if request.auth != null &&
resource.data.userId == request.auth.uid;
// Agents can read/write assigned chats
allow read, write: if request.auth != null &&
resource.data.assignedTo == request.auth.uid;
// Admins can read/write all chats
allow read, write: if request.auth != null &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
Indexes
// Active chats by update time
chats: [status ASC, updatedAt DESC]
// Agent's chats
chats: [assignedTo ASC, status ASC]
// Customer history
chats: [userId ASC, createdAt DESC]
// Unassigned chats
chats: [assignedTo ASC, status ASC, createdAt ASC]