Lex Intellecta · SaaS Zero-Knowledge
Integración técnica · 30 minutos · 3 piezas de código

Integra el SaaS en tu plataforma —
solo copiar y pegar

Tu equipo técnico no necesita escribir código nuevo. Pega estos 3 snippets en su lugar correspondiente y la integración queda lista para producción. Personaliza con tu API key abajo y los snippets se ajustan automáticamente.

Personaliza tu integración

Los snippets de abajo se actualizan en tiempo real con los valores que ingreses aquí. Si todavía no tienes API key, usa los valores de sandbox y pruébalo contra nuestra cooperativa demo.

Sandbox por defecto — útil para probar inmediatamente.

Endpoint donde recibirás los resultados (HMAC firmado).

1.Botón "Diagnóstico Legal" en tu oficina virtual

Pega este HTML+JS en cualquier página de tu portal de afiliados. El botón llama a tu propio backend (que tiene la API key segura), genera el token, y abre la pantalla de Lex Intellecta en una nueva pestaña.

Frontend (HTML + JavaScript)

HTML · JavaScript · cliente
<!-- Pega esto donde quieras el botón "Diagnóstico Legal" -->
<button id="btn-asesoria-legal" class="tu-estilo-de-boton">
  ⚖️ Diagnóstico legal
</button>

<script>
document.getElementById("btn-asesoria-legal").addEventListener("click", async () => {
  // 1. Pedir token a TU PROPIO backend (donde guardas la API key)
  // NUNCA expongas la API key en el cliente.
  const r = await fetch("/api/asesoria-legal/iniciar", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    // Tu sistema sabe quién es el afiliado por su sesión actual.
    // El asociado_id se asocia al token en tu BD, no se manda a Lex.
    body: JSON.stringify({ asociado_id: "el_id_interno_del_afiliado" })
  });
  const { embed_url } = await r.json();

  // 2. Abrir la pantalla de Lex Intellecta con el token
  window.open(embed_url, "_blank", "width=420,height=720");
});
</script>

2.Endpoint de tu backend que pide el token

Esta es la pieza que mantiene tu API key segura del lado servidor. Tu sistema asocia el token recién emitido al asociado real en tu BD interna y devuelve solo el embed_url al frontend.

Node.js / Express

Node.js · Express · servidor
// En tu app Express (o equivalente en cualquier framework Node)
const express = require("express");
const router = express.Router();

const LEX_API_URL = "https://lexintellecta.com/zk/api/v1";
const LEX_API_KEY = process.env.LEX_API_KEY;  // Tu API key (env var, NUNCA hardcoded)

router.post("/asesoria-legal/iniciar", async (req, res) => {
  const { asociado_id } = req.body;
  // Verifica que el usuario esté autenticado en tu sesión
  if (!req.session.user || req.session.user.id !== asociado_id) {
    return res.status(403).json({ error: "unauthorized" });
  }

  // 1. Pedir token a Lex Intellecta
  const lex = await fetch(`${LEX_API_URL}/token/generate`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${LEX_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      scope: "multi",           // n1 | n2 | docs | review | multi
      metadata: { categoria: req.session.user.categoria }  // SIN PII
    })
  });
  const data = await lex.json();
  if (!data.ok) return res.status(502).json({ error: "lex_error" });

  // 2. Asociar el token al asociado real EN TU BD (esto vive solo aquí)
  await db.query(
    "INSERT INTO lex_sesiones (token, asociado_id, expira) VALUES (?, ?, ?)",
    [data.token, asociado_id, data.expires_at]
  );

  // 3. Devolver solo el URL al frontend (sin la API key)
  res.json({ embed_url: data.embed_url, expires_at: data.expires_at });
});

module.exports = router;

3.Webhook receiver — recibir el resultado

Cuando el asociado completa una interacción (llamada N1, diagnóstico N2, documento generado, doc revisado), Lex Intellecta envía el resultado a tu webhook con firma HMAC. Tu sistema verifica la firma y entrega al asociado por sus canales.

Node.js / Express

Node.js · Express · webhook receiver
// Endpoint donde Lex Intellecta envía los resultados de cada sesión
const crypto = require("crypto");
const LEX_WEBHOOK_SECRET = process.env.LEX_WEBHOOK_SECRET;  // del DPA

// Necesitas el body crudo (raw) para verificar HMAC — no el parsed JSON
app.post("/webhooks/lex", express.raw({ type: "application/json" }), async (req, res) => {
  // 1. Verificar firma HMAC-SHA256
  const signature = req.headers["x-lex-signature"] || "";
  const expected = "sha256=" + crypto
    .createHmac("sha256", LEX_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send("invalid signature");
  }

  // 2. Parsear el payload
  const payload = JSON.parse(req.body.toString());
  const { token, tipo_resultado, resultado } = payload;

  // 3. Asociar al afiliado real (mediante tu BD)
  const sesion = await db.query(
    "SELECT asociado_id FROM lex_sesiones WHERE token = ?",
    [token]
  );
  if (!sesion[0]) return res.status(404).send("unknown token");

  // 4. Actuar según tipo
  switch (tipo_resultado) {
    case "n1_orientacion":
      // Enviar email de resumen al asociado por TUS canales
      await enviarEmailN1(sesion[0].asociado_id, resultado);
      break;
    case "n2_diagnostico":
      await enviarPDFDiagnostico(sesion[0].asociado_id, resultado);
      break;
    case "doc_generado":
      // Hacer merge final con datos del afiliado y entregar
      const docMergeado = mergeTags(resultado.contenido_html, sesion[0].asociado_id);
      await guardarYEnviar(sesion[0].asociado_id, docMergeado);
      break;
    case "doc_revisado":
      await enviarReviewReport(sesion[0].asociado_id, resultado);
      break;
  }

  res.status(200).send("ok");
});

PHP / Laravel

PHP · Laravel · webhook receiver
// routes/api.php o el controlador correspondiente
Route::post('/webhooks/lex', function (Request $request) {
    // 1. Verificar firma HMAC
    $secret = env('LEX_WEBHOOK_SECRET');
    $signature = $request->header('X-Lex-Signature', '');
    $expected = 'sha256=' . hash_hmac('sha256', $request->getContent(), $secret);

    if (!hash_equals($signature, $expected)) {
        return response('invalid signature', 401);
    }

    // 2. Parsear y enrutar
    $payload = $request->json()->all();
    $sesion = DB::table('lex_sesiones')
        ->where('token', $payload['token'])->first();
    if (!$sesion) return response('unknown token', 404);

    // 3. Despachar según tipo
    match ($payload['tipo_resultado']) {
        'n1_orientacion'  => EnviarEmailN1::dispatch($sesion->asociado_id, $payload['resultado']),
        'n2_diagnostico'  => EnviarPDFDiagnostico::dispatch($sesion->asociado_id, $payload['resultado']),
        'doc_generado'    => ProcesarDocumentoLex::dispatch($sesion->asociado_id, $payload['resultado']),
        'doc_revisado'    => EnviarReviewReport::dispatch($sesion->asociado_id, $payload['resultado']),
    };

    return response('ok', 200);
});

4.Merge de documentos en tu servidor

Cuando recibes un documento (Lex Docs), viene con campos variables sin completar. Tu servidor reemplaza los placeholders con los datos del afiliado antes de entregarlo.

Función helper de merge

JavaScript · ejecuta en tu servidor
// El documento llega con tags como [NOMBRE_AFILIADO], [CEDULA], [DIRECCION]
function mergeDocumento(htmlConTags, datosAfiliado) {
  // Mapeo de placeholders a datos del afiliado (de tu BD)
  const mapping = {
    "[NOMBRE_AFILIADO]": datosAfiliado.nombre_completo,
    "[CEDULA]":          datosAfiliado.cedula,
    "[DIRECCION]":       datosAfiliado.direccion,
    "[TELEFONO]":        datosAfiliado.telefono,
    "[EMAIL]":           datosAfiliado.email,
    "[CIUDAD]":          datosAfiliado.ciudad,
    "[FECHA]":           new Date().toLocaleDateString("es-CO", {
      day: "numeric", month: "long", year: "numeric"
    })
  };

  let resultado = htmlConTags;
  for (const [tag, valor] of Object.entries(mapping)) {
    // Reemplaza también <span data-merge="...">...</span> si vino así
    const escapedTag = tag.replace(/\[|\]/g, "\\$&");
    resultado = resultado.replace(new RegExp(escapedTag, "g"), valor);
  }

  // Validación: si quedan tags sin merger, log warning
  const tagsRestantes = resultado.match(/\[[A-Z_]+\]/g);
  if (tagsRestantes) {
    console.warn("Tags sin merger:", tagsRestantes);
  }

  return resultado;
}

5.Smoke test rápido (cURL)

Prueba que tu API key funciona contra el sandbox antes de implementar el código.

Bash · cURL · sandbox
# 1. Generar un token (devuelve UUID + embed_url)
curl -X POST https://lexintellecta.com/zk/api/v1/token/generate \
  -H "Authorization: Bearer lex_zk_demo_2026" \
  -H "Content-Type: application/json" \
  -d '{"scope": "multi"}'

# Respuesta esperada:
# {
#   "ok": true,
#   "token": "550e8400-e29b-41d4-a716-446655440000",
#   "embed_url": "https://lexintellecta.com/saas/demo/inicio?token=...",
#   "expires_at": "2026-04-30T22:30:00.000Z",
#   "scope": "multi",
#   "organizacion": { "nombre": "...", "branding_color": "..." }
# }

# 2. Validar el token (lo hace el embed automáticamente)
curl https://lexintellecta.com/zk/api/v1/token/validate/EL_UUID_DE_ARRIBA

6.Checklist de integración

Tiempo estimado: 1-2 horas para el dev mid-level. Probado con clientes piloto. Tu equipo de IT revisa, copia, ajusta a su framework, y queda en producción el mismo día.
Lo que NUNCA debes hacer: 1. Exponer la API key en el frontend o en un repo público. 2. Saltarte la verificación HMAC del webhook — un atacante podría inyectar resultados falsos. 3. Almacenar PII del afiliado del lado nuestro — tu sistema retiene el control de los datos.

7.Referencia rápida de campos del payload

Tipo de resultadoCampos en resultado
n1_orientacion respuestaTexto · resumenCaso · areaJuridica · derivarN2 (boolean)
n2_diagnostico resumenEjecutivo · escenarios[] (con probabilidad) · fuentesCitadas[] · recomendacion
doc_generado nombre_documento · contenido_html (con merge tags) · merge_fields_requeridos[]
doc_revisado conceptoJuridico · hallazgos[] (RIESGO/OMISION/INCONSISTENCIA/FORTALEZA) · riesgoGeneral