Oleada UX 7: Chat (presencia, busqueda en historial) y WACRM (metricas para gerencia, notas internas y etiquetas que sobreviven al sync) + fixes de concurrencia

This commit is contained in:
2026-09-17 23:46:40 +00:00
parent 31c92ac419
commit c38783eb31
13 changed files with 1589 additions and 36 deletions

View File

@@ -626,6 +626,43 @@ export interface WacrmConversation {
last_message_at: string | null;
unread_count: number;
created_at: string | null;
tags?: { id: number; name: string; color: string }[];
}
export interface WacrmTag {
id: number;
name: string;
color: string;
sequence: number;
active: boolean;
}
export interface WacrmNote {
id: number;
body: string;
author: string;
date: string | null;
resolved: boolean;
resolved_at: string | null;
resolved_by: string;
}
export interface WacrmMetrics {
status: string;
generado: string;
periodo: { desde: string; hasta: string };
sin_respuesta_horas: number;
conversaciones: { hoy: number; semana: number; mes: number; total: number };
mensajes: {
hoy: { entrantes: number; salientes: number };
semana: { entrantes: number; salientes: number };
mes: { entrantes: number; salientes: number };
};
primera_respuesta: { muestras: number; promedio_min: number | null; mediana_min: number | null };
sin_responder: { total: number; horas_minimas: number; detalle: { conversation_id: string; horas: number; ultimo_mensaje: string }[] };
leads: { ganados: number; perdidos: number; abiertos: number; valor_ganado: number };
criterios: Record<string, string>;
message?: string;
}
export interface WacrmMessage {
@@ -713,6 +750,8 @@ export interface ChatUser {
last_message: string | null;
last_date: string | null;
last_mine: boolean | null;
online: boolean;
last_seen: string | null;
}
export interface ChatMessage {
@@ -1180,8 +1219,24 @@ export const odooApi = {
return data;
},
async getChatMessages(userId: number, sinceId?: number): Promise<{ status: string; messages: ChatMessage[] }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/chat/messages`, { user_id: userId, since_id: sinceId || undefined });
async getChatMessages(userId: number, sinceId?: number, aroundId?: number): Promise<{ status: string; messages: ChatMessage[]; around_id?: number | null }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/chat/messages`, {
user_id: userId,
since_id: sinceId || undefined,
around_id: aroundId || undefined,
});
return data;
},
/** Búsqueda en el historial con otro usuario (servidor). */
async searchChatMessages(userId: number, q: string, limit = 50): Promise<{ status: string; query: string; count: number; results: ChatMessage[] }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/chat/search`, { user_id: userId, q, limit });
return data;
},
/** Latido de presencia (se manda cada ~30 s con la app abierta). */
async chatHeartbeat(): Promise<{ status: string; online_count: number; online_window_seconds: number }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/chat/heartbeat`);
return data;
},
@@ -1860,6 +1915,59 @@ export const odooApi = {
return data;
},
// ---- Notas internas ----
async getWacrmNotes(conversationId: string): Promise<{ status: string; notes: WacrmNote[]; pendientes: number }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/conversations/${conversationId}/notes`);
return data;
},
async createWacrmNote(conversationId: string, body: string): Promise<{ status: string; note: WacrmNote }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/conversations/${conversationId}/notes`, { body });
return data;
},
async updateWacrmNote(noteId: number, payload: { body?: string; resolved?: boolean }): Promise<{ status: string; note: WacrmNote }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/wacrm/notes/${noteId}`, payload);
return data;
},
async deleteWacrmNote(noteId: number): Promise<{ status: string; message: string }> {
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/wacrm/notes/${noteId}`);
return data;
},
// ---- Etiquetas ----
async getWacrmTags(): Promise<{ status: string; tags: WacrmTag[] }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/tags`);
return data;
},
async createWacrmTag(payload: { name: string; color?: string; sequence?: number }): Promise<{ status: string; tag: WacrmTag }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/tags`, payload);
return data;
},
async updateWacrmTag(tagId: number, payload: Partial<WacrmTag>): Promise<{ status: string; tag: WacrmTag }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/wacrm/tags/${tagId}`, payload);
return data;
},
async deleteWacrmTag(tagId: number): Promise<{ status: string; message: string }> {
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/wacrm/tags/${tagId}`);
return data;
},
async assignWacrmTag(conversationId: string, tagId: number, action: 'add' | 'remove' = 'add'): Promise<{ status: string; tags: { id: number; name: string; color: string }[] }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/conversations/${conversationId}/tags`, { tag_id: tagId, action });
return data;
},
// ---- Métricas para gerencia ----
async getWacrmMetrics(params?: { sin_respuesta_horas?: number }): Promise<WacrmMetrics> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/metrics`, { params });
return data;
},
async sendWacrmMessage(payload: { conversation_id: string; text: string; assigned_agent_id: string }): Promise<{ status: string; message: string }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/messages`, payload);
return data;