{"path":"/public_html/cases/voxia.html","name":"voxia.html","size":31572,"extension":".html","modified":"2026-10-16T13:24:09.058617367Z","mode":420,"isDir":false,"isSymlink":false,"type":"text","content":"\u003c!doctype html\u003e\n\u003chtml lang=\"pt-BR\"\u003e\n\u003chead\u003e\n\u003cmeta charset=\"utf-8\"\u003e\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n\u003ctitle\u003eVOXIA — Estudo de caso | Giulia AI Engineering\u003c/title\u003e\n\u003cmeta name=\"description\" content=\"Plataforma de atendimento omnichannel com IA em produção em 15 dias: arquitetura, controles de IA, LGPD e governança de entrega.\"\u003e\n\u003clink rel=\"preconnect\" href=\"https://fonts.googleapis.com\"\u003e\n\u003clink rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin\u003e\n\u003clink href=\"https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,700;12..96,800\u0026family=Instrument+Sans:wght@400;500;600\u0026family=IBM+Plex+Mono:wght@400;500\u0026display=swap\" rel=\"stylesheet\"\u003e\n\u003cstyle\u003e\n:root { --bg: #F2F5F6; --surface: #FFFFFF; --surface-2: #E8EEF0; --fg: #0F1E26; --fg-muted: #4A5E69; --line: #CBD7DC; --teal: #0A6B6B; --teal-soft: #D5EBEA; --amber: #A8480F; --amber-soft: #F6E2D2; --font-display: \"Bricolage Grotesque\", \"Segoe UI\", system-ui, sans-serif; --font-body: \"Instrument Sans\", \"Segoe UI\", system-ui, sans-serif; --font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, monospace; }\n@media (prefers-color-scheme: dark) { :root:not([data-theme=\"light\"]) { --bg: #0A1318; --surface: #111D24; --surface-2: #16262F; --fg: #E6EEF1; --fg-muted: #93A9B4; --line: #26394380; --teal: #4CC9BE; --teal-soft: #12353540; --amber: #F2A065; --amber-soft: #3A2616; color-scheme: dark; } }\n:root[data-theme=\"dark\"] { --bg: #0A1318; --surface: #111D24; --surface-2: #16262F; --fg: #E6EEF1; --fg-muted: #93A9B4; --line: #26394380; --teal: #4CC9BE; --teal-soft: #12353540; --amber: #F2A065; --amber-soft: #3A2616; color-scheme: dark; }\n* { box-sizing: border-box; }\nbody { background: var(--bg); color: var(--fg); font-family: var(--font-body); font-size: 16px; line-height: 1.6; margin: 0; }\n.wrap { max-width: 1080px; margin: 0 auto; padding-inline: 20px; }\na { color: var(--teal); }\n:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }\n.top { padding-block: 56px 40px; border-bottom: 1px solid var(--line); }\n.brand { font-family: var(--font-mono); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--fg-muted); margin: 0 0 28px; }\nh1 { font-family: var(--font-display); font-weight: 800; font-size: clamp(2.2rem, 6.2vw, 4.4rem); line-height: 1.02; letter-spacing: -.02em; margin: 0 0 22px; max-width: 18ch; text-wrap: balance; }\nh1 em { font-style: normal; color: var(--teal); }\n.lede { font-size: clamp(1.05rem, 2vw, 1.25rem); color: var(--fg-muted); max-width: 62ch; margin: 0; }\n.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0; margin: 40px 0 0; border-top: 1px solid var(--line); }\n.facts div { padding: 18px 18px 6px 0; display: flex; flex-direction: column; gap: 6px; }\n.facts dt { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-muted); order: 2; }\n.facts dd { margin: 0; font-family: var(--font-display); font-weight: 700; font-size: 2.2rem; line-height: 1; font-variant-numeric: tabular-nums; }\nsection { padding-block: 56px; border-bottom: 1px solid var(--line); }\n.eyebrow { font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--teal); margin: 0 0 10px; }\n.eyebrow.ctl { color: var(--amber); }\nh2 { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.6rem, 3.6vw, 2.5rem); line-height: 1.1; letter-spacing: -.015em; margin: 0 0 14px; max-width: 24ch; text-wrap: balance; }\n.intro { color: var(--fg-muted); max-width: 66ch; margin: 0 0 32px; }\nh3 { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; margin: 0 0 6px; }\np { margin: 0 0 12px; }\n.layers { display: flex; flex-direction: column; gap: 10px; }\n.layer { display: grid; grid-template-columns: 170px 1fr; gap: 16px; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 14px 16px; align-items: start; }\n.layer \u003e b { font-family: var(--font-mono); font-size: 12px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-muted); padding-top: 4px; }\n.chips { display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; }\n.chip { font-family: var(--font-mono); font-size: 12.5px; padding: 4px 10px; border-radius: 4px; background: var(--teal-soft); color: var(--teal); }\n.chip.ctl { background: var(--amber-soft); color: var(--amber); }\n.pipe { list-style: none; counter-reset: p; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }\n.pipe li { counter-increment: p; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 14px; font-size: 14px; position: relative; min-width: 0; }\n.pipe li::before { content: counter(p); font-family: var(--font-mono); font-size: 11px; color: var(--amber); display: block; margin-bottom: 6px; }\n.pipe li b { display: block; font-family: var(--font-display); font-size: 1.05rem; margin-bottom: 4px; }\n.pipe li.llm { background: var(--amber-soft); }\n.note { font-size: 14px; color: var(--fg-muted); margin-top: 14px; max-width: 70ch; }\n.stack { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 28px 40px; }\n.stack h3 { font-size: .8rem; font-family: var(--font-mono); font-weight: 500; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-muted); border-bottom: 1px solid var(--line); padding-bottom: 8px; margin-bottom: 12px; }\n.stack ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }\n.stack li { font-size: 15px; }\n.stack li code { font-family: var(--font-mono); font-size: 13.5px; font-weight: 500; color: var(--teal); }\n.stack li span { color: var(--fg-muted); }\n.caps { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }\n.cap { background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 20px; min-width: 0; }\n.cap .tag { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--teal); margin-bottom: 8px; }\n.cap ul { margin: 10px 0 0; padding-left: 18px; color: var(--fg-muted); font-size: 14.5px; }\n.cap li { margin-bottom: 4px; }\n.ctrl-group { margin-top: 40px; }\n.ctrl-group \u003e h3 { font-size: 1.3rem; display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }\n.ctrl-group \u003e h3 span { font-family: var(--font-mono); font-size: 12px; font-weight: 400; color: var(--amber); letter-spacing: .06em; }\n.ctrl-group \u003e p { color: var(--fg-muted); max-width: 64ch; }\n.ctrls { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 0 32px; margin-top: 14px; }\n.ctrl { padding: 14px 0; border-top: 1px solid var(--line); min-width: 0; }\n.ctrl b { display: block; font-weight: 600; margin-bottom: 2px; }\n.ctrl p { font-size: 14.5px; color: var(--fg-muted); margin: 0; }\n.kind { display: inline-block; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; padding: 1px 7px; border-radius: 3px; background: var(--amber-soft); color: var(--amber); margin-left: 6px; vertical-align: 1px; white-space: nowrap; }\n.jira { background: var(--surface); border: 1px solid var(--line); border-radius: 8px; padding: 24px; }\n.jira-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; }\n.jira-big { font-family: var(--font-display); font-weight: 800; font-size: clamp(2.4rem, 7vw, 3.6rem); line-height: 1; font-variant-numeric: tabular-nums; }\n.jira-of { color: var(--fg-muted); font-size: 1.05rem; }\n.jira-pct { font-family: var(--font-mono); font-size: 1.1rem; color: var(--teal); }\n.jira-bar { height: 12px; background: var(--surface-2); border-radius: 3px; margin: 16px 0 20px; overflow: hidden; }\n.jira-bar i { display: block; height: 100%; background: var(--teal); }\n.jira-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin: 0; }\n.jira-stats div { display: flex; flex-direction: column; gap: 2px; }\n.jira-stats dd { margin: 0; font-family: var(--font-display); font-weight: 700; font-size: 1.8rem; font-variant-numeric: tabular-nums; }\n.jira-stats dt { font-size: 14px; color: var(--fg-muted); order: 2; }\n.adr { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }\n.adr article { background: var(--surface-2); border-radius: 6px; padding: 20px; min-width: 0; }\n.adr article p { color: var(--fg-muted); font-size: 14.5px; margin: 0; }\n.adr article h3 { font-size: 1.05rem; }\nfooter { padding-block: 40px 64px; color: var(--fg-muted); font-size: 14px; }\nfooter p { max-width: 70ch; }\n@media (max-width: 760px) { .layer { grid-template-columns: 1fr; gap: 8px; } .pipe { grid-template-columns: 1fr; } section { padding-block: 40px; } }\n\u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cheader class=\"top\"\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"brand\"\u003eGiulia AI Engineering · Estudo de caso\u003c/p\u003e\n \u003ch1\u003eUma plataforma de atendimento com IA em produção em 15 dias, \u003cem\u003ecom a IA sob controle\u003c/em\u003e\u003c/h1\u003e\n \u003cp class=\"lede\"\u003eAtendimento omnichannel, RAG, painel em tempo real e disparos em massa para uma campanha eleitoral de 2026 (candidato a deputado federal, eleito). Foram 15 dias até entrar em produção e 33 dias de projeto, com a plataforma operando e evoluindo durante a campanha. Este documento descreve a stack e os controles que mantêm a IA, os dados e a infraestrutura dentro de limites verificáveis, sem mostrar uma única tela. Foi construído com base no ecossistema GIULIA AI de engenharia e gestão: SDD antes do código, TDD, boards Jira e um agente de desenvolvimento próprio.\u003c/p\u003e\n \u003cdl class=\"facts\"\u003e\n \u003cdiv\u003e\u003cdd\u003e15\u003c/dd\u003e\u003cdt\u003edias até a produção\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e33\u003c/dd\u003e\u003cdt\u003edias de projeto\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e4\u003c/dd\u003e\u003cdt\u003emarcos entregues\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e11\u003c/dd\u003e\u003cdt\u003epacotes no monorepo\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e2.300+\u003c/dd\u003e\u003cdt\u003ecasos de teste automatizados\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e203\u003c/dd\u003e\u003cdt\u003earquivos de teste\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e132 mil\u003c/dd\u003e\u003cdt\u003elinhas de código, 48 mil delas de teste\u003c/dt\u003e\u003c/div\u003e\n \u003c/dl\u003e\n \u003c/div\u003e\n\u003c/header\u003e\n\n\u003cmain\u003e\n\u003csection\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"eyebrow\"\u003eArquitetura\u003c/p\u003e\n \u003ch2\u003eSete camadas, cada uma com um único trabalho\u003c/h2\u003e\n \u003cp class=\"intro\"\u003eRedis guarda filas e nunca a verdade. O estado vive no PostgreSQL. Toda chamada de LLM passa por uma fila, nunca direto do webhook. Em amarelo, o que existe para impor limites.\u003c/p\u003e\n \u003cdiv class=\"layers\"\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eCanais\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003eWhatsApp Cloud API\u003c/span\u003e\u003cspan class=\"chip\"\u003eWhatsApp via Evolution\u003c/span\u003e\u003cspan class=\"chip\"\u003eInstagram Direct\u003c/span\u003e\u003cspan class=\"chip\"\u003eREST + Webhooks externos\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eBorda\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003eCloudflare Tunnel\u003c/span\u003e\u003cspan class=\"chip\"\u003eNginx\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eWAF\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003esem porta pública\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eheaders CSP / HSTS\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eAplicação\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003eFastify 5\u003c/span\u003e\u003cspan class=\"chip\"\u003eZod\u003c/span\u003e\u003cspan class=\"chip\"\u003eSSE via LISTEN/NOTIFY\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eRBAC + TOTP\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003evalidação de tenant\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eFilas e workers\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003eBullMQ\u003c/span\u003e\u003cspan class=\"chip\"\u003eai-jobs\u003c/span\u003e\u003cspan class=\"chip\"\u003eaudio-jobs\u003c/span\u003e\u003cspan class=\"chip\"\u003ebroadcast:\u0026lt;número\u0026gt;\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003ejobs idempotentes\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003erate limit por número\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eInteligência\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003eRAG com pgvector\u003c/span\u003e\u003cspan class=\"chip\"\u003efailover multi-provedor\u003c/span\u003e\u003cspan class=\"chip\"\u003efaster-whisper\u003c/span\u003e\u003cspan class=\"chip\"\u003effmpeg\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eguardrail de saída\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eblindagem global\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eDados\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003ePostgreSQL 16\u003c/span\u003e\u003cspan class=\"chip\"\u003eDrizzle ORM\u003c/span\u003e\u003cspan class=\"chip\"\u003eréplica de leitura\u003c/span\u003e\u003cspan class=\"chip\"\u003eCloudflare R2\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eRow Level Security\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eauditoria append-only\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"layer\"\u003e\u003cb\u003eOperação\u003c/b\u003e\u003cdiv class=\"chips\"\u003e\u003cspan class=\"chip\"\u003eDocker Compose\u003c/span\u003e\u003cspan class=\"chip\"\u003eGitHub Actions\u003c/span\u003e\u003cspan class=\"chip\"\u003eAWS Lightsail\u003c/span\u003e\u003cspan class=\"chip\"\u003epino + prom-client\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003eTailscale (acesso admin)\u003c/span\u003e\u003cspan class=\"chip ctl\"\u003ebackup + restore testado\u003c/span\u003e\u003c/div\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\u003c/section\u003e\n\n\u003csection\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"eyebrow\"\u003eStack\u003c/p\u003e\n \u003ch2\u003eLinguagem única no núcleo, ferramenta certa em cada borda\u003c/h2\u003e\n \u003cp class=\"intro\"\u003eO sistema é limitado por I/O (LLM de 2 a 5 segundos, API da Meta, rate limit por número). A linguagem não é o gargalo, então o núcleo inteiro é TypeScript e o ecossistema de filas e SDKs é o de Node.\u003c/p\u003e\n \u003cdiv class=\"stack\"\u003e\n \u003cdiv\u003e\u003ch3\u003eLinguagens e runtime\u003c/h3\u003e\u003cul\u003e\n \u003cli\u003e\u003ccode\u003eTypeScript\u003c/code\u003e \u003cspan\u003estrict + noUncheckedIndexedAccess\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eNode.js 20\u003c/code\u003e \u003cspan\u003eLTS\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003ePython\u003c/code\u003e \u003cspan\u003esomente dentro do container do faster-whisper\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eSQL\u003c/code\u003e \u003cspan\u003emigrations versionadas, políticas RLS\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003ch3\u003eBackend\u003c/h3\u003e\u003cul\u003e\n \u003cli\u003e\u003ccode\u003eFastify 5\u003c/code\u003e \u003cspan\u003eAPI, webhooks, SSE\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eDrizzle ORM\u003c/code\u003e \u003cspan\u003eschema e migrations tipadas\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eZod\u003c/code\u003e \u003cspan\u003evalidação na borda de cada rota\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eBullMQ + ioredis\u003c/code\u003e \u003cspan\u003efilas com backoff e limiter\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003epino\u003c/code\u003e \u003cspan\u003elogs estruturados\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003ch3\u003eDados\u003c/h3\u003e\u003cul\u003e\n \u003cli\u003e\u003ccode\u003ePostgreSQL 16\u003c/code\u003e \u003cspan\u003eprimário + réplica de streaming\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003epgvector\u003c/code\u003e \u003cspan\u003eembeddings 1536 dimensões, índice IVFFlat\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eRedis 7\u003c/code\u003e \u003cspan\u003eTLS, AOF, limite de memória\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eCloudflare R2\u003c/code\u003e \u003cspan\u003emídia, WAL e dumps\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003ch3\u003eIA\u003c/h3\u003e\u003cul\u003e\n \u003cli\u003e\u003ccode\u003eOpenAI · Azure · Gemini · Grok · OpenRouter · Claude · LLM local\u003c/code\u003e \u003cspan\u003efailover configurável no banco\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003efaster-whisper\u003c/code\u003e \u003cspan\u003etranscrição de áudio em CPU\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003effmpeg\u003c/code\u003e \u003cspan\u003equadros e áudio de vídeo\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eRAG\u003c/code\u003e \u003cspan\u003echunking por Q\u0026amp;A para dados tabulares\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003ch3\u003eFrontend\u003c/h3\u003e\u003cul\u003e\n \u003cli\u003e\u003ccode\u003eReact 18 + Vite\u003c/code\u003e \u003cspan\u003epainel de atendimento e analytics\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eReact Router 7\u003c/code\u003e \u003cspan\u003erotas protegidas por papel\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eRecharts\u003c/code\u003e \u003cspan\u003edashboard analítico\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eEventSource\u003c/code\u003e \u003cspan\u003etempo real abaixo de 500 ms\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003ch3\u003eQualidade e entrega\u003c/h3\u003e\u003cul\u003e\n \u003cli\u003e\u003ccode\u003eVitest + supertest\u003c/code\u003e \u003cspan\u003etestes unitários e de rota\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eBiome\u003c/code\u003e \u003cspan\u003elint e formatação\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eTurborepo + pnpm\u003c/code\u003e \u003cspan\u003emonorepo com 4 apps e 11 pacotes\u003c/span\u003e\u003c/li\u003e\n \u003cli\u003e\u003ccode\u003eDocker Compose + GitHub Actions\u003c/code\u003e \u003cspan\u003ebuild, smoke test e rollback\u003c/span\u003e\u003c/li\u003e\u003c/ul\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\u003c/section\u003e\n\n\u003csection\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"eyebrow\"\u003eRecursos desenvolvidos\u003c/p\u003e\n \u003ch2\u003eO que a plataforma faz, por frente de entrega\u003c/h2\u003e\n \u003cp class=\"intro\"\u003eQuatro marcos contratuais, entregues em sequência e depois sustentados em plantão.\u003c/p\u003e\n \u003cdiv class=\"caps\"\u003e\n \u003carticle class=\"cap\"\u003e\u003cdiv class=\"tag\"\u003eMarco 1 · Mensageria e IA\u003c/div\u003e\u003ch3\u003eAtendimento automático conversacional\u003c/h3\u003e\u003cp\u003eCada número fala por um adaptador próprio, sem cruzar canais. Mensagens picadas são agrupadas por debounce antes de ir à IA.\u003c/p\u003e\u003cul\u003e\u003cli\u003eWebhooks bidirecionais com filas e idempotência\u003c/li\u003e\u003cli\u003eTranscrição de áudio e leitura de imagem e vídeo\u003c/li\u003e\u003cli\u003eRAG sobre a base de conhecimento da campanha, com upload de PDF, TXT, MD e CSV\u003c/li\u003e\u003cli\u003eExtração estruturada em JSON (nome, bairro, intenção)\u003c/li\u003e\u003c/ul\u003e\u003c/article\u003e\n \u003carticle class=\"cap\"\u003e\u003cdiv class=\"tag\"\u003eMarco 2 · Omnichannel\u003c/div\u003e\u003ch3\u003eAtendimento humano com resumo por IA\u003c/h3\u003e\u003cp\u003eO painel unifica WhatsApp e Instagram e passa a conversa de bot para humano com contexto.\u003c/p\u003e\u003cul\u003e\u003cli\u003eRoteamento por departamento e transbordo com resumo\u003c/li\u003e\u003cli\u003eAgenda integrada ao Google Calendar\u003c/li\u003e\u003cli\u003eWhite label: logo, cores e domínio por campanha\u003c/li\u003e\u003cli\u003eComposer com texto, emoji, foto, áudio e documento\u003c/li\u003e\u003c/ul\u003e\u003c/article\u003e\n \u003carticle class=\"cap\"\u003e\u003cdiv class=\"tag\"\u003eMarco 3 · Inteligência\u003c/div\u003e\u003ch3\u003eDashboard analítico e monitoramento\u003c/h3\u003e\u003cp\u003eDados geográficos de qualquer origem são normalizados para o mesmo cadastro de bairros.\u003c/p\u003e\u003cul\u003e\u003cli\u003eDemografia, intenção de voto, sentimento e cruzamentos regionais\u003c/li\u003e\u003cli\u003eÍndice de engajamento calculado por eleitor\u003c/li\u003e\u003cli\u003eMonitoramento de grupos de WhatsApp com resumo diário por e-mail\u003c/li\u003e\u003cli\u003eAlertas de crise e de desinformação por WhatsApp e e-mail\u003c/li\u003e\u003c/ul\u003e\u003c/article\u003e\n \u003carticle class=\"cap\"\u003e\u003cdiv class=\"tag\"\u003eMarco 4 · Escala\u003c/div\u003e\u003ch3\u003eDisparos em massa e integração externa\u003c/h3\u003e\u003cp\u003eUma fila por número, com taxa controlada e pausa automática quando a qualidade do template cai.\u003c/p\u003e\u003cul\u003e\u003cli\u003eBroadcast segmentado com templates aprovados\u003c/li\u003e\u003cli\u003ePausa automática por queda de qualidade do número\u003c/li\u003e\u003cli\u003eRespeito à janela de 24 horas da Meta\u003c/li\u003e\u003cli\u003eREST e webhooks para sistemas do cliente\u003c/li\u003e\u003c/ul\u003e\u003c/article\u003e\n \u003carticle class=\"cap\"\u003e\u003cdiv class=\"tag\"\u003eTransversal · IA\u003c/div\u003e\u003ch3\u003eCamada de IA configurável\u003c/h3\u003e\u003cp\u003eProvedor, prompt e custo são dados no banco, não código fixo.\u003c/p\u003e\u003cul\u003e\u003cli\u003eFailover sequencial entre sete provedores, 1 tentativa cada, tudo logado\u003c/li\u003e\u003cli\u003ePrompts versionados e imutáveis, com cascata global, campanha e ponto de uso\u003c/li\u003e\u003cli\u003eAnálise de vídeo (quadros e áudio) com liga/desliga por campanha\u003c/li\u003e\u003cli\u003eTelas internas de consumo de LLM e de logs de interação\u003c/li\u003e\u003c/ul\u003e\u003c/article\u003e\n \u003carticle class=\"cap\"\u003e\u003cdiv class=\"tag\"\u003eTransversal · Plataforma\u003c/div\u003e\u003ch3\u003eMulti-tenant e administração\u003c/h3\u003e\u003cp\u003eCada campanha é um tenant isolado, criado só por um papel de plataforma.\u003c/p\u003e\u003cul\u003e\u003cli\u003eOnboarding guiado em etapas e gestão de membros por papel\u003c/li\u003e\u003cli\u003eGestão de canal: pareamento, status e templates\u003c/li\u003e\u003cli\u003eReprocessamento de conversas paradas por script reutilizável\u003c/li\u003e\u003cli\u003eE-mail transacional com dois provedores\u003c/li\u003e\u003c/ul\u003e\u003c/article\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\u003c/section\u003e\n\n\u003csection\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"eyebrow ctl\"\u003eControles e proteções\u003c/p\u003e\n \u003ch2\u003eOnde a IA pode decidir, e onde só o código decide\u003c/h2\u003e\n \u003cp class=\"intro\"\u003eO ponto central do projeto: qualquer coisa irreversível ou juridicamente sensível é um handler determinístico. A LLM apenas sinaliza por marcador. Não existe tool calling, então ela não consegue acionar nada.\u003c/p\u003e\n \u003ch3 style=\"margin-bottom:12px\"\u003eOrdem fixa de entrada, igual para todo canal\u003c/h3\u003e\n \u003col class=\"pipe\"\u003e\n \u003cli\u003e\u003cb\u003eAuditoria\u003c/b\u003eregistra a mensagem bruta antes de qualquer decisão\u003c/li\u003e\n \u003cli\u003e\u003cb\u003eOpt-out\u003c/b\u003edescadastro por palavra-chave ou frase natural\u003c/li\u003e\n \u003cli\u003e\u003cb\u003eAnti-injeção\u003c/b\u003epadrões de ataque ao prompt são barrados\u003c/li\u003e\n \u003cli\u003e\u003cb\u003eDebounce\u003c/b\u003eagrupa mensagens picadas\u003c/li\u003e\n \u003cli class=\"llm\"\u003e\u003cb\u003eIA\u003c/b\u003esó chega aqui o que passou por tudo antes\u003c/li\u003e\n \u003c/ol\u003e\n \u003cp class=\"note\"\u003eNenhum canal e nenhum estado de conversa (humano, pausada, número pausado) pula a auditoria ou o opt-out. Mensagem que casou padrão de injeção nunca é enviada à LLM.\u003c/p\u003e\n\n \u003cdiv class=\"ctrl-group\"\u003e\n \u003ch3\u003eBlindagem da IA \u003cspan\u003ecompliance eleitoral\u003c/span\u003e\u003c/h3\u003e\n \u003cp\u003eRegras de comportamento que valem para todo agente que toca o atendimento.\u003c/p\u003e\n \u003cdiv class=\"ctrls\"\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eBlindagem global antes da persona \u003cspan class=\"kind\"\u003eimutável\u003c/span\u003e\u003c/b\u003e\u003cp\u003eInstruções de segurança são prefixadas em todo prompt. A campanha não consegue sobrescrever e a API recusa a tentativa.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eGuardrail de saída \u003cspan class=\"kind\"\u003efail-closed\u003c/span\u003e\u003c/b\u003e\u003cp\u003eToda resposta é classificada antes de enviar, em dois estágios. Se o classificador falha, sai um texto seguro, nunca a resposta não verificada.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eFatos só do RAG e da persona validada \u003cspan class=\"kind\"\u003eanti-alucinação\u003c/span\u003e\u003c/b\u003e\u003cp\u003ePergunta fora da base vira marcador, texto fixo e demanda registrada. O modelo não completa com conhecimento próprio.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eOpt-out determinístico \u003cspan class=\"kind\"\u003edeterminístico\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSempre o mesmo handler e o mesmo texto, com data gravada. Quem saiu nunca entra em disparo. Só volta por mensagem espontânea.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eTemas sensíveis em código \u003cspan class=\"kind\"\u003edeterminístico\u003c/span\u003e\u003c/b\u003e\u003cp\u003eAssuntos de alto risco jurídico e reputacional têm resposta fixa na entrada e na saída, sem depender do modelo.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eSilêncio eleitoral \u003cspan class=\"kind\"\u003edeterminístico\u003c/span\u003e\u003c/b\u003e\u003cp\u003eNo período definido pela legislação, a IA é substituída por silêncio total em todos os canais.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eTemperatura baixa no atendimento \u003cspan class=\"kind\"\u003eteste falha\u003c/span\u003e\u003c/b\u003e\u003cp\u003eAtendimento a 0,2 e extração a 0. Um teste automatizado quebra o build se o valor subir.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eTextos legais versionados \u003cspan class=\"kind\"\u003eimutável\u003c/span\u003e\u003c/b\u003e\u003cp\u003eBoas-vindas, descadastro, recusas e termos ficam em catálogo versionado. Nunca no código, nunca parafraseados pela IA.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eRed team como gate de PR \u003cspan class=\"kind\"\u003eprocesso\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSuíte de 57 casos de ataque à resposta, executada por canal. Mudança em prompt, guardrail ou pipeline exige rodá-la. Antes de cada release, roda contra o LLM real.\u003c/p\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"ctrl-group\"\u003e\n \u003ch3\u003eDados e privacidade \u003cspan\u003eLGPD\u003c/span\u003e\u003c/h3\u003e\n \u003cp\u003eDados pessoais de eleitores tratados como ativo regulado.\u003c/p\u003e\n \u003cdiv class=\"ctrls\"\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eAuditoria append-only \u003cspan class=\"kind\"\u003esem UPDATE/DELETE\u003c/span\u003e\u003c/b\u003e\u003cp\u003eCada mensagem de entrada e de saída gera uma linha com payload bruto, contexto do RAG, versão do prompt, provedor e modelo.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003ePseudonimização no expurgo \u003cspan class=\"kind\"\u003eexceção única\u003c/span\u003e\u003c/b\u003e\u003cp\u003eA única operação que altera a auditoria é a função de pseudonimização do expurgo, projetada para esse fim.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eIdentificadores redigidos no payload \u003cspan class=\"kind\"\u003eminimização\u003c/span\u003e\u003c/b\u003e\u003cp\u003eValores como identificadores de contato e nome de perfil ficam com a estrutura preservada e o valor mascarado.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eAviso de dado pessoal na base de conhecimento \u003cspan class=\"kind\"\u003econfirmação explícita\u003c/span\u003e\u003c/b\u003e\u003cp\u003eTelefone ou e-mail em documento enviado ao RAG bloqueia o envio até confirmação, e a decisão é registrada.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eSegredos fora do banco e do código \u003cspan class=\"kind\"\u003eenv only\u003c/span\u003e\u003c/b\u003e\u003cp\u003eO banco guarda só o nome da variável de ambiente da credencial. Chaves de auditoria e de provedor ficam exclusivamente no ambiente.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eProjeção explícita de colunas \u003cspan class=\"kind\"\u003eanti-vazamento\u003c/span\u003e\u003c/b\u003e\u003cp\u003eRotas não devolvem linhas inteiras de tabelas com segredo. Um vazamento encontrado em revisão virou regra de varredura.\u003c/p\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"ctrl-group\"\u003e\n \u003ch3\u003eIdentidade, acesso e isolamento \u003cspan\u003emulti-tenant\u003c/span\u003e\u003c/h3\u003e\n \u003cp\u003eQuem pode ver o quê, e onde o banco impõe isso sozinho.\u003c/p\u003e\n \u003cdiv class=\"ctrls\"\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eRow Level Security no PostgreSQL \u003cspan class=\"kind\"\u003edefesa em profundidade\u003c/span\u003e\u003c/b\u003e\u003cp\u003eRole restrito sem privilégio de bypass, trocado por requisição via contexto assíncrono. Não substitui a validação de tenant na aplicação, soma a ela.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eRBAC com papéis de plataforma e de campanha \u003cspan class=\"kind\"\u003edupla checagem\u003c/span\u003e\u003c/b\u003e\u003cp\u003eGuarda no frontend e checagem de novo no backend. Áreas técnicas ficam em leitura para o administrador comum.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eAutenticação em dois fatores \u003cspan class=\"kind\"\u003eTOTP\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSegundo fator por aplicativo, além de convite e definição de senha por e-mail.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eAcesso de suporte cross-tenant \u003cspan class=\"kind\"\u003eautorizado e logado\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSó com autorização dedicada por sessão e com registro. Fora do escopo do RLS de propósito.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eKill-switches por papel de plataforma \u003cspan class=\"kind\"\u003eoperação\u003c/span\u003e\u003c/b\u003e\u003cp\u003eFunções de risco, como guardrail e busca de foto de perfil, só são ligadas ou desligadas por quem tem o papel mais alto, nunca no código.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eCriação de campanha restrita \u003cspan class=\"kind\"\u003esem self-signup\u003c/span\u003e\u003c/b\u003e\u003cp\u003eCadastro público foi removido. Nova campanha nasce só por papel de plataforma.\u003c/p\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"ctrl-group\"\u003e\n \u003ch3\u003eInfraestrutura e resiliência \u003cspan\u003eoperação\u003c/span\u003e\u003c/h3\u003e\n \u003cp\u003eSuperfície de ataque mínima e recuperação ensaiada.\u003c/p\u003e\n \u003cdiv class=\"ctrls\"\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eEntrada pública só por túnel de saída \u003cspan class=\"kind\"\u003ezero porta aberta\u003c/span\u003e\u003c/b\u003e\u003cp\u003eA conexão parte da VM para a borda da Cloudflare. Não há porta 80 ou 443 exposta, nem lista de IPs para manter.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eAcesso administrativo por rede privada \u003cspan class=\"kind\"\u003eTailscale\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSSH público fechado nas máquinas. Réplica e workers falam com o primário por IP privado.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003ePolítica de egress \u003cspan class=\"kind\"\u003eallowlist\u003c/span\u003e\u003c/b\u003e\u003cp\u003eContainers só alcançam LLM, e-mail e canais de mensagem previstos. O desenho está documentado e versionado.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eRedis com TLS e autenticação \u003cspan class=\"kind\"\u003etransporte seguro\u003c/span\u003e\u003c/b\u003e\u003cp\u003eFila protegida, com persistência AOF e limite de memória. Redis é fila, nunca fonte de verdade.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eBackup em camadas \u003cspan class=\"kind\"\u003erestore testado\u003c/span\u003e\u003c/b\u003e\u003cp\u003eRéplica de streaming, arquivamento de WAL, dumps periódicos em armazenamento externo e snapshots antes de cada release. Um backup só conta depois de restaurado.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eDeploy com gate e rollback \u003cspan class=\"kind\"\u003ehealth check\u003c/span\u003e\u003c/b\u003e\u003cp\u003eRelease só passa se migrations e o endpoint de saúde responderem. Falha volta sozinha para a versão anterior.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eJobs idempotentes e limiter por fila \u003cspan class=\"kind\"\u003esem duplicidade\u003c/span\u003e\u003c/b\u003e\u003cp\u003eIdentificador determinístico por entidade evita envio duplicado. Taxa por número protege contra banimento.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eFailover entre provedores de IA \u003cspan class=\"kind\"\u003edisponibilidade\u003c/span\u003e\u003c/b\u003e\u003cp\u003eFalha de um provedor tenta o próximo da ordem ativa. Provou valor em um incidente real de indisponibilidade.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eObservabilidade \u003cspan class=\"kind\"\u003emétricas + logs\u003c/span\u003e\u003c/b\u003e\u003cp\u003eLogs estruturados, métricas Prometheus, painel de filas, monitor externo de saúde e telas internas de custo e interação da IA.\u003c/p\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\u003c/section\u003e\n\n\u003csection\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"eyebrow ctl\"\u003eGovernança de entrega\u003c/p\u003e\n \u003ch2\u003eToda tarefa de código nasce de uma issue, com escopo e critério de pronto\u003c/h2\u003e\n \u003cp class=\"intro\"\u003eO backlog do projeto vive no Jira e cada tarefa de código passa pelo mesmo ciclo. Números extraídos do projeto em 10/10/2026.\u003c/p\u003e\n \u003cdiv class=\"jira\"\u003e\n \u003cdiv class=\"jira-head\"\u003e\n \u003cdiv\u003e\u003cspan class=\"jira-big\"\u003e361\u003c/span\u003e\u003cspan class=\"jira-of\"\u003e de 361 issues encerradas\u003c/span\u003e\u003c/div\u003e\n \u003cspan class=\"jira-pct\"\u003e100%\u003c/span\u003e\n \u003c/div\u003e\n \u003cdiv class=\"jira-bar\" role=\"img\" aria-label=\"361 de 361 issues encerradas, 100 por cento\"\u003e\u003ci style=\"width:100%\"\u003e\u003c/i\u003e\u003c/div\u003e\n \u003cdl class=\"jira-stats\"\u003e\n \u003cdiv\u003e\u003cdd\u003e49\u003c/dd\u003e\u003cdt\u003ebugs registrados e encerrados\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e7\u003c/dd\u003e\u003cdt\u003eépicos encerrados, entre marcos e plantão\u003c/dt\u003e\u003c/div\u003e\n \u003cdiv\u003e\u003cdd\u003e429\u003c/dd\u003e\u003cdt\u003ecommits, cada um citando a issue\u003c/dt\u003e\u003c/div\u003e\n \u003c/dl\u003e\n \u003c/div\u003e\n \u003cdiv class=\"ctrls\" style=\"margin-top:28px\"\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eIssue em andamento antes do código \u003cspan class=\"kind\"\u003egate de início\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSem issue com escopo, arquivos esperados e critério de pronto, a tarefa não começa. Tarefas de código são atribuídas ao agente de IA, e épicos ficam com o responsável humano.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eComentário com evidência ao encerrar \u003cspan class=\"kind\"\u003egate de fim\u003c/span\u003e\u003c/b\u003e\u003cp\u003eArquivos criados, decisões e resultado dos testes entram no comentário de cada issue. Mudar só o status não vale.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eÉpicos por marco e plantão \u003cspan class=\"kind\"\u003evisão de entrega\u003c/span\u003e\u003c/b\u003e\u003cp\u003eSete épicos organizam as issues por marco contratual e pelo plantão, com o escopo de cada um descrito no próprio épico.\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"ctrl\"\u003e\u003cb\u003eDescrição sempre atualizada \u003cspan class=\"kind\"\u003erastreabilidade\u003c/span\u003e\u003c/b\u003e\u003cp\u003eA descrição reflete o escopo realmente entregue, e o histórico liga issue, commit, teste e decisão.\u003c/p\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\u003c/section\u003e\n\n\u003csection\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp class=\"eyebrow\"\u003eDecisões de engenharia\u003c/p\u003e\n \u003ch2\u003eEscolhas que custaram caro não fazer\u003c/h2\u003e\n \u003cdiv class=\"adr\"\u003e\n \u003carticle\u003e\u003ch3\u003eSem tool calling na IA\u003c/h3\u003e\u003cp\u003eA LLM sinaliza por marcador e o código age. Uma ação irreversível nunca depende de um modelo \"se comportar\".\u003c/p\u003e\u003c/article\u003e\n \u003carticle\u003e\u003ch3\u003eSSE em vez de WebSocket\u003c/h3\u003e\u003cp\u003eLISTEN/NOTIFY do PostgreSQL alimenta Server-Sent Events. Menos peças, tempo real abaixo de 500 ms e nenhum serviço externo.\u003c/p\u003e\u003c/article\u003e\n \u003carticle\u003e\u003ch3\u003eRedis é fila, nunca fonte de verdade\u003c/h3\u003e\u003cp\u003eTodo estado vive no PostgreSQL. Os jobs são idempotentes, com identificador por entidade, então reprocessar uma fila nunca duplica um envio.\u003c/p\u003e\u003c/article\u003e\n \u003carticle\u003e\u003ch3\u003ePrompt é dado, não código\u003c/h3\u003e\u003cp\u003eVersionado no banco, imutável depois de publicado, com cascata de escopo. Corrigir um prompt não exige deploy.\u003c/p\u003e\u003c/article\u003e\n \u003carticle\u003e\u003ch3\u003eProvedor de transcrição atrás de interface\u003c/h3\u003e\u003cp\u003eTrocar o serviço de Whisper é mudar uma variável de ambiente. O processamento de áudio fica fora do caminho da LLM.\u003c/p\u003e\u003c/article\u003e\n \u003carticle\u003e\u003ch3\u003eValidação real não usa mock\u003c/h3\u003e\u003cp\u003eMock é válido em teste unitário. Para provar uma integração de ponta a ponta, o teste roda contra o serviço real.\u003c/p\u003e\u003c/article\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n\u003c/section\u003e\n\u003c/main\u003e\n\n\u003cfooter\u003e\n \u003cdiv class=\"wrap\"\u003e\n \u003cp\u003eEste documento descreve decisões de engenharia e controles do sistema em nível de arquitetura. Nomes de pessoas, clientes, endereços de infraestrutura, credenciais e dados de eleitores não fazem parte dele.\u003c/p\u003e\n \u003cp\u003eGiulia AI Engineering Ecosystem · 2026 · \u003ca href=\"/\"\u003egiulia-ai.com\u003c/a\u003e\u003c/p\u003e\n \u003c/div\u003e\n\u003c/footer\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n","link":""}