{
  "name": "CAR Claro",
  "nodes": [
    {
      "parameters": {
        "public": true,
        "mode": "hostedChat",
        "options": {
          "title": "CAR Claro",
          "subtitle": "Seu ajudante do CAR \u2014 em linguagem simples",
          "inputPlaceholder": "Digite o c\u00f3digo (ex.: CAR-7K3F9) ou cole o texto da pend\u00eancia...",
          "showWelcomeScreen": false,
          "loadPreviousSession": "notSupported",
          "responseMode": "lastNode",
          "initialMessages": "Ol\u00e1! Eu sou o CAR Claro, seu ajudante do CAR. \ud83c\udf31\nDigite o c\u00f3digo que veio no seu aviso (ex.: CAR-7K3F9) ou cole o texto da pend\u00eancia."
        }
      },
      "id": "chat-trigger-node",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1.1,
      "position": [
        400,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "/*\n * CAR Claro \u2014 o cerebro (logica do bot)\n * ------------------------------------------------------------------\n * Funcao PURA: processar(entrada, estado) -> { respostas, estado }\n *   - entrada: texto do produtor (chatInput)\n *   - estado : objeto de memoria da conversa (por sessionId). undefined = conversa nova.\n *   - retorna: { respostas: string[], estado: novoEstado }\n *\n * NAO depende do n8n. O wrapper do Code node (ver workflows/car-claro.json\n * e README) e quem le/grava o estado por sessao e devolve o texto pro chat.\n *\n * Modo de resposta: SCRIPTED (roteirizado, deterministico). Sem IA/LLM.\n * (Onde uma futura chamada de IA entraria esta marcado com [IA-FUTURO].)\n *\n * IMPORTANTE: os textos exibidos ao produtor usam portugues com acentuacao\n * correta. O casamento de gatilhos/comandos passa por normalizar() (sem\n * acento, minusculo), entao a acentuacao do texto NAO atrapalha a deteccao.\n *\n * Os dados (catalogo + casos) vivem embutidos aqui como constantes para o\n * Code node ser autossuficiente. A fonte curada em JSON fica em\n * cerebro/catalogo_pendencias.json e cerebro/casos_exemplo.json.\n */\n\n// ===================== DADOS (espelho dos JSON) =====================\n\nconst CATALOGO = [\n  {\n    id: \"RL_INSUFICIENTE\",\n    titulo_tecnico: \"\u00c1rea de Reserva Legal inferior ao m\u00ednimo legal\",\n    gatilhos: [\"reserva legal\", \"rl \", \" rl\", \"reserva legal insuficiente\", \"20%\", \"inferior ao minimo\", \"abaixo do minimo\", \"deficit de reserva\", \"rl insuficiente\"],\n    traducao: \"Seu Raimundo, todo im\u00f3vel rural precisa manter um peda\u00e7o de mata em p\u00e9 \u2014 e chamam isso de Reserva Legal. No seu caso, o sistema apontou que a sua \u00e1rea de mata est\u00e1 menor do que a lei pede pra sua regi\u00e3o. N\u00e3o \u00e9 multa nem perda de terra: \u00e9 s\u00f3 um ajuste pra regularizar.\",\n    como_resolver: [\n      \"Confira no mapa do CAR onde est\u00e1 marcada a sua Reserva Legal hoje.\",\n      \"Voc\u00ea tem 3 caminhos pra completar o que falta: (1) deixar uma \u00e1rea da sua propriedade virar mata de novo (recuperar), (2) compensar com \u00e1rea de mata equivalente em outro lugar do mesmo bioma, ou (3) juntar com a Reserva de vizinhos (condom\u00ednio de reserva).\",\n      \"Procure a Secretaria de Meio Ambiente do seu estado ou um t\u00e9cnico (agr\u00f4nomo/florestal) pra escolher o caminho mais barato pro seu caso.\",\n      \"Com o caminho escolhido, voc\u00ea assina um compromisso de regulariza\u00e7\u00e3o (PRA) e o prazo passa a contar \u2014 sem risco enquanto estiver cumprindo.\"\n    ],\n    base_legal: \"Lei 12.651/2012 (C\u00f3digo Florestal), arts. 12, 66 e 68.\",\n    prazo_dias: 60\n  },\n  {\n    id: \"APP_RECOMPOR\",\n    titulo_tecnico: \"D\u00e9ficit de \u00c1rea de Preserva\u00e7\u00e3o Permanente (APP) \u2014 mata ciliar a recompor\",\n    gatilhos: [\"app\", \"area de preservacao\", \"mata ciliar\", \"beira de rio\", \"margem do rio\", \"nascente\", \"recompor\", \"faixa de mata\", \"preservacao permanente\", \"olho d'agua\", \"olho dagua\"],\n    traducao: \"Seu Raimundo, a faixa de mata na beira de rios, c\u00f3rregos e nascentes tem um nome: APP (\u00c1rea de Preserva\u00e7\u00e3o Permanente). O sistema viu que falta um pouco dessa mata na beira da \u00e1gua dentro da sua \u00e1rea. Isso protege a sua pr\u00f3pria \u00e1gua de secar e assorear \u2014 e d\u00e1 pra regularizar plantando de volta.\",\n    como_resolver: [\n      \"Veja no mapa do CAR qual trecho de rio, c\u00f3rrego ou nascente est\u00e1 sem a faixa de mata.\",\n      \"Para im\u00f3vel pequeno (at\u00e9 4 m\u00f3dulos fiscais), a faixa a recompor \u00e9 menor \u2014 a pr\u00f3pria lei facilita. Confirme o tamanho do seu im\u00f3vel.\",\n      \"Plante mudas nativas na faixa indicada ou deixe a vegeta\u00e7\u00e3o se regenerar sozinha (cercar pra o gado n\u00e3o entrar j\u00e1 ajuda muito).\",\n      \"Registre que aderiu ao PRA (Programa de Regulariza\u00e7\u00e3o Ambiental) na sua Secretaria de Meio Ambiente \u2014 isso formaliza o prazo e protege voc\u00ea durante a recomposi\u00e7\u00e3o.\"\n    ],\n    base_legal: \"Lei 12.651/2012 (C\u00f3digo Florestal), arts. 4\u00ba, 61-A e 61-B.\",\n    prazo_dias: 90\n  },\n  {\n    id: \"DOC_FALTANTE\",\n    titulo_tecnico: \"Documenta\u00e7\u00e3o ou georreferenciamento pendente / sobreposi\u00e7\u00e3o a esclarecer\",\n    gatilhos: [\"documento\", \"documentacao\", \"matricula\", \"ccir\", \"cpf\", \"sobreposicao\", \"falta documento\", \"pendencia documental\", \"georreferenciamento\", \"comprovacao\", \"titulo da terra\"],\n    traducao: \"Seu Raimundo, dessa vez n\u00e3o \u00e9 sobre mata: faltou um documento ou a sua \u00e1rea no mapa est\u00e1 encostando na \u00e1rea de um vizinho (o que chamam de sobreposi\u00e7\u00e3o). \u00c9 o tipo de pend\u00eancia mais r\u00e1pida de resolver \u2014 geralmente \u00e9 s\u00f3 juntar um papel ou ajustar o desenho do mapa.\",\n    como_resolver: [\n      \"Separe os documentos do im\u00f3vel: matr\u00edcula/registro, CCIR (do INCRA) e o seu CPF.\",\n      \"Se for sobreposi\u00e7\u00e3o com vizinho, leve o mapa e converse \u2014 na maioria das vezes \u00e9 s\u00f3 corrigir um ponto que ficou torto no desenho da \u00e1rea.\",\n      \"Entre no sistema do CAR (ou pe\u00e7a ajuda na Secretaria de Meio Ambiente / sindicato rural) e anexe o documento ou corrija o limite da \u00e1rea.\",\n      \"Guarde o protocolo de envio \u2014 \u00e9 ele que comprova que voc\u00ea regularizou dentro do prazo.\"\n    ],\n    base_legal: \"Lei 12.651/2012, art. 29 e instru\u00e7\u00f5es do SICAR.\",\n    prazo_dias: 30\n  }\n];\n\nconst CASOS = {\n  \"CAR-7K3F9\": {\n    produtor: \"Raimundo\",\n    imovel: \"S\u00edtio Boa Esperan\u00e7a - Bom Jesus do Norte/ES\",\n    pendencia_id: \"RL_INSUFICIENTE\",\n    detalhe: \"Reserva Legal declarada de 12%, m\u00ednimo exigido de 20% (bioma Mata Atl\u00e2ntica). Faltam 8%.\",\n    prazo_data: \"2026-08-25\",\n    confirmacao: \"123\"\n  },\n  \"CAR-4B8T2\": {\n    produtor: \"Dona Benedita\",\n    imovel: \"Ch\u00e1cara \u00c1gua Limpa - Buritis/MG\",\n    pendencia_id: \"APP_RECOMPOR\",\n    detalhe: \"Faixa de mata ciliar ausente em ~120 m de margem de c\u00f3rrego. Im\u00f3vel com 2 m\u00f3dulos fiscais.\",\n    prazo_data: \"2026-09-30\",\n    confirmacao: \"456\"\n  },\n  \"CAR-9Z1Q7\": {\n    produtor: \"Jo\u00e3o Pereira\",\n    imovel: \"Fazenda Tr\u00eas Irm\u00e3os - Sorriso/MT\",\n    pendencia_id: \"DOC_FALTANTE\",\n    detalhe: \"Sobreposi\u00e7\u00e3o de 0,4 ha com o im\u00f3vel vizinho a esclarecer + CCIR desatualizado.\",\n    prazo_data: \"2026-07-28\",\n    confirmacao: \"789\"\n  }\n};\n\nconst NOME_BOT = \"CAR Claro\";\n\n// ===================== HELPERS =====================\n\nfunction normalizar(texto) {\n  return String(texto || \"\")\n    .toLowerCase()\n    .normalize(\"NFD\")\n    .replace(/[\u0300-\u036f]/g, \"\") // tira acentos (so para casar gatilhos/comandos)\n    .trim();\n}\n\nfunction acharPendencia(id) {\n  return CATALOGO.find(function (p) { return p.id === id; }) || null;\n}\n\n// Porta A: detecta um codigo de caso na entrada (ex.: \"CAR-7K3F9\", \"car 7k3f9\")\nfunction detectarCodigo(entrada) {\n  const bruto = String(entrada || \"\").toUpperCase();\n  const compacto = bruto.replace(/[^A-Z0-9]/g, \"\");\n  for (const codigo in CASOS) {\n    if (compacto === codigo.replace(/[^A-Z0-9]/g, \"\")) return codigo;\n  }\n  return null;\n}\n\n// Porta B: identifica a pendencia pelo texto via gatilhos\nfunction identificarPorTexto(entrada) {\n  const t = \" \" + normalizar(entrada) + \" \";\n  let melhor = null;\n  let melhorScore = 0;\n  for (const p of CATALOGO) {\n    let score = 0;\n    for (const g of p.gatilhos) {\n      const gn = normalizar(g);\n      if (gn && t.indexOf(gn) !== -1) score += 1;\n    }\n    if (score > melhorScore) { melhorScore = score; melhor = p; }\n  }\n  return melhorScore > 0 ? melhor : null;\n}\n\nfunction dataBR(iso) {\n  if (!iso) return \"\";\n  const m = String(iso).match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n  return m ? (m[3] + \"/\" + m[2] + \"/\" + m[1]) : String(iso);\n}\n\nfunction blocoComoResolver(pendencia) {\n  const linhas = pendencia.como_resolver.map(function (passo, i) {\n    return (i + 1) + \") \" + passo;\n  });\n  return \"Como resolver, passo a passo:\\n\" + linhas.join(\"\\n\");\n}\n\nfunction blocoOpcoes() {\n  return \"O que voc\u00ea quer fazer agora?\\n\" +\n    \"1) Ver o passo a passo de novo\\n\" +\n    \"2) Falar com um t\u00e9cnico\\n\" +\n    \"3) Lembrar do prazo\\n\" +\n    \"(ou digite *menu* pra recome\u00e7ar)\";\n}\n\nfunction menuInicial() {\n  return \"Ol\u00e1! Eu sou o \" + NOME_BOT + \", seu ajudante do CAR. \ud83c\udf31\\n\" +\n    \"Vou te ajudar a entender e resolver a pend\u00eancia do seu Cadastro Ambiental Rural, em linguagem simples.\\n\\n\" +\n    \"Voc\u00ea pode:\\n\" +\n    \"\u2022 Digitar o *c\u00f3digo* que veio no seu aviso (ex.: CAR-7K3F9), ou\\n\" +\n    \"\u2022 *Colar o texto* da pend\u00eancia que apareceu pra voc\u00ea.\";\n}\n\n// Monta a entrega da pendencia (traducao + como resolver + prazo).\n// dadosCaso (opcional, Porta A) traz imovel/detalhe/prazo_data.\nfunction entregarPendencia(pendencia, dadosCaso) {\n  const respostas = [];\n  if (dadosCaso) {\n    respostas.push(\n      \"Encontrei seu cadastro, \" + dadosCaso.produtor + \". \u2705\\n\" +\n      \"Im\u00f3vel: \" + dadosCaso.imovel + \"\\n\" +\n      \"Pend\u00eancia: \" + pendencia.titulo_tecnico\n    );\n  } else {\n    respostas.push(\"Entendi a sua pend\u00eancia: *\" + pendencia.titulo_tecnico + \"*.\");\n  }\n\n  let traducao = pendencia.traducao;\n  if (dadosCaso && dadosCaso.detalhe) {\n    traducao += \"\\n\\nNo seu caso: \" + dadosCaso.detalhe;\n  }\n  respostas.push(traducao);\n\n  respostas.push(blocoComoResolver(pendencia));\n\n  let prazoTxt;\n  if (dadosCaso && dadosCaso.prazo_data) {\n    prazoTxt = \"Prazo pra regularizar: at\u00e9 \" + dataBR(dadosCaso.prazo_data) + \".\";\n  } else {\n    prazoTxt = \"Prazo de refer\u00eancia: cerca de \" + pendencia.prazo_dias + \" dias a partir da ades\u00e3o ao PRA.\";\n  }\n  prazoTxt += \"\\nBase legal: \" + pendencia.base_legal;\n  respostas.push(prazoTxt);\n\n  respostas.push(blocoOpcoes());\n  return respostas;\n}\n\n// ===================== O CEREBRO =====================\n\nfunction processar(entrada, estado) {\n  // estado novo / vazio\n  if (!estado || !estado.etapa) {\n    estado = { etapa: \"INICIO\" };\n  }\n  const txt = normalizar(entrada);\n\n  // comandos globais (valem em qualquer etapa)\n  if (txt === \"menu\" || txt === \"recomecar\" || txt === \"reiniciar\" || txt === \"voltar\") {\n    return { respostas: [menuInicial()], estado: { etapa: \"INICIO\" } };\n  }\n\n  // ---------- ETAPA: AGUARDA_CONFIRMACAO (Porta A, identidade leve) ----------\n  if (estado.etapa === \"AGUARDA_CONFIRMACAO\") {\n    const caso = CASOS[estado.codigo];\n    if (!caso) {\n      return { respostas: [menuInicial()], estado: { etapa: \"INICIO\" } };\n    }\n    if (txt === normalizar(caso.confirmacao)) {\n      const pendencia = acharPendencia(caso.pendencia_id);\n      const respostas = entregarPendencia(pendencia, caso);\n      return {\n        respostas: respostas,\n        estado: { etapa: \"IDENTIFICADO\", pendencia_id: pendencia.id, codigo: estado.codigo }\n      };\n    }\n    return {\n      respostas: [\"Hmm, esses n\u00fameros n\u00e3o bateram. \ud83e\udd14 Digite os *3 n\u00fameros* que vieram junto com o seu c\u00f3digo (no aviso/e-mail). Ou digite *menu* pra recome\u00e7ar.\"],\n      estado: estado\n    };\n  }\n\n  // ---------- ETAPA: IDENTIFICADO (pos-entrega, opcoes) ----------\n  if (estado.etapa === \"IDENTIFICADO\") {\n    const pendencia = acharPendencia(estado.pendencia_id);\n    if (txt === \"1\" || txt.indexOf(\"passo\") !== -1) {\n      return { respostas: [blocoComoResolver(pendencia), blocoOpcoes()], estado: estado };\n    }\n    if (txt === \"2\" || txt.indexOf(\"tecnico\") !== -1 || txt.indexOf(\"ajuda\") !== -1) {\n      return {\n        respostas: [\n          \"Sem problema! Quem resolve isso na pr\u00e1tica \u00e9 a *Secretaria de Meio Ambiente* do seu estado ou o *sindicato rural* da sua cidade \u2014 os dois costumam ter atendimento gratuito.\\n\" +\n          \"Leve o c\u00f3digo da sua pend\u00eancia e os documentos do im\u00f3vel. Quer que eu te lembre do prazo? Digite *3*.\",\n          blocoOpcoes()\n        ],\n        estado: estado\n      };\n    }\n    if (txt === \"3\" || txt.indexOf(\"prazo\") !== -1 || txt.indexOf(\"lembr\") !== -1) {\n      const caso = CASOS[estado.codigo];\n      const quando = caso && caso.prazo_data ? (\"at\u00e9 \" + dataBR(caso.prazo_data)) : (\"em ~\" + pendencia.prazo_dias + \" dias\");\n      return {\n        respostas: [\n          \"Combinado! \ud83d\udccc Anotei o seu prazo: *\" + quando + \"*.\\n\" +\n          \"Vou te lembrar conforme a data se aproximar. Enquanto estiver resolvendo dentro do prazo, voc\u00ea est\u00e1 tranquilo.\",\n          blocoOpcoes()\n        ],\n        estado: estado\n      };\n    }\n    // talvez tenha colado outra pendencia / outro codigo -> reidentifica\n    const reidentificado = roteamentoInicial(entrada);\n    if (reidentificado) return reidentificado;\n\n    return {\n      respostas: [\"N\u00e3o entendi. \" + blocoOpcoes()],\n      estado: estado\n    };\n  }\n\n  // ---------- ETAPA: INICIO ----------\n  if (!txt || txt === \"oi\" || txt === \"ola\" || txt === \"ola!\" || txt === \"bom dia\" || txt === \"boa tarde\" || txt === \"boa noite\" || txt === \"ajuda\" || txt === \"start\") {\n    return { respostas: [menuInicial()], estado: { etapa: \"INICIO\" } };\n  }\n\n  const roteado = roteamentoInicial(entrada);\n  if (roteado) return roteado;\n\n  // nao entendeu\n  return {\n    respostas: [\n      \"N\u00e3o consegui identificar a sua pend\u00eancia ainda. \ud83d\ude15\\n\" +\n      \"Tente *colar o texto* exatamente como apareceu, ou digite o *c\u00f3digo* do aviso (ex.: CAR-7K3F9).\\n\" +\n      \"Se preferir, digite *menu*.\"\n    ],\n    estado: { etapa: \"INICIO\" }\n  };\n}\n\n// Decide entre Porta A (codigo) e Porta B (texto). Retorna null se nada casou.\nfunction roteamentoInicial(entrada) {\n  // Porta A: codigo unico\n  const codigo = detectarCodigo(entrada);\n  if (codigo) {\n    return {\n      respostas: [\n        \"Achei o cadastro ligado ao c\u00f3digo *\" + codigo + \"*. \ud83d\udd12\\n\" +\n        \"Pra proteger seus dados, me confirme os *3 n\u00fameros* que vieram junto com o c\u00f3digo no seu aviso.\"\n      ],\n      estado: { etapa: \"AGUARDA_CONFIRMACAO\", codigo: codigo }\n    };\n  }\n\n  // Porta B: texto da pendencia\n  const pendencia = identificarPorTexto(entrada);\n  if (pendencia) {\n    return {\n      respostas: entregarPendencia(pendencia, null),\n      estado: { etapa: \"IDENTIFICADO\", pendencia_id: pendencia.id }\n    };\n  }\n\n  return null;\n}\n\n// [IA-FUTURO] Se nenhum gatilho casar, aqui entraria uma chamada de LLM para\n// classificar o texto livre na pendencia mais provavel do CATALOGO e/ou\n// reescrever a traducao de forma ainda mais proxima do produtor. Mantido\n// desligado a noite (modo scripted, sem custo e sem risco na demo).\n\n// ===================== EXPORT (so para testes em Node) =====================\nif (typeof module !== \"undefined\" && module.exports) {\n  module.exports = { processar: processar, CATALOGO: CATALOGO, CASOS: CASOS, normalizar: normalizar };\n}\n\n\n// ===================== WRAPPER n8n (Code node) =====================\n// Le chatInput/sessionId do Chat Trigger, gerencia o estado por sessao\n// em getWorkflowStaticData('global') e devolve o texto pro chat.\nconst _item = $input.first().json;\nconst _chatInput = _item.chatInput || _item.message || _item.text || \"\";\nconst _sessionId = _item.sessionId || _item.session || \"default\";\nconst _store = $getWorkflowStaticData(\"global\");\nconst _estadoAnterior = _store[_sessionId];\nconst _r = processar(_chatInput, _estadoAnterior);\n_store[_sessionId] = _r.estado;\nconst _texto = _r.respostas.join(\"\\n\\n\");\nreturn [{ json: { output: _texto, sessionId: _sessionId } }];\n"
      },
      "id": "cerebro-code-node",
      "name": "Cerebro CAR Claro",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ]
    }
  ],
  "connections": {
    "When chat message received": {
      "main": [
        [
          {
            "node": "Cerebro CAR Claro",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateId": "car-claro"
  },
  "tags": []
}