Synaplan
开源 AI 软件平台,面向企业:自托管 AI 代理,符合 GDPR 的聊天机器人小部件,RAG 知识库,WhatsApp 与 Outlook AI,MCP,多模型路由。支持 Docker、Kubernetes 或在 Germ 托管。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Sistema de gestao financeira de obras - Mello e Travasso Engenharia Civil
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Seu negócio conectado a novas possibilidades.
Plataforma para gerenciar a empresa, encontrar profissionais confiáveis e descobrir novas oportunidades, em um só lugar. Tem duas áreas no ar e uma planejada:
disponivel: false) e não tem rota.Nome, slogan, descrição, áreas e cores da marca ficam em src/lib/marca.ts.
O logo é Logo.tsx; favicon, ícone do app, manifest e imagem
de compartilhamento são gerados por código em src/app/ (icon.tsx, apple-icon.tsx,
manifest.ts, opengraph-image.tsx).
any)src/app é uma telaPor que Next.js em vez de uma SPA com Vite: as rotas de API e Server Actions
(src/app/api/**, src/services/data/server/**) rodam no mesmo projeto e
mesmo deploy da Vercel — isso resolve de graça tanto "manter a chave de IA
fora do cliente" quanto "falar com o Postgres sem expor credenciais",
sem precisar de um backend separado.
src/
types/ Contratos de dados (Obra, ContaFinanceira, ItemProduto,
DadosExtraidosComprovante, ...) — definidos ANTES da UI.
services/
ai/ Camada de IA. reciboReader.ts é chamado pela UI;
provedor.server.ts e providers/* só rodam no servidor.
data/ Camada de dados (repositórios). repositorios.ts define as
interfaces; server/*Actions.ts são Server Actions "use
server" que falam com o Postgres via Prisma
(src/lib/prisma.ts); index.ts monta os repositórios a
partir dessas actions. UI e hooks só conhecem as
interfaces — trocar de banco não toca em componente algum.
hooks/ useObras, useContas, useConta, useItensProduto, useFuncionarios,
useFuncionario, useCamera, useIsDesktop
components/
ui/ Design system: Button, Card, Input, Select, Textarea,
Badge, StatusContaBadge, StatusObraBadge,
StatusFuncionarioBadge, ImageLightbox
camera/ CameraCapture (câmera no mobile / upload no desktop),
PassosCaptura (indicador 1-2-3), ConfirmacaoExtracao
contas/ Listagem, tabela (desktop), filtros, formulário e detalhe
obras/ Listagem e detalhe de obras (resumo financeiro)
funcionarios/ Listagem, formulário e detalhe de funcionários
produtos/ Gráfico de evolução de preço (recharts)
dashboard/ KpiCard, FluxoFinanceiroChart, GastosCategoriaChart,
ProgressoObrasList, ContasProximasList, AtividadeRecenteList
layout/ Sidebar (desktop), BottomNav (mobile), Topbar, Logo,
ShellWrapper (decide o chrome por rota), PageHeader
lib/ Helpers puros (formatação, categorias, cn, resumo
financeiro, métricas do dashboard)
app/ Telas (App Router) — ver tabela abaixo
| Rota | Tela |
|---|---|
/ | Landing page (marketing, sem chrome do app) → CTA "Iniciar" leva ao dashboard |
/dashboard | KPIs, fluxo financeiro, gastos por categoria, obras em andamento, contas próximas, atividade recente |
/capturar | Wizard de 3 passos (Capturar → Revisar → Confirmar). No desktop pula a câmera e mostra só upload de arquivo |
/contas-a-pagar | Tabela (desktop) / lista de cards (mobile) + filtros + cadastro manual |
/contas-a-pagar/[id] | Detalhe + comprovante em tamanho real + dar baixa |
/contas-a-receber | Idem, para recebimentos |
/contas-a-receber/[id] | Idem |
/obras | Lista de obras |
/obras/novo | Cadastro de obra |
/obras/[id] | Detalhe: resumo financeiro + contas a pagar/receber vinculadas |
/custos-produtos | Busca por material → histórico de preço, gráfico, tabela por obra/fornecedor |
/estoque/deposito | Depósito: saldos, última compra, valor em estoque, onde mais o material está, transferir/devolver e histórico |
/funcionarios | Lista + filtros (obra/status/busca) + cadastro |
/funcionarios/novo | Cadastro de funcionário |
/funcionarios/[id] | Detalhe + ativar/inativar |
/configuracoes | Placeholder de preferências e integrações |
Decisões de arquitetura:
/) fica fora do "shell" do app (sem sidebar/bottom nav) — ver
a lista ROTAS_SEM_CHROME em ShellWrapper.tsx./capturar (em vez de rotas separadas por passo), para não precisar
serializar um Blob de imagem entre rotas.≥768px, useIsDesktop.ts), a
tela de captura pula getUserMedia e mostra só um botão de upload —
fotografar a própria tela não faz sentido nesse contexto.Definido em src/types antes de qualquer tela, conforme pedido.
Principais tipos: Obra, ContaFinanceira (pagar/receber), ItemProduto
(itens de nota fiscal, alimentam a consulta de custo) e o contrato de IA
DadosExtraidosComprovante / ProvedorExtracaoIA.
Todo campo que a IA não conseguiu ler com segurança fica undefined em vez
de um valor chutado — a UI de confirmação destaca esses campos
(camposComBaixaConfianca) em amarelo para revisão manual.
POST /api/ai/extract-recibo (route.ts), que roda no servidor.comprovante vs nota_fiscal) e
extrai os campos correspondentes — o usuário pode corrigir essa
classificação no toggle da tela de revisão.tool_choice forçando saída
JSON estruturada (evita parsing frágil de texto livre). Implementação em
src/services/ai/providers/anthropicProvider.ts.ANTHROPIC_API_KEY configurada, o app cai automaticamente para um
MockExtracaoProvider (retorna confiança 0, força preenchimento manual) —
assim dá para rodar e testar a UI sem custo de API.Copie .env.example para .env.local e preencha:
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-sonnet-5 # opcional, esse é o padrão
Em produção (Vercel): Project Settings → Environment Variables. Nunca
commitar a chave real nem chamar a API de IA a partir do cliente — por isso
anthropicProvider.ts só é importado por provedor.server.ts, que só é
importado pela API route.
Postgres real (Neon, provisionado via vercel integration add neon e
conectado ao projeto), não mais localStorage — os dados são compartilhados
entre todos os dispositivos e usuários, o que era o motivo de contas
lançadas pelo celular não aparecerem no computador antes desta migração.
dataVencimento, criadoEm, etc.) ficam como String ISO, não DateTime
nativo — decisão deliberada, ver comentário no topo do schema.historico de cada conta (quem criou/editou/baixou/cancelou) é um array
JSON na própria linha, não uma tabela separada.src/lib/categorias.ts) ficam em
CategoriaPersonalizada/FormaPagamentoPersonalizada.src/lib/prisma.ts mantém um único PrismaClient (evita esgotar conexões
a cada hot-reload em dev).npx prisma db push # aplica o schema no Postgres (sem histórico de migrations)
node prisma/seed.mjs # dados de exemplo — só roda se o banco estiver vazio
O Prisma CLI lê .env (não .env.local, que é uma convenção só do Next.js)
— por isso existe um .env além do .env.local, ambos gitignorados. Rode
vercel env pull .env.local para trazer as variáveis de conexão do projeto
Vercel (DATABASE_URL, DATABASE_URL_UNPOOLED, etc.) e copie as duas linhas
DATABASE_URL* para um .env local.
Nota sobre a versão do Prisma: o Prisma 7 reestruturou o CLI inteiro em
torno da própria plataforma hospedada deles (prisma deploy, prisma auth login, etc.) e não tem mais prisma generate/db push como comandos de
topo. Este projeto fica no Prisma 6, que é o ORM tradicional e funciona
com qualquer Postgres — não instale a v7 por engano.
Módulo do Elo Gestão em /calendario (visões mês, semana e dia). Cada compromisso é do calendário de quem o criou;
o criador pode compartilhá-lo com usuários ativos da mesma empresa, que o veem no próprio calendário e recebem os
lembretes. A empresa em si não abre o calendário pessoal de ninguém, nem o dono. Só o criador edita, exclui e compartilha.
Lembretes. Cada compromisso agendado gera dois: na véspera e no dia, por e-mail (Resend) e notificação no sino. Com horário, a véspera sai 24 h antes do início e o do dia às 8h locais (nunca a menos de 60 min do início). Dia inteiro: às 9h locais, na véspera e no dia. Lembrete cujo horário já passou na hora de criar ou editar não é enviado depois do fato. Cancelar, concluir ou excluir cancela o que ainda não saiu; mudar data ou horário recalcula e permite avisar de novo. Falha de e-mail tem até 5 tentativas (5 min, 15 min, 1 h, 3 h) e depois fica registrada como falha.
Como funciona o disparo. Nada depende do site aberto. Um agendador chama POST /api/cron/lembretes; a rota é
idempotente e reserva cada item com um UPDATE condicional, então chamadas simultâneas não duplicam. A unicidade
(lembrete, versão, usuário, canal) em EntregaLembrete é a segunda barreira, e o e-mail leva chave de idempotência.
Configuração (uma vez):
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))") e defina-o como
CRON_SECRET na Vercel (Production). Sem ele a rota responde 503.CRON_SECRET (mesmo valor) e APP_URL (endereço do
site, sem barra final). O workflow .github/workflows/lembretes.yml chama a rota a cada 5 minutos.RESEND_API_KEY e EMAIL_REMETENTE (já usados pela confirmação de e-mail). Sem eles, em produção o
envio falha e vira nova tentativa; em desenvolvimento a mensagem sai no console.Opcional: CALENDARIO_HORA_DIA_INTEIRO e CALENDARIO_HORA_LEMBRETE_DO_DIA (hora local, 0 a 23). O fuso é da empresa
(Configurações, só o dono; padrão Brasília). Preferências de e-mail e de sino são de cada usuário.
Testes. yarn test roda os testes unitários (regras puras, sem banco). yarn testar:calendario roda a integração
contra o banco do .env.local: cria duas empresas descartáveis, exercita compartilhamento, isolamento, lembretes,
repetição e concorrência (e-mail simulado) e apaga o que criou.
O dono cria os usuários da empresa em Configurações e escolhe os módulos de cada um (Dashboard, Calendário, Captura, Contas a pagar, Contas a receber, Projetos, Estoque, Funcionários, Fornecedores, Elo Profissionais). O dono sempre tem todos; um membro usa só o que foi marcado, e o dono pode mudar isso depois (precisa da própria senha). A lista de módulos e o que cada ação exige estão numa definição só, modulos.ts, usada em três camadas: o menu esconde o que a pessoa não usa, a página mostra "sem acesso" e, o que realmente protege, cada Server Action confere no servidor (os módulos são lidos do banco a cada requisição, então revogar vale na hora). "Ler" é mais largo que "escrever": quem lança uma conta enxerga a lista de projetos para escolher, mas só altera projetos quem tem o módulo Projetos. Os módulos que a pessoa pode receber também respeitam o plano da empresa.
Cada empresa tem um Depósito: um local de estoque especial (uma linha em Obra com ehDeposito, no máximo uma por
empresa, garantido por índice único parcial). Ele não é projeto: não aparece nas listas de projetos, não tem orçamento
e não entra nos indicadores de projeto.
Captura de nota fiscal. Na revisão, o campo "Destino de todos os itens" manda a nota inteira para um projeto ou para o
Depósito; em cada item é possível trocar o destino. A gravação é uma só, no servidor e numa transação
(capturaCore.server.ts): conta (ou parcelas), itens, produto do
catálogo e entrada no estoque do destino do item. Com mais de um destino, o valor da conta é rateado proporcional ao
valor dos itens (frete e impostos entram na mesma proporção), em centavos inteiros pelo método do maior resto, para a
soma fechar exatamente no valor da nota (rateio.ts). A conta continua uma só (um
boleto, uma baixa): o rateio fica em RateioConta, e a conta fica com o projeto de maior parte. Em notas parceladas,
cada parcela é rateada na mesma proporção e os itens ficam na primeira conta. A soma das parcelas precisa bater com o
valor da nota.
Custo do projeto. Compra para o Depósito não é gasto de projeto. O projeto soma: contas que têm parte dele (só a parte dele, via rateio) + material transferido do Depósito ao valor da última compra. Isso aparece no detalhe do projeto (seção "Materiais vindos do depósito") e no progresso de orçamento do dashboard.
Transferir / devolver (depositoCore.server.ts). Duas
movimentações de estoque ligadas por transferenciaId (saída de um local, entrada no outro) na mesma transação; o
saldo nunca fica negativo e transferências simultâneas não passam do saldo. O valor unitário é o da última compra
(itens de contas não canceladas); sem compra, o custo médio; sem nenhum, R$ 0,00 (a tela avisa antes). Transferir não
é compra: não muda o custo médio, não cria conta e não mexe no caixa. Devolver ao Depósito abate o custo do projeto
pelo mesmo critério. Limite conhecido: a devolução usa a última compra do momento, então, se o preço mudou entre a ida
e a volta, sobra uma pequena diferença no custo do projeto.
Testes. yarn testar:deposito (integração, banco do .env.local): captura com rateio, parcelas, Depósito,
transferência, devolução, última compra com conta cancelada, isolamento entre empresas e concorrência; cria empresas
descartáveis e apaga o que criou.
Tema. O tema claro é o padrão do site e do app: as variáveis claras ficam em :root (globals.css) e o
escuro só vale com data-theme="dark", escolhido em Configurações e guardado no navegador (elo:tema). Um script
no <head> (layout.tsx) aplica o escuro antes da primeira pintura, sem flash. As cores de texto do tema
claro foram calculadas para contraste AA (4,5:1), inclusive sobre os fundos tingidos de sucesso, aviso, erro e informação.
Link de convite. Cada empresa tem um link (/criar-conta?convite=CÓDIGO) que o dono copia ou envia pelo WhatsApp em
Configurações. Quem cria a conta por ele abre a própria empresa, separada da que convidou; o convite é só rastreio: grava
Empresa.convidadaPorId e o dono vê quantas empresas se cadastraram, nunca quem. O código (8 caracteres sem 0/O/1/I/L) nasce
na primeira vez que o dono abre o link (conviteCore.server.ts); código inválido,
desconhecido ou de empresa bloqueada é ignorado e o cadastro segue normalmente. Se a empresa que convidou for apagada, a convidada
permanece (ON DELETE SET NULL). yarn testar:convite (integração, banco do .env.local) cobre código único, chamadas simultâneas,
código inválido/bloqueado, contagem isolada por empresa e a exclusão; cria empresas descartáveis e apaga o que criou.
Teste. A duração padrão é 14 dias (ConfiguracaoPlataforma, chave ciclo_de_vida, editável em Admin > Teste e e-mails; valor
ausente ou inválido volta ao padrão de config.ts). Vale só para novos testes: quem já está em teste
mantém a data. O teste começa no cadastro (a pessoa escolhe "Teste de N dias" ou "Só o Elo Profissionais") ou pelo botão da rede
(iniciarTesteGratis); nada começa em silêncio. Ao vencer, a conta fica somente para consulta (escrita e IA bloqueadas no
servidor, regra que já existia): nada é apagado e o plano não muda sozinho.
Extensão. Só a administração concede (senha de novo + motivo, registrada em LogAdmin e em ExtensaoTeste). Limites
configuráveis (padrão: 1 extensão por empresa, até 14 dias, 14 no total) conferidos no servidor, dentro da transação
(extensaoCore.server.ts); duas extensões simultâneas não passam juntas. Teste já vencido
conta a partir de hoje. A empresa recebe aviso no app e e-mail com a nova data; o histórico (fim original, extensões, quem concedeu e
por quê) aparece na administração.
E-mails. cicloDeVidaCore.server.ts mantém uma fila (EmailAgendado) com chave única
(sem duplicar), reserva por UPDATE condicional (seguro em paralelo), tentativas com espera crescente (até MAX_TENTATIVAS), teto diário
(EMAIL_LIMITE_CICLO_DIA, padrão 50) e no máximo uma mensagem promocional a cada 2 dias por pessoa. Antes de enviar, cada e-mail é
conferido de novo e cancelado se deixou de fazer sentido (pessoa já fez a ação, descadastrou, o fim do teste mudou, empresa bloqueada).
Duas categorias, separadas: transacional (boas-vindas, fim do teste, extensão: sobre a conta, ignora a preferência) e promocional
(dicas de uso: respeita o descadastro, leva o link e os cabeçalhos List-Unsubscribe de um clique). Campanhas: boas-vindas (depois de
confirmar o e-mail), dica da primeira ação (D+1), dica do problema do dia a dia (D+3), recursos não usados (D+7) e aviso do fim do teste
(D-3, D-1, D0). Dias e liga/desliga de cada uma ficam na configuração. Tudo vem desligado: ligar "Enviar e-mails de ciclo de vida"
grava o instante, e dicas e boas-vindas só valem para quem se cadastrar depois dele (ninguém antigo recebe rajada). Os avisos de fim do
teste também aparecem no app (sino) e não dependem da chave de e-mails. O descadastro é em /descadastrar/<token> (página com botão; o GET
não muda nada) e em POST /api/descadastrar/<token> (um clique do provedor), e a preferência fica em Configurações.
Eventos. EventoProduto (sem relações, sem conteúdo sensível, chave única = idempotente): cadastro_concluido, primeiro_acesso,
teste_iniciado, teste_estendido, teste_encerrado, aviso_fim_teste_app, email_enviado, email_falhou, email_descadastro. Nenhum evento
é inventado: assinatura confirmada e checkout só existirão quando houver cobrança real. "E-mail entregue/aberto" depende do webhook do
provedor, que ainda não está ligado.
Agendador. .github/workflows/ciclo-de-vida.yml chama POST /api/cron/ciclo-de-vida a cada 15 minutos com
CRON_SECRET (mesmos segredos APP_URL e CRON_SECRET de lembretes.yml; sem CRON_SECRET a rota fica fechada com 503). É idempotente.
Para ativar em produção (nada disso eu configuro por você): RESEND_API_KEY e EMAIL_REMETENTE (domínio verificado na Resend),
NEXT_PUBLIC_SITE_URL (https, com o endereço principal), CRON_SECRET na Vercel e APP_URL + CRON_SECRET nos segredos do GitHub,
NEXT_PUBLIC_EMAIL_CONTATO (e-mail de contato mostrado na faixa de teste encerrado e nos e-mails), e então ligar a chave em Admin > Teste e e-mails.
Atenção ao teto de envio do plano da Resend (a conta é compartilhada com o que mais enviar por ela).
Testes. yarn test (regras puras: datas, extensão, configuração, quando cada campanha se aplica, modelos de e-mail) e
yarn testar:ciclo (integração, banco do .env.local, nenhum e-mail real: fila, duplicidade, concorrência, falhas e tentativas, descadastro,
fim do teste, extensão, encerramento; cria empresas descartáveis, não toca na configuração real e apaga o que criou).
Tour inicial. TourInicial.tsx abre sozinho só para quem se cadastrou nos últimos 14 dias e
ainda não viu o tour; quem já usa o sistema não leva susto. Os passos vêm filtrados no servidor pelo plano e pelos módulos da pessoa
(tour.ts): quem só usa a rede não vê passo de gestão (e recebe o convite para conhecer o Elo Gestão), e o
funcionário só vê o que o dono liberou. Dá para avançar, voltar, pular ("pulado") e retomar: fechar (X, ESC) guarda o passo em
que parou ("em andamento") e o tour nunca reabre sozinho depois de começado. Em Ajuda e tutoriais (/ajuda, também em Configurações) há
"Continuar de onde parei", "Recomeçar" e "Rever".
Como funciona? Botão ao lado do título das telas de cada módulo (AjudaDaPagina.tsx, dentro do
PageHeader). Cada tutorial tem as seis partes pedidas: o que é, qual problema resolve, o que você ganha, como usar (passo a passo),
exemplo prático e o botão da próxima ação (ajuda.ts). O texto só descreve o que a tela faz e fala de benefício
possível, nunca de resultado garantido (há teste que confere que cada botão leva à tela do módulo certo). Ao mudar uma tela, confira o
tutorial dela. Abrir um tutorial grava "tutorial visto" (uma vez por pessoa) e vira o evento tutorial_visualizado.
Checklist de ativação. checklist.ts: cada item é "feito" por um fato real nos dados (projeto criado,
conta lançada, produto cadastrado, compromisso criado, perfil publicado, dados da empresa preenchidos). Não existe botão de "marcar como
feito", e a conclusão não é guardada como marcação: é calculada a cada leitura. A lista é personalizada pelo plano e pelos módulos (o
funcionário vê só os dele, e itens de dono só o dono vê). A primeira vez que um item aparece feito vira o evento item_checklist_concluido
(um por empresa e item). O progresso por pessoa (estado do tour, checklist oculto, tutoriais vistos) fica em ProgressoOnboarding. Aparece no
Dashboard e no Perfil da rede, e sempre em Ajuda; "Ocultar do painel" é por pessoa e reversível.
Instalar no celular. Em Ajuda e tutoriais (InstalarNoCelular.tsx) há o passo a passo para colocar o
ícone na tela de início do iPhone (Safari) e do Android (Chrome); abre já no aparelho da pessoa, e quem está no computador vê os dois com o aviso para abrir no celular.
Depois da primeira ação concluída, o painel mostra uma sugestão (SugestaoInstalar.tsx), só no celular e só se o site ainda
não está instalado; "Agora não" some para sempre naquele aparelho (fica no navegador, porque a decisão é por aparelho). Textos e detecção de plataforma em
instalacao.ts, com testes; os nomes dos botões do iOS e do Chrome mudam com as versões, então reconfira os passos de vez em quando.
O manifesto declara PNG de 192 e 512 px e uma versão maskable (rota /icones/[tamanho], pública no proxy): sem 192 e 512 o Chrome
no Android não oferece "Instalar app". Não há botão próprio de "Instalar" (o evento beforeinstallprompt não existe no Safari do iPhone, e o Next recomenda não depender dele).
Eventos novos: onboarding_iniciado, onboarding_concluido, tutorial_visualizado, item_checklist_concluido (todos idempotentes por chave).
Testes. yarn test (conteúdo e regras: cada módulo tem tutorial completo, links no módulo certo, filtro por plano e módulos, checklist
só por fato real) e yarn testar:onboarding (integração, banco do .env.local: estado inicial sem gravar, tour e eventos sem repetir,
entradas inválidas recusadas, checklist avançando só com dados reais, isolamento entre pessoas e empresas; cria empresas descartáveis e apaga
o que criou).
Cada empresa nova é convidada a marcar uma conversa de 20 minutos com o fundador. O agendamento é numa página externa (Cal.com, Calendly, Google Agenda): o app guarda só o link.
https://) e ligue; vem desligado. Salvar pede motivo e senha e entra no registro de ações./agendar-configuracao e link no e-mail de boas-vindas (se os e-mails estiverem ligados).Empresa.chamadaConfiguracaoEstado). O app não vê a sua agenda: "agendada" é o que a empresa informa.chamada_config_clicou, chamada_config_agendada, chamada_config_dispensada.mudar1234 (a tela avisa), a troca é obrigatória no primeiro acesso e a senha inicial vence em 7 dias (senhaTemporaria.server.ts). A ficha guarda o vínculo (Funcionario.usuarioId).
Em Configurações o dono só gerencia quem já existe (editar módulos, redefinir, desativar); não há mais "Adicionar usuário" lá.Cargo): o dono cria e edita cargos; cada um guarda os módulos que vêm marcados ao dar acesso. Mudar um cargo não altera o acesso de quem já foi cadastrado; o dono ajusta pessoa a pessoa.listarContasNoCalendario): contas a pagar e a receber em aberto aparecem no dia do vencimento (só os tipos que a pessoa pode ver) e levam à conta.Em Configurações (só o dono), cada forma de pagamento tem baixa automática e dia do vencimento (FormasPagamentoConfig.tsx, tabela ConfiguracaoFormaPagamento).
Padrões (formaPagamento.ts): dinheiro, PIX, débito e transferência baixam sozinhos; cartão de crédito, boleto e cheque ficam em aberto até o vencimento.
baixada quando o cadastro está completo (categoria e forma escolhidas) e não é parcelada, na captura, no lançamento manual e ao completar o cadastro. A data da baixa é a do vencimento, nunca no futuro; o histórico registra "Baixa automática".yarn testar:forma-pagamento (rodar contra a branch demo, não contra o banco real).No topo do Calendário (AvisosDoCalendario.tsx) e em Configurações há dois interruptores: e-mail (preferência da conta) e push neste aparelho
(InterruptorPush.tsx, usePush.ts). O push é por aparelho: cada navegador pede a permissão e cria a própria inscrição (InscricaoPush).
Como funciona: o service worker public/sw.js só mostra a notificação e abre o app ao tocar (sem cache e sem modo offline). O servidor envia com web-push (enviar.server.ts),
no mesmo processador dos lembretes (lembretesWorker.server.ts): o push é um terceiro canal (push), com a mesma garantia de não duplicar e de tentar de novo.
Aparelho que o navegador informa como expirado (404/410) é apagado; falha temporária reagenda.
Para ligar em produção: gere o par de chaves uma vez com npx web-push generate-vapid-keys e coloque NEXT_PUBLIC_VAPID_PUBLIC_KEY e VAPID_PRIVATE_KEY na Vercel (e faça redeploy). Sem as duas, o interruptor de push nem aparece.
Trocar o par depois derruba todas as inscrições. A migração 20261024120000_push_lembretes precisa ser aplicada no banco.
Limites: no iPhone o push só funciona com o app instalado na tela de início (iOS 16.4+); a tela avisa e leva ao guia de instalação. Se a pessoa bloquear as notificações no navegador, só ela libera lá.
Textos definitivos (versão 1.0): termos e privacidade. Quem opera, o encarregado de dados, o foro e os prazos prometidos ficam em operadora.ts
(OPERADORA e POLITICA); o e-mail de contato é MARCA.emailContato (NEXT_PUBLIC_EMAIL_CONTATO sobrescreve). Mudou o texto de forma relevante? Suba VERSAO_TERMOS_ATUAL em termos.ts.
Política: cancelar a qualquer momento, sem multa; o acesso vale até o fim do período pago; reembolso integral em 7 dias (CDC art. 49); dados guardados até 12 meses depois do fim do período pago (e apagados antes, a pedido, em até 15 dias).
Como o código cumpre isso (assinaturaComercialCore.server.ts, regra de datas em periodo.ts):
proximaCobrancaEm = hoje + 1 mês (o primeiro mês pago);cancelled no Mercado Pago marca a assinatura como cancelada, mas a empresa continua ativa; proximaCobrancaEm passa a ser o fim do período pago (notificação repetida não mexe nessa data);/api/cron/assinaturas suspende (somente leitura) quando essa data passa, uma vez só, e não suspende quem já assinou de novo o mesmo plano;paused (cobrança falhou) continua suspendendo na hora; o anual não renova sozinho e é suspenso no vencimento.Ainda é manual: o reembolso de 7 dias (devolver no Mercado Pago e suspender a conta pelo painel admin) e o apagamento dos dados depois de 12 meses.
Poucos campos (CriarContaForm.tsx): o autônomo informa nome, CPF, e-mail e senha (o nome da conta e o da pessoa são o mesmo, então só se pergunta uma vez); a empresa informa CNPJ (a Receita preenche razão social, telefone e endereço), o nome do responsável, e-mail e senha. O endereço da empresa é opcional e fica recolhido. Telefone e endereço completo se preenchem depois, em Configurações.
CEP: ao completar os 8 dígitos o endereço (rua, bairro, cidade e UF) é preenchido na hora (useBuscaCep.ts, ViaCEP), no cadastro, em Configurações e no fornecedor. Resposta atrasada de um CEP que já foi trocado é descartada; CEP inexistente e falha de rede têm mensagens diferentes, e o que a pessoa digitou nunca é apagado.
Senha: mínimo de 8 caracteres (auth.ts, TAMANHO_MINIMO_SENHA), o mesmo número no servidor, nos formulários e nos textos de ajuda.
Erros claros: a tela confere tudo no navegador com o mesmo esquema do servidor (cadastroMensagens.ts) e mostra todos os problemas de uma vez, cada um com o nome do campo e o que fazer, com link que leva ao campo. Os campos não somem depois de um erro. Conta duplicada diz "já existe uma conta com este e-mail ou este CPF/CNPJ" e oferece Entrar e Esqueci minha senha, sem dizer qual dos dois existe (dizer qual deixaria qualquer pessoa descobrir quem tem conta).
Abas numa lista só (navegacao.ts): a barra lateral do computador, o menu lateral do celular e a barra de baixo leem a mesma lista; uma aba nova entra nos três. Quem pode ver cada aba continua sendo decidido pelos módulos que o dono liberou e pelo plano (o menu esconde, a barreira real está nas Server Actions).
Celular: o hambúrguer do topo abre o menu lateral com todas as abas, agrupadas (MenuMovel.tsx). A estrela fixa uma aba na barra de baixo, que mostra
só os favoritos (até 5, regras em favoritos.ts, com testes). A escolha fica no navegador do aparelho (elo:favoritos), não na conta; sem escolha vale o padrão
(Buscar profissionais, Início, Agenda, Capturar, A Pagar, as que a pessoa pode acessar). Computador: a barra lateral fixa, que recolhe e expande.
Encontrar profissionais é o botão de destaque do topo (todas as telas) e o primeiro item do menu. Planos e valores fica em Minha conta, e um resumo do teste ("Teste grátis: N dias restantes", com o link para os planos) aparece no rodapé do menu; a faixa dos últimos 7 dias também leva aos planos.
Administração (/admin): números reais no topo (empresas por plano, usuários, assinaturas e receita estimada, perfis, avaliações, recomendações, portfólio, denúncias) e as seções
Preços e planos (valores, vagas e parcelamento em Planos comerciais), Pessoas e empresas, Conteúdo da rede e Acompanhamento. Recomendações e Portfólio têm moderação própria
(conteudoAdminActions.ts): remover pede a senha do admin e um motivo, e entra no registro de ações; remover um trabalho também apaga as fotos e avisa a empresa (sem o motivo).
Não existe "post" no produto: o conteúdo que as empresas publicam é perfil, portfólio, avaliações e recomendações.
Admin > Métricas (/admin/metricas, só administrador, só leitura). Período de 7, 30 ou 90 dias. A leitura é por coorte: as
empresas criadas no período e o que aconteceu com elas até agora (metricasCore.server.ts).
Cada número tem a fórmula escrita em formulas.ts, e é o mesmo texto que o painel mostra em "Como cada número é calculado".
Testes: yarn test (fórmulas) e yarn testar:metricas (integração: lê o painel, cria empresas descartáveis, confere a diferença e apaga só o que criou).
Catálogo em Admin > Planos comerciais (PlanoComercial): nome, descrição, preço mensal e anual (opcionais), benefícios, "recomendado", publicado e ordem.
Salvar pede motivo e a senha do admin e entra no registro de ações. Não há exclusão: tirar do ar é despublicar. É só vitrine: não concede acesso nem cobra
(o acesso continua em Planos e permissões, e a contratação é combinada com a equipe por e-mail).
Nada inventado. Sem plano publicado, a página /planos diz que os planos ainda não foram publicados e as ofertas não aparecem. O preço é sempre o cadastrado;
sem preço o plano aparece como "sob consulta"; a economia anual só é mostrada quando os dois preços existem e o anual realmente sai mais barato
(comercial.ts). Publicar exige ao menos um benefício.
Onde aparece: página /planos (cards + tabela de comparação; link em Configurações e na faixa de teste encerrado), oferta ao abrir o Elo Gestão sem acesso
(UpsellGestao.tsx) e oferta em Configurações depois do fim do teste (OfertaContextual.tsx).
Visualização e clique viram os eventos oferta_visualizada e oferta_clicada (uma vez por pessoa, contexto, plano e dia) e entram em Admin > Métricas.
Vagas limitadas (plano fundador). O plano pode ter vagasTotal. O "Restam N vagas" que o cliente vê é calculado: limite menos as empresas no plano Ativo
com aquela chave de plano comercial (Empresa.planoComercialChave, definida por Admin > Empresas > Plano comercial). Não existe campo para digitar o "restam", então a
escassez mostrada é sempre a real. A atribuição confere as vagas na mesma transação, com a linha do plano travada (planosComerciaisCore.server.ts), e duas
atribuições simultâneas não passam do limite. Quem sai do plano Ativo libera a vaga. Teste: yarn testar:planos.
Parcelamento sem juros. O campo parcelasSemJuros fica vazio até o provedor de pagamento confirmar que oferece; só então a página mostra "12x de R$ X sem juros".
A parcela é o anual dividido pelo número de parcelas e só aparece se a divisão for exata.
Conteúdo comercial (vitrine.ts): os problemas do dia a dia que o sistema resolve e tudo o que o Elo Gestão inclui. Cada item descreve algo que o sistema faz hoje, sem prometer resultado (há teste contra absolutos como "garantido", "100%", "ilimitado"). Ao mudar uma funcionalidade, confira o texto.
Ainda não existe: pagamento. Falta escolher o provedor (Asaas ou Mercado Pago), criar a conta e definir os planos; só então entram checkout e os eventos de assinatura, que só podem nascer do retorno do provedor, nunca de um clique.
yarn install # roda "prisma generate" automaticamente (postinstall)
cp .env.example .env.local # preencha ANTHROPIC_API_KEY (opcional — sem isso, IA roda em modo mock)
# preencha também DATABASE_URL e DATABASE_URL_UNPOOLED em .env.local e .env
# (vercel env pull .env.local traz do projeto Vercel)
yarn dev
Limitação conhecida: o comprovante fotografado ainda é salvo como data
URL (base64) numa coluna de texto (ContaFinanceira.comprovanteUrl), não em
um object storage dedicado. Funciona, mas infla o tamanho das linhas no
Postgres — mover para Vercel Blob/S3 é a melhoria óbvia seguinte.
Abra http://localhost:3000. Em desktop sem câmera, a tela inicial cai
automaticamente no fallback de upload/capture=environment.
yarn dev — desenvolvimentoyarn build — build de produçãoyarn lint — ESLint (inclui regras do React Compiler/hooks)yarn tsc --noEmit — checagem de tipos