Documentación

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.

EMPIECE AQUÍ

Para quien administra

Active el plan, invite a su equipo, fije su prefijo de casos y controle la facturación.

Guía de inicio →
SU SITIO WEB

Para quien publica la web

Tres widgets embebibles con una línea de script: contacto, apertura de casos y tablero del cliente final.

Widgets →
INTEGRACIÓN

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.

Sus clientes no necesitan cuenta con nosotros. Hablan con su marca: el widget, su web y su correo. Quien tiene la cuenta (y el control) es usted.

Guía de inicio

De cero a operar, en cinco pasos.

  1. Cree su cuenta en dash.elportaldelcliente.com.
  2. 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.
  3. Invite a sus agentes en la pestaña «Agentes»: correo y rol (agente o administrador). Recibirá un enlace de acceso para entregarles.
  4. 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.
  5. ¿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.
Su prefijo de casos: en «Widgets y API» puede fijar 4 letras propias (por ejemplo 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ñaQué hace
ResumenEstado de su suscripción, vencimiento, renovación y auto-renovación. Aquí también se cancela.
AgentesInvitar por correo con rol de agente o administrador; cambiar roles; quitar acceso. El titular queda protegido.
Widgets y APIPrefijo de casos, orígenes autorizados (CORS), los tres widgets con su script, y las credenciales de la API.
Accesos del proveedorLa 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-widgetQué haceRequiere
contactoFormulario de contacto: nombre, correo y mensaje. Crea un caso en su tablero.Widget encendido + origen autorizado
abrir-casoApertura de casos con asunto: devuelve el número de caso al visitante.Widget encendido + origen autorizado
tableroTablero 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);
El 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:

ScopePermite
casos.readLeer casos y sus respuestas
casos.writeCrear casos, responder y cambiar estado/prioridad
clientes.readLeer sus clientes finales
clientes.writeCrear/desactivar clientes finales y emitir tokens de widget
departamentos.manageLeer y administrar departamentos (incluye buzones IMAP)
webhooks.manageLeer y administrar webhooks
accesos.readLeer 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_id en 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.
Server-side únicamente. La credencial 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étodoRutaScopeQué hace
GET/clientesclientes.readListar clientes (hasta 500, más recientes primero)
POST/clientesclientes.writeCrear o actualizar por external_ref (upsert idempotente)
GET/clientes/:refclientes.readLeer un cliente por su external_ref
DELETE/clientes/:refclientes.writeDesactivar (no borra: conserva su historial de casos)
POST/clientes/:ref/token-widgetclientes.writeEmitir 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"}'
Reintentos seguros: 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étodoRutaScopeQué hace
GET/casoscasos.readListar con filtros: estado, cliente, updated_since, limit (1-100), page
POST/casoscasos.writeCrear un caso (subject requerido; cliente_ref, priority, department_id, body/body_html opcionales)
GET/casos/:idcasos.readDetalle con la conversación completa (respuestas ordenadas). Acepta el ID o el número de caso (p. ej. SOLC-1051) indistintamente
POST/casos/:id/respuestascasos.writeResponder como su equipo, o como el cliente con en_nombre_de
PATCH/casos/:idcasos.writeCambiar 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"
Un filtro que no filtra es peor que un error: un 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"}'
CampoValores
statusopen · pending · resolved · closed
prioritylow · medium · high · urgent
author_typeclient · agent
Aislamiento verificable: toda consulta de casos pasa por el alcance de su inquilino en el servidor. Un ID de caso de otro tablero responde 404 — la API se comporta como si no existiera, porque para usted no existe.

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étodoRutaScopeQué hace
GET/departamentosdepartamentos.manageListar departamentos (incluye la configuración de correo, sin la contraseña)
POST/departamentosdepartamentos.manageCrear (name requerido; buzón IMAP opcional; sujeto al límite del plan)
PUT/departamentos/:iddepartamentos.manageEditar cualquier campo; email_password vacío borra la credencial
DELETE/departamentos/:iddepartamentos.manageArchivar (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.

EventoSe dispara cuando
caso.creadoNace un caso en su tablero — por API, por widget o por correo entrante
caso.respondidoAlguien responde: su equipo (desde el panel o la API), o su cliente final (widget, API o correo)
caso.estado_cambiadoCambia 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 /webhooks muestra fail_count, last_ok_at y last_error_at de 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":"…"}
HTTPSignificado
400Petición inválida: falta un campo requerido o un valor no pasa la validación
401Credencial ausente, inválida o revocada
402Suscripción vencida: solo lectura hasta renovar (suscripcion_vencida)
403La credencial no tiene el scope que exige el endpoint
404No existe para usted: recursos ajenos y rutas inexistentes responden igual
409Conflicto: límite del plan alcanzado o slug ya en uso
429Demasiados intentos (widgets anónimos: 10 por IP cada 10 minutos)
500Error 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 estableSignificado
suscripcion_vencida402 en escrituras con la suscripción vencida
token_vencido401 del widget «tablero» cuando el token tabw_ expiró: canjee otro
saldo_insuficiente402 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.

PlanAgentesDepartamentosArtículos de KB
ZERO2225
BASE55100
PLUS1010250
PRO2015500
MAX50251000
Sin prueba gratuita, a propósito. El plan ZERO existe para empezar chico con compromiso real. La activación debita el año completo del saldo de su cuenta.

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 limit de 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/:ref para 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.