El manual de su tablero
Una página, tres carriles: empiece aquí, pase el segundo a quien atiende los casos, y mande el tercero a quien escribe el código.
Para quien administra
Active el plan, invite a su equipo, fije su prefijo de casos y controle la facturación.
Guía de inicio →Para quien publica la web
Tres widgets embebibles con una línea de script: contacto, apertura de casos y tablero del cliente final.
Widgets →Para quien integra
API REST con credenciales propias, webhooks firmados y sincronización incremental.
Referencia de la API →Qué es el Tablero
Un tablero de soporte multi-marca: usted atiende a SUS clientes, bajo SU marca, sobre nuestra infraestructura.
El Tablero de Soporte es una cola única de casos para su operación: sus clientes abren casos desde su web (widgets), por API o por correo, y su equipo los atiende desde un panel propio en dash.elportaldelcliente.com. Cada caso entra, avanza y escala sin que nadie esté despierto — pero no concluye sin un nombre detrás.
Su tablero es un inquilino aislado: sus casos, sus clientes finales y sus agentes no se mezclan con los de nadie más. El aislamiento es parte del diseño del sistema, no una configuración que se pueda olvidar. Si nuestro staff toca un caso suyo como soporte de último nivel, el acceso queda registrado y usted puede consultarlo.
Guía de inicio
De cero a operar, en cinco pasos.
- Cree su cuenta en dash.elportaldelcliente.com.
- Active un plan. Se paga con el saldo de su cuenta (el mismo monedero de todo el ecosistema); si le falta, la pantalla le ofrece recargar exactamente lo que falta.
- Invite a sus agentes en la pestaña «Agentes»: correo y rol (agente o administrador). Recibirá un enlace de acceso para entregarles.
- Encienda un widget en «Widgets y API»: registre el dominio de su web como origen autorizado, copie el script y péguelo en su sitio.
- ¿Integra por código? Emita su credencial
tab_live_en la misma pestaña y siga la referencia de la API de esta página.
ACME) y sus casos se numeran ACME-1044. Los casos ya emitidos no se renumeran.El panel del tablero
El panel del inquilino en dash.elportaldelcliente.com, pestaña por pestaña.
| Pestaña | Qué hace |
|---|---|
Resumen | Estado de su suscripción, vencimiento, renovación y auto-renovación. Aquí también se cancela. |
Agentes | Invitar por correo con rol de agente o administrador; cambiar roles; quitar acceso. El titular queda protegido. |
Widgets y API | Prefijo de casos, orígenes autorizados (CORS), los tres widgets con su script, y las credenciales de la API. |
Accesos del proveedor | La auditoría: cada acceso de nuestro staff a casos suyos, con quién, qué y cuándo. |
Los agentes que usted invita ven y atienden los casos de su tablero; los administradores además gestionan la suscripción, el equipo y las integraciones.
Widgets para su web
Tres widgets, una línea de script cada uno. Sin iframes de terceros, sin cookies de rastreo.
En la pestaña «Widgets y API» de su panel: encienda el widget, registre el dominio de su sitio como origen autorizado y copie el script generado. El parámetro data-color acepta su color de marca.
<script src="https://api.elportaldelcliente.com/api/tablero/widget/tablero.js"
data-tenant="su-tablero" data-widget="contacto" data-color="#a177ff"></script>
| data-widget | Qué hace | Requiere |
|---|---|---|
contacto | Formulario de contacto: nombre, correo y mensaje. Crea un caso en su tablero. | Widget encendido + origen autorizado |
abrir-caso | Apertura de casos con asunto: devuelve el número de caso al visitante. | Widget encendido + origen autorizado |
tablero | Tablero del cliente final: ve sus casos, los abre y responde desde su web. | Lo anterior + token de cliente (abajo) |
Protecciones del widget
Los widgets anónimos aceptan 10 intentos por visitante (IP) cada 10 minutos por tablero. Si el widget está apagado, el origen no está autorizado o la suscripción venció, el widget responde 404 widget no disponible — a propósito indistinguible, para no revelar la configuración de su tablero.
El widget «tablero»: identificar a su cliente
El widget de tablero muestra a cada cliente final solo sus propios casos. Para eso, su servidor canjea un token efímero por cliente: llame a POST /v1/clientes/:ref/token-widget con su credencial de API (server-side, nunca desde el navegador) y entregue el token al widget.
curl -X POST https://api.elportaldelcliente.com/api/tablero/v1/clientes/cli-842/token-widget \
-H "Authorization: Bearer $TABLERO_KEY"
HTTP/1.1 201
{"success":true,"data":{"token":"tabw_…","expires_at":"2026-08-18 21:15:00"}}
window.TableroPortal.setToken(token);
tabw_ vive 15 minutos y no se refresca. Cuando venza, el widget recibirá 401 token_vencido: canjee otro. Nunca exponga su credencial tab_live_ en el navegador — el token efímero existe exactamente para eso.Credenciales de la API
Una credencial Bearer por integración, con alcance acotado y revocable.
Las credenciales se emiten en su panel («Widgets y API» → «Credenciales de la API»). Tienen la forma tab_live_… y se muestran una sola vez: guárdela en su gestor de secretos. Nosotros solo almacenamos su hash.
curl -H "Authorization: Bearer tab_live_…" \
https://api.elportaldelcliente.com/api/tablero/v1/
Al emitirla puede acotar sus scopes. Una credencial sin scopes explícitos tiene acceso a todo su tablero:
| Scope | Permite |
|---|---|
casos.read | Leer casos y sus respuestas |
casos.write | Crear casos, responder y cambiar estado/prioridad |
clientes.read | Leer sus clientes finales |
clientes.write | Crear/desactivar clientes finales y emitir tokens de widget |
departamentos.manage | Leer y administrar departamentos (incluye buzones IMAP) |
webhooks.manage | Leer y administrar webhooks |
accesos.read | Leer la auditoría de accesos del proveedor |
- Máximo 5 credenciales activas por tablero. Revoque las que ya no use.
- La credencial identifica a su tablero: nunca envíe
tenant_iden el cuerpo — la API lo rechaza con 400. - Con la suscripción vencida la credencial sigue leyendo (GET); toda escritura responde
402 suscripcion_vencida. - Una credencial revocada responde 401 de inmediato en todas las rutas.
tab_live_ jamás va en el navegador, en una app móvil ni en un repositorio. Para el navegador existe el token efímero tabw_.Referencia de la API
REST sobre HTTPS, JSON en ambas direcciones, respuestas con la forma {success, data}.
Convenciones: los cuerpos van en Content-Type: application/json; las fechas son UTC YYYY-MM-DD HH:MM:SS; los listados paginados devuelven total, page y limit. Lo que no es suyo responde 404, nunca 403: la API no confirma la existencia de recursos ajenos.
https://api.elportaldelcliente.com/api/tablero/v1
Raíz: identidad, plan y uso
GET / es el contrato que no puede mentir: devuelve quién es usted, qué scopes tiene esta credencial, su plan con límites, el estado de su suscripción y cuánto lleva usado.
curl -H "Authorization: Bearer $TABLERO_KEY" \
https://api.elportaldelcliente.com/api/tablero/v1/
{"success":true,"data":{
"tenant":{"slug":"su-tablero","nombre":"Su Marca"},
"scopes_de_esta_credencial":["casos.read","casos.write"],
"plan":{"id":"tablero-base","nombre":"BASE","limites":{"max_agentes":5,"max_departamentos":5,"max_articulos_kb":100}},
"suscripcion":{"estado":"active","current_period_end":"2027-08-18"},
"uso":{"agentes":3,"departamentos":2},
"recursos":["/casos","/clientes","/departamentos","/accesos","/webhooks"],
"version":"v1"}}
Clientes finales
Un cliente final es un cliente suyo (no una cuenta de nuestro ecosistema). Se identifica por external_ref: el ID que ese cliente tiene en SU sistema.
| Método | Ruta | Scope | Qué hace |
|---|---|---|---|
| GET | /clientes | clientes.read | Listar clientes (hasta 500, más recientes primero) |
| POST | /clientes | clientes.write | Crear o actualizar por external_ref (upsert idempotente) |
| GET | /clientes/:ref | clientes.read | Leer un cliente por su external_ref |
| DELETE | /clientes/:ref | clientes.write | Desactivar (no borra: conserva su historial de casos) |
| POST | /clientes/:ref/token-widget | clientes.write | Emitir token efímero para el widget «tablero» (15 min) |
curl -X POST https://api.elportaldelcliente.com/api/tablero/v1/clientes \
-H "Authorization: Bearer $TABLERO_KEY" \
-H "Content-Type: application/json" \
-d '{"external_ref":"cli-842","email":"[email protected]","name":"Maria Rojas"}'
POST /clientes es un upsert por external_ref — repetir la llamada no duplica clientes. Al crear casos, en cambio, un reintento de red crea un caso nuevo: reintente solo cuando no recibió respuesta.Casos
El recurso central. Un caso tiene estado, prioridad, un cliente final opcional y una conversación de respuestas. Las notas internas de los agentes jamás se exponen por la API.
| Método | Ruta | Scope | Qué hace |
|---|---|---|---|
| GET | /casos | casos.read | Listar con filtros: estado, cliente, updated_since, limit (1-100), page |
| POST | /casos | casos.write | Crear un caso (subject requerido; cliente_ref, priority, department_id, body/body_html opcionales) |
| GET | /casos/:id | casos.read | Detalle con la conversación completa (respuestas ordenadas). Acepta el ID o el número de caso (p. ej. SOLC-1051) indistintamente |
| POST | /casos/:id/respuestas | casos.write | Responder como su equipo, o como el cliente con en_nombre_de |
| PATCH | /casos/:id | casos.write | Cambiar status y/o priority |
Crear un caso ligado a un cliente final:
curl -X POST https://api.elportaldelcliente.com/api/tablero/v1/casos \
-H "Authorization: Bearer $TABLERO_KEY" \
-H "Content-Type: application/json" \
-d '{"subject":"No puedo entrar al panel","cliente_ref":"cli-842","priority":"high",
"body":"Al iniciar sesion la pagina queda en blanco."}'
HTTP/1.1 201
{"success":true,"data":{"id":"…","numero":"SOLC-1051"}}
Listar con filtros, y sincronización incremental con updated_since (ISO 8601): en ese modo el orden es updated_at ascendente, pensado para avanzar página a página sin perder cambios. Guarde el updated_at más alto que procesó y úselo como cursor de la siguiente pasada.
curl -H "Authorization: Bearer $TABLERO_KEY" \
"https://api.elportaldelcliente.com/api/tablero/v1/casos?estado=open&cliente=cli-842&limit=50&page=1"
# sincronizacion incremental (orden: updated_at ascendente)
curl -H "Authorization: Bearer $TABLERO_KEY" \
"https://api.elportaldelcliente.com/api/tablero/v1/casos?updated_since=2026-08-18T00:00:00Z&limit=100"
estado fuera de la lista válida (o cualquier parámetro que no reconozcamos) responde 400 con el detalle — nunca se ignora en silencio ni devuelve la lista completa sin avisar.Responder un caso. Sin en_nombre_de la respuesta firma como su equipo (author_type: agent); con en_nombre_de firma como ese cliente final (author_type: client):
curl -X POST https://api.elportaldelcliente.com/api/tablero/v1/casos/$CASO_ID/respuestas \
-H "Authorization: Bearer $TABLERO_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"Ya lo revisamos: intente de nuevo.","autor_nombre":"Soporte Acme"}'
# ...o en nombre del cliente final:
-d '{"body":"Sigue igual desde mi lado.","en_nombre_de":"cli-842"}'
| Campo | Valores |
|---|---|
status | open · pending · resolved · closed |
priority | low · medium · high · urgent |
author_type | client · agent |
Departamentos
Colas internas de su tablero (ventas, facturación, técnico…). Cada una puede tener su propio buzón IMAP: el tablero lo sondea y convierte los correos entrantes en casos.
| Método | Ruta | Scope | Qué hace |
|---|---|---|---|
| GET | /departamentos | departamentos.manage | Listar departamentos (incluye la configuración de correo, sin la contraseña) |
| POST | /departamentos | departamentos.manage | Crear (name requerido; buzón IMAP opcional; sujeto al límite del plan) |
| PUT | /departamentos/:id | departamentos.manage | Editar cualquier campo; email_password vacío borra la credencial |
| DELETE | /departamentos/:id | departamentos.manage | Archivar (los casos existentes no se tocan) |
El buzón habla IMAP siempre (email_protocol: imap); la contraseña se cifra en reposo y nunca se devuelve. Con email_polling_enabled: 1 el sondeo corre automáticamente.
Auditoría de accesos
Cada acceso de nuestro staff a casos de su tablero queda registrado. Esta es la misma auditoría de la pestaña «Accesos del proveedor», legible por API:
curl -H "Authorization: Bearer $TABLERO_KEY" \
https://api.elportaldelcliente.com/api/tablero/v1/accesos
Webhooks
Su sistema se entera en el momento: cada evento va firmado a su URL HTTPS.
| Evento | Se dispara cuando |
|---|---|
caso.creado | Nace un caso en su tablero — por API, por widget o por correo entrante |
caso.respondido | Alguien responde: su equipo (desde el panel o la API), o su cliente final (widget, API o correo) |
caso.estado_cambiado | Cambia el estado de un caso (por su equipo, por la API o por nuestro sistema) |
Registre hasta donde necesite con POST /webhooks: URL https, los eventos que quiere y un secreto de al menos 16 caracteres con el que firmaremos cada entrega.
curl -X POST https://api.elportaldelcliente.com/api/tablero/v1/webhooks \
-H "Authorization: Bearer $TABLERO_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://su-sistema.com/hooks/tablero",
"eventos":["caso.creado","caso.respondido","caso.estado_cambiado"],
"secret":"un-secreto-de-al-menos-16-caracteres"}'
Cada entrega es un POST con el evento, el momento de emisión y los datos mínimos para reaccionar. La firma cubre timestamp.cuerpo:
POST /hooks/tablero
Content-Type: application/json
X-Tablero-Evento: caso.respondido
X-Tablero-Timestamp: 1765432100
X-Tablero-Firma: 3f1a… # HMAC-SHA256(secret, timestamp + "." + cuerpo)
{"evento":"caso.respondido","emitido_at":"2026-08-18T20:15:00.000Z",
"data":{"caso_id":"…","numero":"SOLC-1051","respuesta_id":"…","autor_tipo":"agent"}}
Verificar la firma
Rechace toda entrega cuya firma no verifique o cuyo timestamp esté a más de 5 minutos del reloj. Compare en tiempo constante.
// Node.js
const crypto = require('node:crypto');
function verificarFirma(rawBody, headers, secret) {
const ts = headers['x-tablero-timestamp'];
const sig = headers['x-tablero-firma'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const esperado = crypto.createHmac('sha256', secret)
.update(ts + '.' + rawBody).digest('hex');
return sig.length === esperado.length &&
crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(sig));
}
# Python
import hashlib, hmac, time
def verificar_firma(raw_body: bytes, headers: dict, secret: str) -> bool:
ts = headers.get("x-tablero-timestamp", "")
sig = headers.get("x-tablero-firma", "")
if abs(time.time() - float(ts or 0)) > 300:
return False
esperado = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, sig)
Semántica de entrega (léala antes de confiar)
- Un intento por evento, sin reintentos. Si su endpoint no responde 2xx en 10 segundos, esa entrega se pierde — el estado real siempre es re-leíble por la API.
- Su respaldo es la sincronización incremental: una pasada periódica de
GET /casos?updated_since=…recupera cualquier evento perdido. GET /webhooksmuestrafail_count,last_ok_atylast_error_atde cada webhook: revíselos al depurar.- Los eventos se emiten sin importar la vía: API, widgets, el panel de su equipo, nuestro staff o el correo entrante de su cliente final.
Errores
Errores JSON con success:false; el código HTTP manda.
{"success":false,"error":"…"}
| HTTP | Significado |
|---|---|
400 | Petición inválida: falta un campo requerido o un valor no pasa la validación |
401 | Credencial ausente, inválida o revocada |
402 | Suscripción vencida: solo lectura hasta renovar (suscripcion_vencida) |
403 | La credencial no tiene el scope que exige el endpoint |
404 | No existe para usted: recursos ajenos y rutas inexistentes responden igual |
409 | Conflicto: límite del plan alcanzado o slug ya en uso |
429 | Demasiados intentos (widgets anónimos: 10 por IP cada 10 minutos) |
500 | Error nuestro; si persiste, escríbanos con la hora exacta |
Los mensajes de error son texto legible para humanos y pueden cambiar. Ramifique su código por el código HTTP y por estos slugs estables:
| Slug estable | Significado |
|---|---|
suscripcion_vencida | 402 en escrituras con la suscripción vencida |
token_vencido | 401 del widget «tablero» cuando el token tabw_ expiró: canjee otro |
saldo_insuficiente | 402 del panel al activar/renovar sin saldo suficiente |
Planes
Cinco planes anuales, el mismo tablero. La diferencia es cuánta gente y estructura caben.
Los precios vigentes se publican en la página principal y por API pública (GET /api/public/tablero/plans). Todos los planes incluyen casos ilimitados, widgets, API, webhooks y auditoría de accesos.
| Plan | Agentes | Departamentos | Artículos de KB |
|---|---|---|---|
| ZERO | 2 | 2 | 25 |
| BASE | 5 | 5 | 100 |
| PLUS | 10 | 10 | 250 |
| PRO | 20 | 15 | 500 |
| MAX | 50 | 25 | 1000 |
Al vencer su suscripción hay un período de gracia; después el tablero pasa a solo lectura (los GET siguen respondiendo, las escrituras devuelven 402). Sus datos no se borran.
Límites operativos
- Casos: ilimitados en todos los planes. Los listados aceptan
limitde 1 a 100 por página. - Clientes finales y auditoría: los listados devuelven hasta 500 filas (los más recientes primero). Use
GET /clientes/:refpara lecturas puntuales. - Credenciales: hasta 5 activas por tablero.
- Widgets anónimos: 10 intentos por visitante (IP) cada 10 minutos.
- Adjuntos: la API no acepta ficheros todavía; los adjuntos de correo entrante se conservan con el caso.
La troncal no impone hoy una cuota rígida de llamadas; opere con un ritmo razonable (pausas entre páginas, reintentos con espera exponencial). Si una integración necesita volumen garantizado, hablemos antes.