LMS e CRM com 205 mil linhas de código feitos por agentes de IA: o custo da operação, três status de lead e um caractere \r
Dois sistemas em produção interligados — uma plataforma educacional LMS (121.853 linhas de TS/TSX, 47 roteadores, 288 procedures) e um CRM com um registro unificado de 334 ferramentas (83.251 linhas) — foram escritos sem equipe de desenvolvedores, com ajuda de agentes de IA, sobre uma única instância de PostgreSQL. Escrever o código levou algumas noites, mas o verdadeiro custo apareceu na preparação para produção: 14 defeitos críticos na fase de revisão adversarial, mais 5 ao checar as próprias correções, e um caractere invisível de retorno de carro \r que desativou silenciosamente a prática interativa em 138 de 146 aulas. Integrar agentes de IA com um negócio real de forma confiável exigiu dividir o trabalho com o lead em três estados independentes sob bloqueio FOR UPDATE, reconstruir o histórico da conversa no servidor e salvar o próprio registro da reunião antes de chamar as APIs externas do Zoom e do Google Calendar.

- Volume da base de código sem equipe de desenvolvedores: 205.104 linhas de TypeScript/TSX (121.853 no LMS, em 532 arquivos, e 83.251 no CRM, em 367 arquivos)
- Superfície de API e fachada para agentes: 47 roteadores e 288 procedures tRPC no LMS (93 ferramentas) + 334 ferramentas no registro do CRM (257 expostas na fachada)
- Escala de um único bug invisível de CRLF: 138 de 146 aulas práticas com zero envios bem-sucedidos por causa de um caractere `\r` no final, ao fazer o parsing no servidor
- Colheita da revisão adversarial antes do lançamento: 14 vulnerabilidades perigosas na primeira leva + 5 defeitos introduzidos pelas próprias correções
Escrever um sistema de grande porte usando agentes de IA, sem um time de programadores, hoje se tornou uma tarefa barata. O que sai caro é tudo que começa na manhã seguinte à demo: proteção contra a falsificação do histórico pelo cliente, bloqueios no banco de dados por duplo clique dos gestores e caracteres especiais invisíveis em segredos.
Os meus dois sistemas — a plataforma de ensino LMS (121.853 linhas de TS/TSX, Next 16.2.
6, React 19, Drizzle ORM, 58 migrations, 16 trilhas, 211 aulas e 55 simuladores) e o CRM que roda ao lado (83.251 linhas, Prisma 7.6, 48 modelos de banco de dados, BullMQ e quatro agentes de IA conectados) — vivem na mesma instância de PostgreSQL.
Construímos nosso próprio stack por dois motivos: LMSs prontos não têm o primitivo em que a rede neural primeiro interpreta um prompt fraco do estudante e só depois dá o coaching com base em uma premissa oculta, e o CRM precisava, sem duplicar bancos de dados, se encaixar sobre um funil que já funcionava em um bot do Telegram. A propósito, repintar 289 variáveis de cor ao bifurcar o CRM levou uma noite, enquanto a limpeza dos dados de demonstração herdados (205 solicitações de teste, o fallback "margem 38%" e selos falsos de "Tarefas 7") se arrastou por dois meses.
Como um caractere invisível de retorno de carro \r quebrou 138 aulas,
a deduplicação e o OAuth?
O bug mais traiçoeiro do LMS não deixava erros nos logs. Os estudantes viam o bloco `:::practice` renderizado normalmente, clicavam em "Enviar" e recebiam "Bloco prático não encontrado". As estatísticas do banco mostraram que em 138 de 146 aulas havia exatamente zero envios bem-sucedidos. O conteúdo era criado no Windows com quebras de linha `CRLF` (`\r\n`): o frontend normalizava o texto antes de renderizar, mas o parser do servidor cortava por `\n`, deixando um `\r` invisível no final da linha:
```typescript // Sem normalização, o \r residual quebra a âncora $ em JS, // já que o ponto (.) não captura \r, e o campo task é interpretado como vazio: md = md.replace(/\r\n?/g, "\n"); const match = line.match(/^(task|starter|goal|model|system)\s:\s?(.)$/i); ```
O mesmo `\r` alterava o hash de controle do texto na divisão em chunks, forçando o sistema a pagar de novo pela geração de embeddings, e, pela terceira vez, apareceu no final de um segredo no `.env`, fazendo o Google OAuth retornar `invalid_client` (detectado por uma verificação do tamanho da string `awk -F= '/^GOOGLE_/ {print $1, length($2)}' .env` — 36 bytes em vez de 35).
Por que não se pode confiar nos dados vindos do navegador e como
funciona o registro de 334 ferramentas do CRM?
A revisão adversarial antes do lançamento revelou 14 defeitos graves, e a verificação dos patches escritos para corrigi-los revelou mais 5. Na primeira versão da prática, o próprio navegador enviava ao servidor o texto da tarefa e o array `history`, permitindo substituir a resposta pelo DevTools, e 8 dos 55 simuladores eram aprovados com 71 a 90 pontos simplesmente copiando o enunciado como resposta. Transferimos a montagem da premissa e a reconstrução das 6 últimas mensagens estritamente para o servidor, e aumentamos o timeout do avaliador de 30 para 60 segundos.
No CRM, toda a arquitetura é construída em torno de um único registro de 334 ferramentas (257 expostas na fachada para agentes, enquanto 77 ferramentas de sistema ficam bloqueadas pela flag `facadeExposed: false`). Toda chamada passa por um único pipeline `invoke()`: a identidade `acting_user` é extraída apenas da sessão (diferenciando os erros `missing` e `invalid`, quando é enviado um ID de sessão `api-...` ou um ID negativo de chat em grupo), e os argumentos são higienizados pela regex `SECRET_KEY_RE` antes de serem gravados no `audit_log`.
Por que dividir um único botão de lead em três estados e como pegar reuniões órfãs no
Zoom?
Em vendas reais, o vendedor escreve para o cliente no Telegram pessoal, enquanto, em paralelo, o mesmo contato é aquecido pelo bot. Por isso, foi preciso dividir um único botão "Assumir o lead" em três estados independentes: "Assumindo" (responsável), "Contato feito" (fato do primeiro contato) e "Interceptado" (o bot se cala e cede a conversa ao humano). Para que dois gestores não sobrescrevam o cartão com um duplo clique, o CRM localiza o registro mais antigo pelo `telegram_id` e o bloqueia com `SELECT ...
FOR UPDATE`. E, ao criar reuniões no Zoom e no Google Calendar, a primeira linha é sempre um `INSERT` no próprio banco com status `creating` e os campos `zoom_state` / `gcal_state`, para que, em caso de falha da API externa, não fiquem reuniões órfãs não registradas na nuvem.
Essa mecânica de simuladores interativos e registros para agentes funciona dentro da [Hamidun Academy](https://academy.hamidun.com) (trial de 72 horas disponível) e no ecossistema da [Nomad AI](https://nomadnet.ai) / [agent.nomadnet.ai](https://agent.nomadnet.ai), e a arquitetura dos nossos CRM e LMS é detalhada no [Vibe Club](https://hamidun.com/club).
- 27 de out. de 2026Arquitetura ~/.claude com mais de 200 skills: como manter o system prompt em 50 mil tokens e pagar $0,15 por sessão
- 27 de out. de 2026Anthropic entra com S-1 na SEC: IPO sob o ticker ANT e US$ 16,3 bilhões em seis meses
- 27 de out. de 2026Do prompting à engenharia de contexto: 6 técnicas práticas que economizam horas de ajuste de respostas
- 27 de out. de 2026Qwen3.8 tentou subir um arquivo para a nuvem da Alibaba em Pequim de dentro do sandbox da Cohere
Perguntas frequentes sobre a notícia
Como um caractere invisível de retorno de carro \r quebrou 138 aulas, a deduplicação e o OAuth?
O bug mais traiçoeiro do LMS não deixava erros nos logs. Os estudantes viam o bloco `:::practice` renderizado normalmente, clicavam em "Enviar" e recebiam "Bloco prático não encontrado". As estatísticas do banco mostraram que em 138 de 146 aulas havia exatamente zero envios bem-sucedidos. O conteúdo era criado no Windows com quebras de linha `CRLF` (`\r\n`): o frontend normalizava o texto antes de renderizar, mas o parser do servidor cortava por `\n`, deixando um `\r` invisível no final da linha: ```typescript // Sem normalização, o \r residual quebra a âncora $ em JS, // j
Por que dividir um único botão de lead em três estados e como pegar reuniões órfãs no Zoom?
Em vendas reais, o vendedor escreve para o cliente no Telegram pessoal, enquanto, em paralelo, o mesmo contato é aquecido pelo bot. Por isso, foi preciso dividir um único botão "Assumir o lead" em três estados independentes: "Assumindo" (responsável), "Contato feito" (fato do primeiro contato) e "Interceptado" (o bot se cala e cede a conversa ao humano). Para que dois gestores não sobrescrevam o cartão com um duplo clique, o CRM localiza o registro mais antigo pelo `telegram_id` e o bloqueia com `SELECT ... FOR UPDATE`. E, ao criar reuniões no Zoom e no Google Calendar, a p
Delegate Deep Research, Coding & Multi-Step Workflows to Nomad Agent
Deploy autonomous task execution with Nomad Agent (agent.nomadnet.ai) — automated web research, data synthesis, code generation, and MCP tool orchestration.
O essencial da IA — uma vez por semana
Sete histórias que realmente importaram, escolhidas a dedo. Sem ruído nem releases.
Pronto! Verifique seu e-mail para a confirmação.