Apariencia
Chat de Servicio
Nuevo en v3
El módulo de Chat fue introducido en API v3 y no está disponible en versiones anteriores.
Descripción
El módulo de Chat permite la comunicación en tiempo real entre el cliente y el técnico durante la ejecución de un servicio. Se identifica cada conversación por el número de expediente del servicio.
Todos los endpoints de chat requieren validación de integridad HMAC-SHA256. Ver la sección de Autenticación para detalles.
Autenticación
Los endpoints de chat utilizan un esquema de validación de integridad en lugar de autenticación básica HTTP. Se deben incluir los siguientes headers en cada petición:
| Header | Requerido | Descripción |
|---|---|---|
X-Integrity-Timestamp | Sí | Timestamp Unix actual en segundos |
X-Integrity-Signature | Sí | Firma HMAC-SHA256 en formato hexadecimal |
X-Integrity-Algorithm | Sí | Debe ser HMAC-SHA256 |
X-Integrity-Version | Sí | Debe ser 1.0 |
Cómo generar la firma
La firma se genera así:
payload = "{METHOD}|{PATH}|{TIMESTAMP}|{BODY_JSON_ORDENADO}"
signature = HMAC-SHA256(payload, SECRET_KEY)javascript
const crypto = require('crypto');
function generateIntegrityHeaders(method, path, body, secretKey) {
const timestamp = Math.floor(Date.now() / 1000).toString();
// Ordenar el body alfabéticamente (recursivo)
const sortedBody = sortObjectRecursively(body || {});
const bodyString = JSON.stringify(sortedBody);
const payload = `${method}|${path}|${timestamp}|${bodyString}`;
const signature = crypto.createHmac('sha256', secretKey).update(payload).digest('hex');
return {
'X-Integrity-Timestamp': timestamp,
'X-Integrity-Signature': signature,
'X-Integrity-Algorithm': 'HMAC-SHA256',
'X-Integrity-Version': '1.0'
};
}
function sortObjectRecursively(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(sortObjectRecursively);
return Object.keys(obj).sort().reduce((sorted, key) => {
sorted[key] = sortObjectRecursively(obj[key]);
return sorted;
}, {});
}Clave secreta
La clave secreta para la validación de integridad es proporcionada por el equipo de TSALVA junto con las credenciales de acceso. Contacta a soporte@robpixels.com para obtenerla.
Validez del timestamp
El timestamp tiene una ventana de validez de 5 minutos. Peticiones con timestamps más antiguos serán rechazadas para prevenir ataques de replay.
Endpoints
Obtener Conversación
GET /api/v3/chat/:expedienteObtiene la conversación completa de un servicio con su historial inicial.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
expediente | string | Número de expediente del servicio |
Query params (opcionales)
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
page | number | 1 | Página del historial |
limit | number | 50 | Mensajes por página |
Respuesta exitosa (200 OK)
json
{
"success": true,
"conversation": {
"expediente": "EXP-2025-001234",
"status": "active",
"created_at": "2025-07-03T14:30:00.000Z"
},
"messages": [
{
"id": 1,
"sender_type": "technician",
"sender_id": "TEC001",
"sender_name": "Carlos Ramírez",
"message": "En camino al punto de servicio",
"created_at": "2025-07-03T14:35:00.000Z"
}
],
"total": 5,
"page": 1,
"limit": 50,
"hasMore": false,
"unreadCount": 2
}Historial de Mensajes
GET /api/v3/chat/:expediente/historyObtiene el historial paginado de mensajes de una conversación.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
expediente | string | Número de expediente del servicio |
Query params (opcionales)
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
page | number | 1 | Número de página |
limit | number | 50 | Mensajes por página |
Respuesta exitosa (200 OK)
json
{
"success": true,
"expediente": "EXP-2025-001234",
"messages": [
{
"id": 1,
"sender_type": "client",
"sender_id": "CLI001",
"sender_name": "María García",
"message": "¿Cuánto tiempo tarda en llegar?",
"created_at": "2025-07-03T14:32:00.000Z"
},
{
"id": 2,
"sender_type": "technician",
"sender_id": "TEC001",
"sender_name": "Carlos Ramírez",
"message": "Aproximadamente 12 minutos",
"created_at": "2025-07-03T14:33:00.000Z"
}
],
"total": 2,
"page": 1,
"limit": 50,
"hasMore": false
}Enviar Mensaje
POST /api/v3/chat/:expediente/messageEnvía un mensaje a la conversación del servicio (endpoint REST como alternativa al WebSocket).
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
expediente | string | Número de expediente del servicio |
Body (JSON)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
message | string | Sí | Texto del mensaje a enviar |
Ejemplo de Request
json
{
"message": "Ya estoy llegando al punto de servicio"
}Respuesta exitosa (201 Created)
json
{
"success": true,
"message": "Mensaje enviado exitosamente",
"data": {
"id": 15,
"expediente": "EXP-2025-001234",
"sender_type": "technician",
"sender_id": "TEC001",
"sender_name": "Carlos Ramírez",
"message": "Ya estoy llegando al punto de servicio",
"created_at": "2025-07-03T14:40:00.000Z"
}
}Marcar Mensajes como Leídos
PUT /api/v3/chat/:expediente/readMarca todos los mensajes no leídos de la conversación como leídos para el tipo de usuario autenticado.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
expediente | string | Número de expediente del servicio |
Respuesta exitosa (200 OK)
json
{
"success": true,
"message": "Mensajes marcados como leídos",
"markedCount": 3
}Contador de No Leídos
GET /api/v3/chat/:expediente/unreadObtiene el número de mensajes no leídos para el usuario autenticado.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
expediente | string | Número de expediente del servicio |
Respuesta exitosa (200 OK)
json
{
"success": true,
"expediente": "EXP-2025-001234",
"unreadCount": 4
}Estadísticas del Chat
GET /api/v3/chat/:expediente/statsObtiene estadísticas de actividad de la conversación de un servicio.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
expediente | string | Número de expediente del servicio |
Respuesta exitosa (200 OK)
json
{
"success": true,
"stats": {
"totalMessages": 18,
"clientMessages": 8,
"technicianMessages": 10,
"firstMessageAt": "2025-07-03T14:30:00.000Z",
"lastMessageAt": "2025-07-03T15:20:00.000Z"
}
}Respuestas de Error Comunes
❌ Headers de integridad ausentes (401)
json
{
"error": "Missing integrity headers",
"message": "Se requieren headers de integridad",
"required_headers": [
"X-Integrity-Timestamp",
"X-Integrity-Signature",
"X-Integrity-Algorithm",
"X-Integrity-Version"
]
}❌ Firma inválida (401)
json
{
"error": "Invalid signature",
"message": "No se pudo validar la integridad de la petición",
"hint": "Verifica que la clave secreta sea correcta"
}❌ Timestamp vencido (401)
json
{
"error": "Invalid timestamp",
"message": "Timestamp inválido o muy antiguo",
"max_age_seconds": 300
}❌ Expediente no encontrado (404)
json
{
"success": false,
"message": "Conversación no encontrada"
}❌ Expediente requerido (400)
json
{
"success": false,
"message": "Expediente requerido"
}Ejemplo de Integración Completa
javascript
const crypto = require('crypto');
const SECRET_KEY = 'tu_clave_secreta_proporcionada_por_tsalva';
const API_URL = '[URL_API]';
function sortObjectRecursively(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(sortObjectRecursively);
return Object.keys(obj).sort().reduce((sorted, key) => {
sorted[key] = sortObjectRecursively(obj[key]);
return sorted;
}, {});
}
function getIntegrityHeaders(method, path, body = {}) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const sortedBody = sortObjectRecursively(body);
const payload = `${method}|${path}|${timestamp}|${JSON.stringify(sortedBody)}`;
const signature = crypto.createHmac('sha256', SECRET_KEY).update(payload).digest('hex');
return {
'X-Integrity-Timestamp': timestamp,
'X-Integrity-Signature': signature,
'X-Integrity-Algorithm': 'HMAC-SHA256',
'X-Integrity-Version': '1.0',
'Content-Type': 'application/json'
};
}
// Enviar un mensaje
const expediente = 'EXP-2025-001234';
const path = `/${expediente}/message`;
const body = { message: 'Mensaje de prueba' };
const response = await fetch(`${API_URL}/api/v3/chat${path}`, {
method: 'POST',
headers: getIntegrityHeaders('POST', path, body),
body: JSON.stringify(body)
});
const result = await response.json();
console.log(result);WebSocket
Los endpoints REST del chat son un fallback al sistema de mensajería en tiempo real via WebSocket. Para experiencias de usuario óptimas, el sistema TSALVA utiliza conexiones WebSocket (namespace /v3) para entregar mensajes instantáneamente. Consulta al equipo técnico de RobPixels para integrar WebSocket en tu aplicación.