Skip to content

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:

HeaderRequeridoDescripción
X-Integrity-TimestampTimestamp Unix actual en segundos
X-Integrity-SignatureFirma HMAC-SHA256 en formato hexadecimal
X-Integrity-AlgorithmDebe ser HMAC-SHA256
X-Integrity-VersionDebe 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/:expediente

Obtiene la conversación completa de un servicio con su historial inicial.

Parámetros de ruta

ParámetroTipoDescripción
expedientestringNúmero de expediente del servicio

Query params (opcionales)

ParámetroTipoDefaultDescripción
pagenumber1Página del historial
limitnumber50Mensajes 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/history

Obtiene el historial paginado de mensajes de una conversación.

Parámetros de ruta

ParámetroTipoDescripción
expedientestringNúmero de expediente del servicio

Query params (opcionales)

ParámetroTipoDefaultDescripción
pagenumber1Número de página
limitnumber50Mensajes 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/message

Envía un mensaje a la conversación del servicio (endpoint REST como alternativa al WebSocket).

Parámetros de ruta

ParámetroTipoDescripción
expedientestringNúmero de expediente del servicio

Body (JSON)

CampoTipoRequeridoDescripción
messagestringTexto 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/read

Marca todos los mensajes no leídos de la conversación como leídos para el tipo de usuario autenticado.

Parámetros de ruta

ParámetroTipoDescripción
expedientestringNú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/unread

Obtiene el número de mensajes no leídos para el usuario autenticado.

Parámetros de ruta

ParámetroTipoDescripción
expedientestringNú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/stats

Obtiene estadísticas de actividad de la conversación de un servicio.

Parámetros de ruta

ParámetroTipoDescripción
expedientestringNú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.

Tsalva API - Documentación desarrollada por RobPixels