NALU/src/Nalu.Web/Pages/Docs/N8n.cshtml
Ricardo Carneiro e01787ee60 Add deploy infrastructure, missing validators, and new features
- Add Docker Swarm deploy stack, CI workflow (.gitea), entrypoint script
- Fix Dockerfile to build Nalu.Web (was pointing to old Nalu.Api path)
- Add validate_name.md and other missing validators to prod
- Add Stripe endpoints, HangfireDashboardAuth, InputGuard, NameLookupService
- Add SuspiciousRateLimiter, En/ pages, Legal/ pages, Seguranca docs
- Add Nalu.Jobs and Nalu.NameImporter projects (were untracked)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 12:31:12 -03:00

257 lines
17 KiB
Plaintext

@page "/docs/n8n"
@model Nalu.Web.Pages.Docs.N8nModel
@{
ViewData["Title"] = "Integração com N8N — NaLU AI Docs";
ViewData["Description"] = "Como usar o NaLU AI no N8N para validar CPF, CEP, nome e mais em fluxos de chatbot sem escrever código.";
}
<section class="bg-gradient-to-b from-slate-50 to-white pt-16 pb-8">
<div class="max-w-3xl mx-auto px-4 sm:px-6">
<div class="text-xs font-semibold text-nalu-600 uppercase tracking-wide mb-3">
<a href="/docs" class="hover:underline">Docs</a> / N8N
</div>
<h1 class="text-3xl font-extrabold text-gray-900 mb-2">Integrando com N8N</h1>
<p class="text-gray-500">Sem código. Arraste um nó, cole a URL, mapeie os campos. Pronto.</p>
</div>
</section>
<section class="py-10 bg-white">
<div class="max-w-3xl mx-auto px-4 sm:px-6 space-y-14">
<!-- O que você vai precisar -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-3">O que você vai precisar</h2>
<ul class="space-y-2 text-sm text-gray-600">
<li class="flex items-start gap-2">
<span class="text-nalu-600 font-bold shrink-0">1.</span>
<span>Uma conta NaLU AI com uma API Key — <a href="/login" class="text-nalu-600 hover:underline">crie grátis aqui</a>. A key fica no seu <a href="/painel" class="text-nalu-600 hover:underline">Painel</a>, formato <code class="bg-slate-100 px-1 rounded text-xs">nalu-XXXXXXXXXX</code>.</span>
</li>
<li class="flex items-start gap-2">
<span class="text-nalu-600 font-bold shrink-0">2.</span>
<span>N8N rodando (cloud ou self-hosted). Qualquer versão moderna funciona.</span>
</li>
<li class="flex items-start gap-2">
<span class="text-nalu-600 font-bold shrink-0">3.</span>
<span>Um fluxo que já recebe a mensagem do usuário — pode ser Webhook, Typebot, Chatwoot, Z-API, Telegram, etc.</span>
</li>
</ul>
</div>
<!-- Conceito rápido -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-3">Conceito em 30 segundos</h2>
<p class="text-sm text-gray-600 mb-4">
O NaLU é uma API REST. No N8N, qualquer API REST é chamada com o nó
<strong>HTTP Request</strong>. Você manda o que o usuário digitou,
o NaLU devolve o dado extraído e limpo — ou uma sugestão de como perguntar de novo.
</p>
<div class="bg-slate-50 rounded-xl p-5 text-sm font-mono text-gray-600 border border-gray-100">
<div class="flex items-center gap-2 mb-1"><span class="text-gray-400">Entrada:</span> <span>"meu cpf é 111.444.777-35"</span></div>
<div class="flex items-center gap-2 mb-1"><span class="text-gray-400"> ↓</span></div>
<div class="flex items-center gap-2 mb-1"><span class="text-gray-400">NaLU:</span> <span class="text-green-600">obtained: true · extracted_value: "111.444.777-35"</span></div>
<div class="flex items-center gap-2 mt-3 mb-1"><span class="text-gray-400">Entrada:</span> <span>"não lembro agora"</span></div>
<div class="flex items-center gap-2 mb-1"><span class="text-gray-400"> ↓</span></div>
<div class="flex items-center gap-2"><span class="text-gray-400">NaLU:</span> <span class="text-amber-600">obtained: false · suggestion_to_agent: "Tudo bem! Pode digitar só os números do CPF?"</span></div>
</div>
</div>
<!-- Passo a passo: HTTP Request -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-1">Configurando o nó HTTP Request</h2>
<p class="text-sm text-gray-500 mb-5">Exemplo com validação de CPF. O processo é idêntico para todos os outros validadores — só muda a URL.</p>
<div class="space-y-6">
<!-- Step 1 -->
<div class="flex gap-4">
<div class="shrink-0 w-8 h-8 bg-nalu-600 text-white rounded-full flex items-center justify-center font-bold text-sm">1</div>
<div class="flex-1">
<div class="font-semibold text-gray-900 mb-1">Arraste um nó <span class="font-mono text-nalu-600">HTTP Request</span></div>
<p class="text-sm text-gray-600">No painel do N8N, clique no <strong>+</strong> para adicionar nó. Busque por "HTTP Request" e selecione.</p>
</div>
</div>
<!-- Step 2 -->
<div class="flex gap-4">
<div class="shrink-0 w-8 h-8 bg-nalu-600 text-white rounded-full flex items-center justify-center font-bold text-sm">2</div>
<div class="flex-1">
<div class="font-semibold text-gray-900 mb-2">Configure o método e a URL</div>
<div class="bg-slate-900 rounded-xl p-4 font-mono text-sm text-slate-300 space-y-1">
<div><span class="text-slate-500">Method:</span> POST</div>
<div><span class="text-slate-500">URL:</span> https://api.naluai.dev/v1/extract/cpf</div>
</div>
<p class="text-xs text-gray-500 mt-2">Troque <code class="bg-slate-100 px-1 rounded">cpf</code> pelo validador que precisar: <code class="bg-slate-100 px-1 rounded">cep</code>, <code class="bg-slate-100 px-1 rounded">name</code>, <code class="bg-slate-100 px-1 rounded">email</code>, etc.</p>
</div>
</div>
<!-- Step 3 -->
<div class="flex gap-4">
<div class="shrink-0 w-8 h-8 bg-nalu-600 text-white rounded-full flex items-center justify-center font-bold text-sm">3</div>
<div class="flex-1">
<div class="font-semibold text-gray-900 mb-2">Adicione a autenticação</div>
<p class="text-sm text-gray-600 mb-2">Em <strong>Authentication</strong>, selecione <strong>Header Auth</strong> e configure:</p>
<div class="bg-slate-900 rounded-xl p-4 font-mono text-sm text-slate-300 space-y-1">
<div><span class="text-slate-500">Name:</span> Authorization</div>
<div><span class="text-slate-500">Value:</span> Bearer nalu-SUA_API_KEY</div>
</div>
<div class="bg-amber-50 border border-amber-100 rounded-xl px-4 py-3 text-xs text-amber-800 mt-3">
<strong>Dica:</strong> Guarde a key em <strong>Credentials → Header Auth</strong> no N8N. Assim você reutiliza em todos os nós NaLU sem copiar a key toda vez.
</div>
</div>
</div>
<!-- Step 4 -->
<div class="flex gap-4">
<div class="shrink-0 w-8 h-8 bg-nalu-600 text-white rounded-full flex items-center justify-center font-bold text-sm">4</div>
<div class="flex-1">
<div class="font-semibold text-gray-900 mb-2">Configure o corpo (Body)</div>
<p class="text-sm text-gray-600 mb-2">Selecione <strong>Body → JSON</strong> e use expressões N8N para mapear os dados:</p>
<div class="bg-slate-900 rounded-xl p-4 font-mono text-sm text-slate-300 overflow-x-auto leading-relaxed">
<pre>{
"user_input": "{{ $json.message }}",
"agent_input": "Qual o seu CPF?",
"agent_context": "Agente de cadastro de clientes",
"language": "pt-BR"
}</pre>
</div>
<p class="text-xs text-gray-500 mt-2">
<code class="bg-slate-100 px-1 rounded">$json.message</code> é o campo que vem do nó anterior com a resposta do usuário.
O nome exato depende do seu trigger (Webhook, Typebot, etc.) — use o painel de expressões do N8N para localizar.
</p>
</div>
</div>
<!-- Step 5 -->
<div class="flex gap-4">
<div class="shrink-0 w-8 h-8 bg-nalu-600 text-white rounded-full flex items-center justify-center font-bold text-sm">5</div>
<div class="flex-1">
<div class="font-semibold text-gray-900 mb-2">Use o resultado com um nó IF</div>
<p class="text-sm text-gray-600 mb-2">Conecte um nó <strong>IF</strong> na saída do HTTP Request:</p>
<div class="bg-slate-900 rounded-xl p-4 font-mono text-sm text-slate-300 space-y-1">
<div><span class="text-slate-500">Condição:</span> <span class="text-green-400">{{ $json.obtained }}</span> <span class="text-slate-500">igual a</span> <span class="text-green-400">true</span></div>
</div>
<div class="grid sm:grid-cols-2 gap-3 mt-3">
<div class="bg-green-50 border border-green-100 rounded-xl p-3 text-sm">
<div class="font-semibold text-green-700 mb-1">✓ Ramo TRUE</div>
<p class="text-green-600 text-xs">Dado extraído. Use <code class="bg-green-100 px-1 rounded">$json.extracted_value</code> no próximo passo do fluxo.</p>
</div>
<div class="bg-amber-50 border border-amber-100 rounded-xl p-3 text-sm">
<div class="font-semibold text-amber-700 mb-1">✗ Ramo FALSE</div>
<p class="text-amber-600 text-xs">Não encontrou. Envie <code class="bg-amber-100 px-1 rounded">$json.suggestion_to_agent</code> como resposta ao usuário e aguarde nova tentativa.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Diagrama do fluxo completo -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-3">Fluxo completo de coleta de dados</h2>
<p class="text-sm text-gray-600 mb-4">Exemplo: coletar CPF em um chatbot de cobrança via WhatsApp.</p>
<div class="bg-slate-900 rounded-xl p-5 font-mono text-sm text-slate-300 leading-relaxed overflow-x-auto">
<pre>[ Webhook — mensagem do WhatsApp ]
[ HTTP Request → NaLU /extract/cpf ]
body: { user_input: mensagem do usuário }
[ IF: obtained === true ]
↓ TRUE ↓ FALSE
[ Salvar CPF no BD ] [ Enviar suggestion_to_agent ]
[ Próxima etapa ] [ Aguardar nova resposta ]
[ (volta para o Webhook) ]</pre>
</div>
</div>
<!-- Exemplo validate_reply -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-3">Exemplo avançado: validate_reply</h2>
<p class="text-sm text-gray-600 mb-4">
O <code class="bg-slate-100 px-1 rounded text-xs">validate_reply</code> analisa a resposta do usuário
<em>no contexto</em> do que o agente disse. Útil para detectar contrapropostas, confirmações e handoffs.
</p>
<div class="bg-slate-900 rounded-xl p-5 font-mono text-sm text-slate-300 leading-relaxed overflow-x-auto mb-4">
<pre>{
"agent_message": "Posso parcelar em 20x de R$100. Topa?",
"user_reply": "{{ $json.message }}",
"agent_context": "Agente de negociação de parcelas",
"language": "pt-BR"
}</pre>
</div>
<p class="text-sm text-gray-600 mb-3">A resposta inclui o campo <code class="bg-slate-100 px-1 rounded text-xs">reply_type</code>. Use um nó <strong>Switch</strong> (em vez de IF) para rotear:</p>
<div class="bg-slate-900 rounded-xl p-5 font-mono text-sm text-slate-300 leading-relaxed overflow-x-auto">
<pre>[ Switch: $json.reply_type ]
"confirmation" → prosseguir com fechamento
"counter_proposal" → avaliar nova proposta ($json.extracted_value)
"rejection" → oferecer alternativa
"handoff" → transferir para atendente humano
"cancel" → acionar fluxo de retenção
"unclear" → pedir esclarecimento</pre>
</div>
</div>
<!-- Campos retornados -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-3">Campos disponíveis na resposta</h2>
<p class="text-sm text-gray-600 mb-3">Todos acessíveis como <code class="bg-slate-100 px-1 rounded text-xs">$json.nome_do_campo</code> no nó seguinte:</p>
<table class="w-full text-sm border border-gray-100 rounded-xl overflow-hidden">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left font-semibold text-gray-700">Campo</th>
<th class="px-4 py-2 text-left font-semibold text-gray-700">O que contém</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
<tr><td class="px-4 py-2 font-mono text-xs text-nalu-600">obtained</td><td class="px-4 py-2 text-gray-600 text-xs">true/false — se o dado foi extraído</td></tr>
<tr><td class="px-4 py-2 font-mono text-xs text-nalu-600">extracted_value</td><td class="px-4 py-2 text-gray-600 text-xs">O dado limpo e normalizado (ex: "111.444.777-35")</td></tr>
<tr><td class="px-4 py-2 font-mono text-xs text-nalu-600">certain</td><td class="px-4 py-2 text-gray-600 text-xs">false = pedir confirmação antes de salvar</td></tr>
<tr><td class="px-4 py-2 font-mono text-xs text-nalu-600">confidence</td><td class="px-4 py-2 text-gray-600 text-xs">low / medium / high</td></tr>
<tr><td class="px-4 py-2 font-mono text-xs text-nalu-600">suggestion_to_agent</td><td class="px-4 py-2 text-gray-600 text-xs">Frase pronta para enviar ao usuário quando obtained=false</td></tr>
<tr><td class="px-4 py-2 font-mono text-xs text-nalu-600">reply_type</td><td class="px-4 py-2 text-gray-600 text-xs">Só validate_reply: confirmation, counter_proposal, handoff, etc.</td></tr>
</tbody>
</table>
</div>
<!-- Dicas práticas -->
<div class="bg-blue-50 border border-blue-100 rounded-2xl p-6">
<div class="font-bold text-blue-800 mb-3">Dicas para não errar</div>
<ul class="text-sm text-blue-700 space-y-2">
<li>• <strong>Limite de tentativas:</strong> coloque um contador no N8N. Se o usuário errar 3 vezes, escale para humano — não deixe o bot em loop infinito.</li>
<li>• <strong>certain: false:</strong> quando true mas certain é false, confirme com o usuário antes de salvar. Ex: "Encontrei o nome João Silva, está correto?"</li>
<li>• <strong>Campos do CEP:</strong> além de <code class="bg-blue-100 px-1 rounded">extracted_value</code>, o validate_cep retorna um objeto com logradouro, bairro, cidade e estado separados — útil para preencher formulários.</li>
<li>• <strong>Erros 429:</strong> créditos acabaram. Trate com um nó IF no status code antes do IF do obtained.</li>
<li>• <strong>agent_context:</strong> sempre envie — melhora muito a qualidade das sugestões contextuais quando o usuário não responde o que foi pedido.</li>
</ul>
</div>
<!-- URLs de referência -->
<div>
<h2 class="text-lg font-bold text-gray-900 mb-3">URLs de referência rápida</h2>
<div class="bg-slate-900 rounded-xl p-5 font-mono text-sm text-slate-300 leading-relaxed overflow-x-auto">
<pre>POST https://api.naluai.dev/v1/extract/cpf
POST https://api.naluai.dev/v1/extract/cep
POST https://api.naluai.dev/v1/extract/cnpj
POST https://api.naluai.dev/v1/extract/email
POST https://api.naluai.dev/v1/extract/phone
POST https://api.naluai.dev/v1/extract/name
POST https://api.naluai.dev/v1/extract/yes-no
POST https://api.naluai.dev/v1/extract/birthdate
POST https://api.naluai.dev/v1/extract/handoff
POST https://api.naluai.dev/v1/extract/cancel-intent
POST https://api.naluai.dev/v1/extract/company-name
POST https://api.naluai.dev/v1/extract/plate-br
POST https://api.naluai.dev/v1/extract/reply ← usa agent_message + user_reply</pre>
</div>
</div>
<div class="border-t border-gray-100 pt-6 flex flex-wrap gap-4">
<a href="/docs" class="text-nalu-600 text-sm hover:underline">← Voltar para Docs</a>
<a href="/docs/api-reference" class="text-nalu-600 text-sm hover:underline">API Reference completa →</a>
<a href="/playground" class="text-nalu-600 text-sm hover:underline">Testar no Playground →</a>
</div>
</div>
</section>