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.
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).
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.
<!-- 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>
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.
// 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;
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.
// 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"); });
// 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); });
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.
// 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; }
Prueba que tu API key funciona contra el sandbox antes de implementar el código.
# 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
LEX_API_KEY y LEX_WEBHOOK_SECRET en tu servidor.POST /asesoria-legal/iniciar de tu backend funciona y guarda el token en tu BD.POST /webhooks/lex verifica HMAC y rutea por tipo_resultado.mergeDocumento() reemplaza los placeholders antes de entregar al asociado.lex_zk_demo_2026) y obtuviste embed_url.| Tipo de resultado | Campos 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 |