Opinião

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.

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
Hamidun News.
Ouvir artigo
Ficha técnica e números-chave
ParâmetroDados
Editor-chefe
Dado-chave #0Volume 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 agentes47 roteadores e 288 procedures tRPC no LMS (93 ferramentas) + 334 ferramentas no registro do CRM (257 expostas na fachada)
Dado-chave #2Colheita da revisão adversarial antes do lançamento: 14 vulnerabilidades perigosas na primeira leva + 5 defeitos introduzidos pelas próprias correções
  • 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).

História

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

ZK
Zhemal Khamidun — CPO da Alpina Digital, gerente de produto do AlpinaGPT. Cria produtos de IA desde 2019. O Hamidun News é um projeto pessoal que virou um agregador em produção com mais de 6.000 leitores.
✦ Autonomous AI Execution · Nomad Agent

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.

Launch Nomad Agent →Nomad AI Workspace
O que você acha?
Carregando comentários…