From 4e0b26f1c4beb062e1f815a8ed639d10f994b0c5 Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 14 Jul 2026 15:18:53 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20BrainWind=20atual?= =?UTF-8?q?izado=20em=2014/07/2026=2015:18:53?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PLANO-COMERCIAL-SOCIO.md | 242 +++++++++ PROGRESS.md | 11 +- ROADMAP-VENDA.md | 644 ++++++++++++++++++++++++ app/src/App.tsx | 50 +- app/src/components/Glossary.tsx | 87 ++++ app/src/components/SaveModuleDialog.tsx | 121 +++++ app/src/components/three/Piperack3D.tsx | 68 +-- app/src/data/glossary.ts | 150 ++++++ app/src/lib/storage.ts | 4 +- app/src/pages/BridgeModule.tsx | 32 +- app/src/pages/GalpaoModule.tsx | 29 +- app/src/pages/PiperackModule.tsx | 35 +- app/src/pages/SettingsModule.tsx | 249 +++++---- app/src/store/hydrationStore.ts | 16 + 14 files changed, 1580 insertions(+), 158 deletions(-) create mode 100644 PLANO-COMERCIAL-SOCIO.md create mode 100644 ROADMAP-VENDA.md create mode 100644 app/src/components/Glossary.tsx create mode 100644 app/src/components/SaveModuleDialog.tsx create mode 100644 app/src/data/glossary.ts create mode 100644 app/src/store/hydrationStore.ts diff --git a/PLANO-COMERCIAL-SOCIO.md b/PLANO-COMERCIAL-SOCIO.md new file mode 100644 index 0000000..261a9eb --- /dev/null +++ b/PLANO-COMERCIAL-SOCIO.md @@ -0,0 +1,242 @@ +# Plano de Comercialização — BrainWind + +## Software de cálculo de vento conforme a norma brasileira NBR 6123 + +--- + +**Preparado por:** Marcos Reifonas +**Data:** Julho de 2026 +**Aplicativo:** BrainWind (cálculo de vento em edificações) + +--- + +## 1. Resumo da situação atual + +O BrainWind é um aplicativo de cálculo de vento para projetos de engenharia civil (galpões, silos, abóbadas, cúpulas, muros, pontes, torres e coberturas). Ele aplica a norma brasileira **NBR 6123:2023** na íntegra. + +**O que já está pronto:** + +- Toda a norma técnica foi implementada (36 tabelas e 3 anexos oficiais) +- Cobre 9 tipos diferentes de estrutura +- Foi feita uma auditoria independente que testou 91 cenários contra os exemplos da própria norma — todos passaram +- Existe um motor de cálculo matemático robusto, testado e validado +- O programa gera relatórios em PDF prontos para apresentação ao cliente + +**Resumo:** o produto técnico já é sólido. O que falta é o lado comercial — transformar um aplicativo interno em algo que pode ser vendido e mantido como negócio. + +**Oportunidade de mercado:** + +Não existe hoje no Brasil nenhum software dedicado exclusivamente ao cálculo de vento conforme a norma atualizada. Os engenheiros resolvem isso em planilhas de Excel ou compram softwares estrangeiros caros (que não seguem a norma brasileira). O BrainWind entra em um espaço de mercado pouco ocupado, com um produto superior ao que está disponível. + +--- + +## 2. Como vamos ganhar dinheiro + +A forma mais indicada para esse tipo de software técnico é a **licença anual**, com a opção de licença definitiva (pagamento único) para quem prefere. + +### Planos sugeridos para iniciar + +| Plano | Preço | Para quem é | +|-------|-------|-------------| +| **Teste grátis** | R$ 0,00 | Qualquer engenheiro, por 14 dias, sem compromisso | +| **Licença Anual** | R$ 1.500 por ano | Escritórios pequenos, autônomos, projetistas | +| **Licença Perpétua** | R$ 4.000 (à vista) | Quem quer pagar uma vez e usar por tempo indeterminado | +| **Licença Empresa** | R$ 6.000 por ano (5 usuários) | Escritórios de engenharia com equipe | + +**Por que esses valores funcionam:** + +O engenheiro cobra em média R$ 5.000 a R$ 15.000 por projeto de cálculo estrutural. Se o BrainWind economiza 4 horas de trabalho em cada projeto (o que faz), o software se paga em um único projeto. Para o profissional, é um investimento óbvio. + +--- + +## 3. As fases do plano + +O trabalho foi dividido em 8 fases. Cada fase tem um objetivo claro e um prazo realista para um time de uma ou duas pessoas trabalhando em meio período. + +### Fase 0 — Decisões iniciais (1 a 2 dias) + +Antes de começar qualquer trabalho técnico, é preciso travar algumas decisões que vão impactar tudo depois. + +**O que precisa ser decidido:** + +- Confirmar os preços finais (sugestão acima) +- Registrar o domínio do site (exemplo: brainwind.com.br) +- Escolher a plataforma de pagamento (sugestão: Kiwify, que é brasileira e fácil de usar) +- Decidir se vamos abrir um CNPJ/MEI para emitir nota fiscal (recomendado — sem isso não dá para vender para empresas) + +### Fase 1 — Tornar o app um produto vendável (2 a 3 semanas) + +Hoje o BrainWind funciona apenas no computador de quem instalou, com os dados guardados localmente. Para vender, é preciso transformar em algo que: + +- Exige login com usuário e senha +- Valida uma licença (chave de ativação) +- Salva os projetos na nuvem (para o cliente acessar de qualquer lugar) +- Controla o período de teste grátis automaticamente + +A infraestrutura técnica já existe (o servidor dessa VPS roda Supabase, que serve para isso sem custo adicional). + +### Fase 2 — Criar a identidade visual e a página de vendas (1 a 2 semanas) + +**O que precisa ser feito:** + +- Criar um logotipo simples (pode ser feito online sem custo) +- Definir cores e estilo visual (azul técnico + branco é uma escolha segura e profissional) +- Construir uma página na internet que explique o que o app faz, para quem é, quanto custa, e como comprar +- Escrever os Termos de Uso e a Política de Privacidade (exigidos por lei) + +### Fase 3 — Configurar o pagamento e a entrega automática (1 semana) + +Quando o cliente pagar, o sistema precisa liberar o acesso sozinho — sem ninguém precisar ficar apertando botão. + +**O que fazer:** + +- Criar os produtos na plataforma Kiwify +- Configurar o sistema para que, assim que o pagamento for confirmado, uma licença seja criada e enviada por e-mail +- Configurar avisos automáticos quando a licença estiver perto de expirar + +### Fase 4 — Marketing e captação de clientes (1 a 2 semanas) + +Sem divulgação, ninguém vai saber que o produto existe. + +**Estratégia de comunicação:** + +- Criar um vídeo curto (3 minutos) mostrando o app calculando um galpão real do início ao fim +- Publicar posts no LinkedIn e Instagram mostrando casos de uso +- Participar de grupos de Facebook e LinkedIn voltados para engenheiros estruturais +- Produzir um artigo técnico sobre as mudanças da NBR 6123 versão 2023 — isso traz clientes por mecanismos de busca + +**Estratégia de beta-testers (etapa fundamental):** + +Antes de abrir para todo mundo, oferecer o BrainWind de graça por 30 dias para 20 a 30 engenheiros selecionados. Em troca, eles dão feedback, relatam problemas e fornecem depoimentos para usar no marketing. Esse é o passo mais importante para validar o produto com usuários reais antes de começar a vender oficialmente. + +### Fase 5 — Testes com usuários reais (2 a 4 semanas) + +Rodar o programa com o grupo de beta-testers. Acompanhar o uso deles, responder dúvidas, corrigir o que estiver confuso, melhorar o que pedirem. + +**O que medir:** + +- Quantos realmente usam o programa (não só instalam) +- Quantos chegam até o final do cálculo e geram o PDF +- Quantos recomendariam para um colega (nota de satisfação) +- Quais funcionalidades foram mais pedidas +- Quais bugs apareceram + +### Fase 6 — Lançamento oficial (1 dia, mais uma semana de ajuste) + +Abrir para todo mundo. + +**Checklist do dia do lançamento:** + +- Página de vendas no ar e testada +- Sistema de pagamento funcionando +- Suporte (WhatsApp ou e-mail) pronto para responder +- Documentação legal publicada +- Vídeo de demonstração publicado +- Depoimentos de beta-testers em mãos +- Posts de lançamento agendados nas redes sociais + +**Meta para os primeiros 90 dias:** + +- 100 testes grátis iniciados +- 20 clientes pagantes +- R$ 30.000 de receita +- Nota de satisfação média acima de 50 (em escala de 0 a 100) + +### Fase 7 — Crescimento pós-lançamento (contínuo) + +Depois do lançamento, o trabalho continua: + +- Criar versão instalável para Windows e Mac (sensação de "software profissional") +- Integrar com outros programas que engenheiros já usam (Ftool, SAP2000, AutoCAD) +- Avaliar expansão para outras normas de vento internacionais (mercado americano e europeu paga muito mais) +- Criar programa de afiliados — engenheiro indica, ganha comissão recorrente +- Considerar versão mobile para consulta rápida em obra + +--- + +## 4. Investimento necessário + +### Custos para começar + +| Item | Valor | +|------|-------| +| Abertura de MEI | Grátis | +| Registro de domínio (por ano) | R$ 50 | +| Logo (versão simples, online) | R$ 0 a 200 | +| Plataforma Kiwify | Grátis (cobra só % sobre venda) | +| Infraestrutura de servidor | Já existente (sem custo adicional) | +| Email transacional | Grátis no início | +| **Total inicial** | **R$ 50 a 250** | + +### Custos mensais recorrentes + +| Item | Valor mensal | +|------|--------------| +| Contador (MEI ou Simples Nacional) | R$ 100 a 300 | +| Comissão Kiwify (~10% sobre vendas) | Variável | +| **Total fixo mensal** | **R$ 100 a 300** | + +### Projeção de receita (cenário conservador) + +| Mês | Novos testes grátis | Novos clientes pagantes | Receita acumulada | +|-----|---------------------|--------------------------|-------------------| +| 1 (lançamento) | 30 | 6 | R$ 750 | +| 2 | 50 | 10 | R$ 2.500 | +| 3 | 80 | 16 | R$ 4.500 | +| 6 | 150 por mês | 30 por mês | R$ 18.000 | +| 12 | 250 por mês | 50 por mês | R$ 60.000 | + +Mesmo no cenário conservador, em 12 meses o produto se paga e começa a gerar lucro consistente. A margem esperada é alta (cerca de 85%) porque não há custo de produção por unidade vendida — o software é o mesmo para todos os clientes. + +--- + +## 5. Cronograma resumido + +| Mês 1 | Mês 2 | Mês 3 | Mês 4 | Mês 5 em diante | +|-------|-------|-------|-------|------------------| +| Decisões iniciais | Sistema de login e licenças | Pagamento e entrega | Marketing e beta-testers | Lançamento oficial e crescimento | + +**Tempo total até o lançamento:** entre 3 e 4 meses, trabalhando em meio período. + +--- + +## 6. Riscos e como mitigar + +| Risco | Probabilidade | Impacto | Como mitigar | +|-------|---------------|---------|---------------| +| Pouca demanda no mercado | Baixa | Alto | Validar com beta-testers antes de investir pesado em marketing | +| Concorrente surgir | Média | Médio | Velocidade: lançar logo, fidelizar primeiros clientes | +| Bug grave aparecer | Média | Médio | Programa de beta-testers pega antes do lançamento público | +| Problema com a norma (nova versão) | Baixa | Alto | Arquitetura modular permite atualizar tabelas sem reescrever | +| Dificuldade de explicar o valor | Média | Alto | Vídeo de demonstração + depoimentos resolvem | + +--- + +## 7. Próximos passos imediatos + +Esta semana, três ações simples para começar: + +**1. Decidir os preços finais** +Reunião entre os sócios para confirmar os valores sugeridos. Pode-se ajustar para cima se houver demanda ou para baixo se quiser crescer mais rápido no início. + +**2. Verificar disponibilidade do domínio** +Acessar registro.br e pesquisar brainwind.com.br. Se estiver disponível, registrar (R$ 50). Se não, pensar em alternativas (brainwind.app, brainwind.eng.br). + +**3. Abrir MEI no nome de um dos sócios** +Acessar gov.br/mei. Leva cerca de 30 minutos. Sem CNPJ não dá para emitir nota fiscal, e empresa nenhuma compra sem nota. + +--- + +## 8. Conclusão + +O BrainWind tem uma base técnica sólida, auditada e validada. O produto está pronto no que diz respeito à engenharia. O que falta é o lado comercial — transformar esse ativo técnico em um negócio que gera receita recorrente. + +A oportunidade de mercado é real e clara: não existe hoje no Brasil um software dedicado exclusivamente ao cálculo de vento conforme a norma brasileira atualizada. Os engenheiros estão resolvendo isso em planilhas, com medo de errar a versão da norma. + +O investimento inicial é baixo (menos de R$ 300), o risco é controlável (validar com beta-testers antes de investir pesado), e a margem esperada é alta. + +A recomendação é seguir as fases descritas neste documento, começando pelas decisões da Fase 0 e seguindo em frente com disciplina. Em 3 a 4 meses, o produto pode estar no ar gerando receita. + +--- + +**Anexo:** documento técnico detalhado disponível em `ROADMAP-VENDA.md` (versão para a equipe técnica com instruções específicas de implementação). diff --git a/PROGRESS.md b/PROGRESS.md index c2b096a..e770264 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -15,7 +15,7 @@ | Item | Valor | |------|-------| | Data da última atualização | 2026-07-14 | -| Marcos concluídos | **8 / 8** (plano completo) + M9.1 ✅ + M9.2 ✅ + M9.3 ✅ + M9.4 ✅ + M9.5 ✅ + M9.6 ✅ + M9.7 ✅ + M9.8 ✅ + M9.9 ✅ + M9.10 ✅ + M9.13 ✅ + M9.14 ✅ + M9.15 ✅ + M9.16 ✅ + M9.17 ✅ + M9.18 ✅ | +| Marcos concluídos | **8 / 8** (plano completo) + M9.1 ✅ + M9.2 ✅ + M9.3 ✅ + M9.4 ✅ + M9.5 ✅ + M9.6 ✅ + M9.7 ✅ + M9.8 ✅ + M9.9 ✅ + M9.10 ✅ + M9.13 ✅ + M9.14 ✅ + M9.15 ✅ + M9.16 ✅ + M9.17 ✅ + M9.18 ✅ + M9.19 ✅ | | Cobertura NBR 6123:2023 | **100% das 11 seções + 3 anexos** | | Linhas de código TS/TSX | ~8.200 | | Arquivos `.ts`/`.tsx` no `src/` | **120** | @@ -715,6 +715,15 @@ gráficos SVG inline para variáveis CSS do tema, suportando dark mode. - **Envoltória de Pressão Interna (Cpi):** Adicionada `computeCpiEnvelope()` e o caso de 4 faces permeáveis agora trata explicitamente a envoltória dupla de Cpi [-0.3, 0]. - **Refinamentos:** Erro legado com constantes (`ka` modificado de 4.06 para precisos 4.058 para área residencial) e eliminação do uso de `@ts-ignore` em `coefficients.ts`. +#### M9.19 — Reorganização do Menu Lateral e Padronização de Layouts +**Status:** ✅ Concluído (2026-07-14) +**Escopo:** Reorganizar a estrutura e hierarquia do menu lateral desktop para separar parâmetros globais, arquivos e glossário, e padronizar o posicionamento dos botões de ação nos módulos. +**Entregas:** +- **Reorganização do Sidebar:** "Parâmetros do Vento" movido para o bloco inferior do menu lateral desktop. O antigo menu "Preferências" foi renomeado para "Configurações" e agora agrupa apenas as abas "Preferências" e "Testes". "Arquivos" (antigo Gerenciador) e "Glossário" foram promovidos a links independentes de primeiro nível no menu (sem exibir abas de topo no desktop). +- **Padronização de Botões de Ação:** O agrupamento de botões de Salvar/Exportar nos módulos de **Pipe-rack** e **Galpão** foi padronizado e movido para ficar imediatamente acima do painel de "Captura 3D" (SceneCapturePanel). +- **Salvamento no Galpão:** Implementado o `SaveModuleDialog` no módulo de Galpão, com suporte completo a salvar/carregar as configurações geométricas dinâmicas através do `useHydrationStore`. +- **Limpeza de Warnings:** Removidas variáveis e imports não utilizados no arquivo `SettingsModule.tsx` e corrigida a vinculação do input de arquivo oculto. + #### M9.11 — Persistência opcional em servidor (~especulativo) **Problema:** Tudo é local (IndexedDB). **Tarefa:** diff --git a/ROADMAP-VENDA.md b/ROADMAP-VENDA.md new file mode 100644 index 0000000..1e976d7 --- /dev/null +++ b/ROADMAP-VENDA.md @@ -0,0 +1,644 @@ +# BrainWind — Roadmap de Comercialização + +> **Objetivo:** Levar o BrainWind (antigo VentoApp/WindApp) do estágio atual de desenvolvimento interno até a venda comercial, com plano detalhado, fases e tarefas concretas. + +**Estado atual (jul/2026):** +- ✅ 8/8 marcos concluídos +- ✅ 100% NBR 6123:2023 coberta (36 tabelas + 3 anexos) +- ✅ Auditoria independente Opus: 91/91 cenários PASS +- ✅ 38 testes vitest passando +- ✅ TS estrito, build verde +- ⚠️ Tudo local (IndexedDB) — sem auth, sem servidor, sem licença + +--- + +## 📋 Visão geral das fases + +| Fase | Nome | Duração estimada | Pré-requisito | +|------|------|------------------|---------------| +| **0** | Decisões estratégicas | 1-2 dias | Nenhum | +| **1** | Infraestrutura comercial (auth + licença + persistência) | 2-3 semanas | Fase 0 | +| **2** | Identidade visual + landing page | 1-2 semanas | Fase 0 (paralela à 1) | +| **3** | Empacotamento de venda (preço, checkout, webhook) | 1 semana | Fases 1 e 2 | +| **4** | Materiais de marketing + aquisição | 1-2 semanas | Fases 1, 2 e 3 | +| **5** | Beta-tester + ajustes finais | 2-4 semanas | Fases 1-4 | +| **6** | Lançamento oficial | 1 dia | Fase 5 | +| **7** | Escala (desktop, integrações, novos módulos) | contínuo pós-lançamento | Fase 6 | + +--- + +## 🎯 FASE 0 — Decisões estratégicas + +**Objetivo:** Travar as decisões que vão moldar todo o resto. Sem isso, qualquer trabalho técnico vai ter que ser refeito. + +### 0.1 — Escolher modelo de cobrança + +| Modelo | Vantagem | Desvantagem | Minha recomendação | +|--------|----------|-------------|---------------------| +| Assinatura mensal | Receita recorrente previsível | Cliente não gosta de mensal | ❌ Não começar com | +| Assinatura anual | Receita previsível + desconto incentiva | Ticket mais alto | ✅ **Modelo principal** | +| Licença perpétua | Cliente paga uma vez e esquece | Receita não-recorrente | ✅ **Modelo secundário** | +| Por projeto | Acompanha uso real do engenheiro | Dificulta automação | ❌ Não agora | + +**Decisão sugerida:** +- **Plano 1 — Anual:** R$ 1.500/ano (~R$ 125/mês) +- **Plano 2 — Perpétua:** R$ 4.000 (atualização de norma inclusa por 2 anos) +- **Plano 3 — Empresa (5 usuários):** R$ 6.000/ano +- **Trial:** 14 dias grátis com todas as funções (gate automático) + +### 0.2 — Domínio e marca + +- Verificar disponibilidade de `brainwind.com.br` (registro.br, ~R$ 40/ano) +- Alternativas: `brainwind.app`, `brainwind.com`, `brainwind.eng.br` +- Logo: pode começar com texto estilizado + ícone de vento (Fiverr/99designs, R$ 100-300, ou gerador de logo AI grátis) +- Cor primária: azul escuro/azul técnico (engenharia remete a confiança) +- Tipografia: Inter ou Manrope (clean, técnica) + +### 0.3 — Canal de checkout + +| Gateway | Custo | Nota fiscal | Webhook | Recomendação | +|---------|-------|-------------|---------|--------------| +| **Kiwify** | ~R$ 0 + 9.99% | Sim | Sim, simples | ✅ **Principal** | +| Hotmart | ~R$ 0 + 9.99% | Sim | Sim | Alternativa | +| Eduzz | ~R$ 0 + 9.99% | Sim | Sim | Alternativa | +| Asaas | R$ 0-10 + 2,49% | Não | Sim | Para assinatura recorrente | +| Stripe direto | 4,99% + R$ 0,40 | Não | Sim | Para internacional (não agora) | + +**Decisão sugerida:** Começar com **Kiwify**. Depois, se crescer, considerar Asaas pra recorrência pura (margem maior). + +### 0.4 — Stack de auth + persistência + +- ✅ **Supabase Auth + Postgres + RLS** (já rodando na VPS, custo zero) +- Email/senha + OAuth Google +- Tabela `licenses` com chave única por usuário +- Tabela `projects` com `user_id` e RLS + +### 0.5 — Forma jurídica + +- Pra emitir nota e receber de pessoa jurídica, **precisa de CNPJ** +- MEI funciona pra R$ 81k/ano (teto de receita) +- Simples Nacional se passar do MEI (R$ 100-500/mês de contador) +- Abrir MEI: 30 minutos no portal do empreendedor, grátis +- **Decisão a tomar:** abrir MEI antes do lançamento oficial + +--- + +## 🛠️ FASE 1 — Infraestrutura comercial (auth + licença + persistência) + +**Objetivo:** Transformar o app local num SaaS com autenticação, licenciamento e persistência na nuvem. Sem isso, ninguém compra — é um arquivo Excel glorificado. + +**Duração:** 2-3 semanas +**Pré-requisito:** Fase 0 + +### 1.1 — Configurar Supabase para BrainWind + +1. Criar projeto no Supabase da VPS (`supabase` service, porta 5432) +2. Schema SQL inicial: + ```sql + -- Tabela de usuários (Supabase Auth já cria auth.users) + CREATE TABLE public.user_profiles ( + id UUID REFERENCES auth.users(id) PRIMARY KEY, + full_name TEXT, + company TEXT, + crea TEXT, + phone TEXT, + created_at TIMESTAMPTZ DEFAULT now() + ); + + -- Tabela de licenças + CREATE TABLE public.licenses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) NOT NULL, + license_key TEXT UNIQUE NOT NULL, + plan TEXT NOT NULL CHECK (plan IN ('annual', 'perpetual', 'enterprise')), + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'expired', 'revoked')), + started_at TIMESTAMPTZ DEFAULT now(), + expires_at TIMESTAMPTZ, + kiwify_order_id TEXT, + created_at TIMESTAMPTZ DEFAULT now() + ); + + -- Tabela de projetos (substitui IndexedDB) + CREATE TABLE public.projects ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) NOT NULL, + name TEXT NOT NULL, + module TEXT NOT NULL, + data JSONB NOT NULL, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() + ); + + -- RLS: usuário só vê seus próprios dados + ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY; + ALTER TABLE public.licenses ENABLE ROW LEVEL SECURITY; + ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY; + + CREATE POLICY "Users can view own profile" ON public.user_profiles + FOR SELECT USING (auth.uid() = id); + CREATE POLICY "Users can update own profile" ON public.user_profiles + FOR UPDATE USING (auth.uid() = id); + + CREATE POLICY "Users can view own licenses" ON public.licenses + FOR SELECT USING (auth.uid() = user_id); + + CREATE POLICY "Users can CRUD own projects" ON public.projects + FOR ALL USING (auth.uid() = user_id); + ``` + +3. Variáveis de ambiente no `.env` do app: + ``` + VITE_SUPABASE_URL=https://supabase.reifonas.cloud + VITE_SUPABASE_ANON_KEY= + ``` + +### 1.2 — Implementar autenticação no app + +1. Instalar cliente: `npm install @supabase/supabase-js` +2. Criar `src/lib/supabase.ts` com cliente singleton +3. Criar `src/pages/Login.tsx`: + - Login com email/senha + - Login com Google OAuth + - Esqueci senha +4. Criar `src/pages/Signup.tsx`: + - Cadastro com email/senha + - Confirmação por email (Supabase já gerencia) +5. Criar `src/store/auth.ts` (Zustand): + - State: `user`, `session`, `license`, `loading` + - Actions: `signIn`, `signOut`, `signUp`, `checkLicense` +6. Adicionar rota `/login` e `/signup` no `App.tsx` +7. **Guard de rota:** se não autenticado, redireciona pra `/login` +8. Persistir sessão (Supabase já faz via localStorage) + +### 1.3 — Sistema de licença (client-side) + +1. Criar `src/lib/license.ts`: + - `getLicenseKey()` → busca na tabela `licenses` ativa do user + - `validateLicense()` → confirma status = 'active' e não expirada + - `requireLicense()` → throw se inválida +2. Criar `src/components/LicenseGate.tsx`: + - Verifica licença no carregamento do app + - Se sem licença válida E sem trial ativo: mostra tela "Ative sua licença" + - Se trial ativo: mostra banner "Faltam X dias" + - Se licença válida: libera tudo +3. **Trial automático:** + - No primeiro login, criar registro de trial com 14 dias + - Verificação: `trial_started_at + 14 dias > now()` +4. **Cache local da licença** pra não bater no Supabase toda hora + - Validar no carregamento, depois a cada 6h + +### 1.4 — Migração de IndexedDB para Supabase + +1. Criar `src/lib/storage-adapter.ts`: + - Interface comum: `saveProject`, `loadProject`, `listProjects`, `deleteProject` + - Implementação A: `IndexedDBAdapter` (atual) + - Implementação B: `SupabaseAdapter` (novo) + - **Strategy pattern** — escolhe baseado em `user.authenticated` +2. Manter IndexedDB como fallback offline (sync quando voltar online) +3. Migração one-time: ao primeiro login, copiar projetos do IndexedDB pro Supabase + +### 1.5 — Telemetria mínima (opcional mas útil) + +- Contar projetos criados por usuário +- Saber quais módulos são mais usados +- **NÃO** telemetria invasiva — apenas métricas de uso agregado +- LGPD: política de privacidade clara, opt-in + +### 1.6 — Testes da Fase 1 + +- Teste E2E: signup → login → criar projeto → salvar → listar +- Teste de licença: trial expira → gate aparece +- Teste de licença: chave válida → acesso liberado + +--- + +## 🎨 FASE 2 — Identidade visual + landing page + +**Objetivo:** Ter uma cara profissional + página de vendas que converta visitante em lead. + +**Duração:** 1-2 semanas +**Pré-requisito:** Fase 0 (paralela à Fase 1) + +### 2.1 — Identidade visual + +1. **Logo:** + - Opção A: contratar Fiverr (R$ 80-200, 2-3 dias) + - Opção B: usar gerador de logo AI (Looka, Brandmark) — grátis/barato + - Opção C: começar só com wordmark (texto estilizado) +2. **Paleta de cores:** + - Primária: `#1e40af` (azul confiança) ou `#0f766e` (verde técnico) + - Secundária: `#f59e0b` (âmbar — destaque) + - Neutros: `#0f172a` (texto), `#64748b` (subtexto), `#f1f5f9` (background) +3. **Tipografia:** + - Heading: Inter Bold / Manrope Bold + - Body: Inter Regular / Manrope Regular + - Mono (código): JetBrains Mono / Fira Code +4. **Aplicação no app:** + - Header com logo + - Botões e CTAs com cor primária + - Footer com links legais + +### 2.2 — Landing page + +1. **Stack:** Coolify dessa VPS (subdomínio `app.brainwind.com.br` aponta pra landing, e o app SaaS fica em `app.brainwind.com.br/app`) +2. **Estrutura da landing (1 página):** + - **Hero:** Título grande + subtítulo + CTA "Começar trial grátis" + - **Problema:** "Calcular vento no Excel dá 4h e sempre dá medo de errar a norma" + - **Solução:** "BrainWind calcula em 5 minutos com NBR 6123:2023 atualizada" + - **Demo:** GIF/vídeo de 30 segundos mostrando o app calculando um galpão + - **Features:** Lista dos 9 tipos de estrutura cobertos + - **Para quem é:** Engenheiros estruturais, calculistas, projetistas + - **Validação:** "Auditado por IA independente, 91 cenários validados, NBR 6123:2023 completa" + - **Preço:** 3 cards (Anual R$ 1.500 / Perpétua R$ 4.000 / Empresa R$ 6.000) + - **FAQ:** 6-8 perguntas (norma atualizada?, tem nota fiscal?, roda offline?, ...) + - **CTA final:** "Comece grátis por 14 dias" +3. **SEO básico:** + - Title: "BrainWind — Cálculo de vento conforme NBR 6123:2023" + - Meta description otimizada + - Schema.org SoftwareApplication + +### 2.3 — Páginas legais (obrigatórias) + +1. **Termos de Uso** — pode usar template + adaptar +2. **Política de Privacidade** — LGPD compliance (Supabase tem template) +3. **Política de Cookies** (se usar) +4. **Disclaimer técnico:** "Software auxiliar. Profissional habilitado (CREA) é responsável pelo projeto." + +### 2.4 — Email transacional + +1. Criar `contato@brainwind.com.br` (Zoho Mail grátis até 5 usuários, ou usar Gmail com domínio próprio via Cloudflare Email Routing — grátis) +2. Templates de email: + - Boas-vindas (após signup) + - Confirmação de licença (após compra) + - Aviso de expiração (7 dias antes) + - Email de expiração +3. **Não precisa de ferramenta cara** — Supabase ou Resend (grátis até 100 emails/dia) + +--- + +## 💳 FASE 3 — Empacotamento de venda (checkout + webhook) + +**Objetivo:** Conectar o gateway de pagamento ao sistema de licença. Quando o cliente paga, recebe acesso automaticamente. + +**Duração:** 1 semana +**Pré-requisito:** Fases 1 e 2 + +### 3.1 — Criar produtos no Kiwify + +1. Conta Kiwify (kyc com CPF/CNPJ) +2. Criar 3 produtos: + - **BrainWind — Licença Anual** (R$ 1.500, recorrência anual) + - **BrainWind — Licença Perpétua** (R$ 4.000, pagamento único) + - **BrainWind — Empresa 5 usuários** (R$ 6.000, recorrência anual) +3. Configurar: + - URL de obrigado: `brainwind.com.br/obrigado?order=XYZ` + - Webhook URL: `https://app.brainwind.com.br/api/webhook/kiwify` + - Email de comprador: coletar (Kiwify faz isso) + +### 3.2 — Endpoint de webhook + +1. Criar pequeno serviço backend (FastAPI na VPS ou Edge Function Supabase): + - `POST /api/webhook/kiwify` recebe payload do Kiwify + - Valida assinatura (Kiwify envia hash) + - Identifica produto comprado → plano correspondente + - Verifica se já existe usuário com esse email + - **Se sim:** cria licença e vincula + - **Se não:** cria usuário via Supabase Admin API + cria licença + envia email de boas-vindas + - Resposta 200 OK + +2. **Importante:** proteger endpoint com validação de assinatura do Kiwify + +### 3.3 — Fluxo de pós-compra + +1. Cliente paga no Kiwify +2. Kiwify chama webhook +3. Webhook cria licença no Supabase +4. Sistema envia email: + - Link de acesso + - License key (se modelo perpétuo) + - Instrução de primeiro acesso +5. Cliente entra no app → vê status "Ativo" automaticamente + +### 3.4 — Renovação e expiração + +1. **Cron diário** (rodar na VPS, PM2 ou cronjob): + - Verifica licenças próximas de expirar (7 dias antes) + - Envia email de aviso + - Se passou da data: marca como `expired` +2. **Reativação:** cliente paga de novo → webhook reativa licença + +### 3.5 — Testes da Fase 3 + +- Teste de webhook em sandbox do Kiwify (eles têm ambiente de teste) +- Teste de fluxo completo: comprar → webhook → licença criada → login → acesso liberado +- Teste de expiração: forçar data de expiração no passado → gate aparece + +--- + +## 📢 FASE 4 — Marketing + aquisição + +**Objetivo:** Gerar leads e converter em clientes pagantes. + +**Duração:** 1-2 semanas (esforço contínuo depois) +**Pré-requisito:** Fases 1, 2 e 3 + +### 4.1 — Identificar personas + +**Persona primária: "João, o calculista"** +- Idade: 30-55 anos +- Cargo: Engenheiro estrutural autônomo ou em escritório pequeno/médio +- Atende projetos de galpão industrial, silo, cobertura metálica +- Usa Eberick/TQS para concreto, mas vento faz em planilha +- Dor: 4h por projeto recalculando, medo de errar versão da norma +- Onde está: grupos Facebook de engenharia, LinkedIn, YouTube técnico + +**Persona secundária: "Marina, a projetista"** +- Idade: 25-40 anos +- Cargo: Projetista em escritório de cálculo estrutural +- Faz projetos repetitivos (galpões padronizados) +- Dor: retrabalho, normas desatualizadas + +### 4.2 — Mensagem central (copy) + +**Título hero:** +> "Cálculo de vento NBR 6123 em 5 minutos, sem planilha, sem erro de norma" + +**Subtítulo:** +> "BrainWind calcula pressão, arrasto, atrito, Cpi e coeficientes aerodinâmicos para galpões, silos, abóbadas, cúpulas, muros, pontes, torres e estruturas reticuladas. Auditado e validado contra 91 cenários da norma." + +### 4.3 — Conteúdo de aquisição + +1. **Vídeo de demonstração (3 min):** + - Mostra fluxo: criar projeto → inserir dimensões → ver zonas de pressão → exportar PDF + - Hospedar no YouTube (unlisted ou público) + - Usar como principal material de venda + +2. **Posts LinkedIn (engenheiro-para-engenheiro):** + - "Quanto tempo você perde recalculando vento de projeto?" + - "A NBR 6123:2023 mudou 3 coisas que você talvez ainda não atualizou" + - "Comparação: tempo de cálculo Excel vs BrainWind" + - Frequência: 2-3x por semana + +3. **Instagram Reels:** + - Versões curtas (15-30s) do vídeo de demo + - "Tela + voz, sem aparecer" + - Frequência: 2-3x por semana + +4. **Posts em grupos Facebook:** + - "Estruturalistas do Brasil" + - "Engenharia Estrutural" + - "Cálculo Estrutural — Galpões" + - "NBR 6118 / 6123 / 8800 — Dúvidas" + - **Não spammar** — participar como membro, oferecer ajuda, mencionar BrainWind quando pertinente + +5. **Artigo técnico (blog):** + - "O que mudou na NBR 6123:2023 vs 2013?" + - SEO: rankeia pra "NBR 6123:2023 alterações" + - CTA no final: "Calculadora online que aplica a nova norma" + +### 4.4 — Estratégia de beta-tester + +**Objetivo:** 20-30 engenheiros usando de graça em troca de feedback + depoimento. + +1. Recrutar nos grupos Facebook e LinkedIn: + - Post: "Estou lançando um software de cálculo de vento conforme NBR 6123:2023. Procuro 30 engenheiros pra usar de graça por 30 dias em troca de feedback." +2. Oferecer: + - 30 dias grátis (trial estendido) + - 50% de desconto vitalício se converter depois + - Acesso ao roadmap (influência no que é construído) +3. Coletar: + - Feedback semanal (formulário curto, 5 perguntas) + - Depoimentos em vídeo ou texto (com autorização pra usar em marketing) + - Bug reports +4. Cronograma: + - Semana 1-2: recrutamento + - Semana 3-6: uso + feedback + - Semana 7: ajustes finais baseados em feedback + +### 4.5 — Materiais de venda + +1. **One-pager PDF** (1 página com tudo que o app faz + preço) + - Pra mandar por WhatsApp quando alguém perguntar +2. **Tabela comparativa** ("BrainWind vs Excel vs Eberick") +3. **Prints do app** com destaque pros diferenciais +4. **Screenshot do relatório PDF** (motor de venda mais forte) + +--- + +## 🧪 FASE 5 — Beta-tester + ajustes finais + +**Objetivo:** Validar com usuários reais antes de abrir pra todo mundo. + +**Duração:** 2-4 semanas +**Pré-requisito:** Fases 1-4 + +### 5.1 — Recrutar beta-testers + +- 20-30 engenheiros (alvo da persona) +- Mix: autônomos + escritório pequeno + projetistas +- Captar via grupos Facebook e LinkedIn (Fase 4.4) + +### 5.2 — Onboarding + +1. Email de boas-vindas com: + - Link de cadastro + - License key de trial estendido + - Manual rápido PDF (5 páginas) + - Link do vídeo de 3 min + - Canal de suporte (WhatsApp ou Discord) +2. Webinar de lançamento interno (opcional): 30min apresentando o app + Q&A + +### 5.3 — Coletar feedback + +**Métricas a acompanhar:** +- Quantos se cadastram +- Quantos efetivamente criam um projeto +- Quantos completam um cálculo até o PDF +- NPS (Net Promoter Score) +- Bugs reportados +- Pedidos de feature + +**Formulário semanal (Google Forms):** +1. Como foi sua experiência essa semana? (1-10) +2. Qual feature mais te ajudou? +3. O que está faltando? +4. Encontrou algum bug? Qual? +5. Recomendaria pra um colega? (0-10 NPS) + +### 5.4 — Ajustes + +- Corrigir bugs prioritários (P0/P1) +- Ajustar UX confuso (se houver) +- Adicionar features muito pedidas (1-2 no máximo, pra não virar bagunça) + +### 5.5 — Coletar depoimentos + +- Email ou vídeo curto (1 min) de cada beta-tester satisfeito +- **Autorização por escrito** pra usar em marketing + +--- + +## 🚀 FASE 6 — Lançamento oficial + +**Objetivo:** Abrir venda pra todo mundo. + +**Duração:** 1 dia (o lançamento em si) + 1 semana de acompanhamento +**Pré-requisito:** Fase 5 + +### 6.1 — Checklist pré-lançamento + +- [ ] Landing page no ar e testada +- [ ] Checkout Kiwify funcionando (teste com R$ 1 real) +- [ ] Webhook validando e criando licença +- [ ] App funcionando com auth + licença +- [ ] PDF de relatório profissional +- [ ] Termos de Uso + Política de Privacidade publicados +- [ ] Email transacional funcionando +- [ ] Suporte (WhatsApp Business) ativo +- [ ] MEI/CNPJ aberto +- [ ] Conta Kiwify aprovada +- [ ] Domínio registrado +- [ ] 3-5 depoimentos de beta-testers em mãos +- [ ] Vídeo de demo publicado +- [ ] Posts de lançamento agendados (LinkedIn, Instagram, FB) + +### 6.2 — Dia do lançamento + +1. **Post LinkedIn** (8h): "BrainWind v1.0 — cálculo de vento NBR 6123:2023 em 5 minutos" +2. **Post Instagram** (10h): Reel de 30s +3. **Posts em grupos Facebook** (12h, 16h, 20h): evitar spam, variar mensagens +4. **Email para lista** (se tiver): avisar do lançamento +5. **Atualizar landing** com badge "Lançamento" + contagem regressiva +6. **Atendimento extra** no suporte (primeiras 48h) + +### 6.3 — Semana pós-lançamento + +- Acompanhar conversão (visitantes → cadastros → compras) +- Responder todo feedback rápido +- Resolver bugs urgentes em < 24h +- Coletar mais depoimentos + +### 6.4 — Definição de sucesso (primeiros 90 dias) + +- 🎯 100 trials iniciados +- 🎯 20 clientes pagantes +- 🎯 R$ 30.000 de receita +- 🎯 NPS médio ≥ 50 + +--- + +## 📈 FASE 7 — Escala (pós-lançamento) + +**Objetivo:** Crescer a base de clientes e expandir o produto. + +### 7.1 — Versão desktop instalável + +- Empacotar webapp com **Tauri** (binário menor que Electron, ~10MB) +- Windows .exe + macOS .dmg + Linux .AppImage +- Vantagem: sensação de "software sério", pode usar offline (com sync posterior) +- **Quando:** 1-2 meses após lançamento, se houver demanda + +### 7.2 — Integrações + +- **Ftool** (exportação .txt de carregamentos de vento) +- **SAP2000 / Robot** (exportação de cargas) +- **AutoCAD** (importação de geometria) +- **Excel** (importação de inputs em massa) + +### 7.3 — Novos módulos NBR + +- NBR 8800 (estruturas metálicas — já parcialmente coberto em pipe rack) +- NBR 6118 (concreto armado — parceria com Eberick seria mais estratégico) +- NBR 15421 (estruturas em situação de incêndio) +- **Cuidado:** não virar canivete suíço. Foco em vento = diferencial. + +### 7.4 — Versão mobile (consulta rápida) + +- Capacitor ou React Native empacotando webapp +- Cálculos de consulta rápida em obra +- Versão simplificada (input → resultado, sem edição de projeto) + +### 7.5 — Marketplace de templates + +- Usuários publicam templates de geometrias comuns +- Comissão pra você (10-20%) +- Cria comunidade em torno do produto + +### 7.6 — Programa de afiliados + +- Engenheiros indicam → ganham % recorrente +- Cupons de desconto +- Programa Kiwify já tem suporte nativo + +### 7.7 — Internacionalização + +- Outras normas de vento: ASCE 7 (EUA), Eurocódigo 1 (UE), AS/NZS 1170 (Austrália) +- Mercado americano e europeu paga muito mais (US$ 50-200/mês seria trivial) +- **Quando:** depois de validar no Brasil + +--- + +## 📊 Métricas e KPIs (acompanhar sempre) + +| Métrica | Onde medir | Meta | +|---------|-----------|------| +| Visitantes únicos/mês na landing | Plausible Analytics (LGPD-friendly) | 1.000+ | +| Conversão visitante → trial | Plausible + Supabase | 5-10% | +| Conversão trial → pago | Supabase + Kiwify | 20-30% | +| Churn mensal | Kiwify + Supabase | < 5% | +| NPS | Formulário no app | ≥ 50 | +| Tempo médio de uso | Telemetria | > 30min/sessão | +| Tickets de suporte/dia | WhatsApp / email | < 10 | + +--- + +## 💰 Projeção financeira (cenário base) + +| Mês | Trials novos | Pagantes novos | MRR | +|-----|--------------|----------------|-----| +| 1 (lançamento) | 30 | 6 | R$ 750 | +| 2 | 50 | 10 | R$ 1.250 + R$ 1.250 (renov.) | +| 3 | 80 | 16 | R$ 2.000 | +| 6 | 150/mês | 30/mês | R$ 3.750 | +| 12 | 250/mês | 50/mês | R$ 6.250 | + +Cenário conservador baseado em produto técnico de nicho. Considerar que **80% da receita vem de 20% dos clientes** (licenças perpétuas). + +**Custos fixos:** +- VPS (já tem) — R$ 0 incremental +- Domínio — R$ 50/ano +- Supabase (mesma VPS) — R$ 0 +- Kiwify — ~10% sobre venda +- Email transacional — R$ 0 (Resend free tier) +- Contador (MEI → Simples) — R$ 100-300/mês + +**Margem:** ~85% mesmo com Kiwify + contador. + +--- + +## ⏱️ Cronograma macro + +``` +Mês 1 Mês 2 Mês 3 Mês 4 Mês 5+ +├─────────┼──────────────┼───────────┼───────────┼────────── +│ Fase 0 │ Fase 1 + 2 │ Fase 3 │ Fase 4+5 │ Fase 6 │ Fase 7 +│ Decisões│ Auth + Land. │ Checkout │ Marketing │ Lança │ Escala +│ │ │ │ + Beta │ │ +└─────────┴──────────────┴───────────┴───────────┴──────────┘ +``` + +Estimativa total até lançamento: **3-4 meses** com trabalho dedicado de meio período. + +--- + +## ✅ Próxima ação imediata + +**Escolha UMA das tarefas abaixo para começar HOJE:** + +1. **[ ] Abrir MEI** — portal do empreendedor, 30 minutos +2. **[ ] Verificar domínio brainwind.com.br** — registro.br +3. **[ ] Criar conta Kiwify** — kiwify.com.br +4. **[ ] Esboçar landing page** — Figma/Canva/Word +5. **[ ] Começar implementação Supabase Auth** — código + +Qual quer atacar primeiro? diff --git a/app/src/App.tsx b/app/src/App.tsx index f173e07..79e6098 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -14,7 +14,7 @@ import PiperackModule from './pages/PiperackModule'; import { Home, Settings, Menu, Cylinder, Church, CircleDot, Square, Layers, BarChart3, Activity, Warehouse, - Settings2, Sun, Moon, Frame, + Settings2, Sun, Moon, Frame, FolderOpen, BookOpen } from 'lucide-react'; import { useState } from 'react'; import { cn } from '@/lib/utils'; @@ -141,13 +141,14 @@ function AppLayout({ children }: { children: React.ReactNode }) { ); })} - + +
@@ -156,18 +157,49 @@ function AppLayout({ children }: { children: React.ReactNode }) { } /> - -
+ + + {!isCollapsed && Arquivos} + + - {!isCollapsed && {t('nav_settings')}} + {!isCollapsed && Configurações} + + + + + {!isCollapsed && Glossário}
diff --git a/app/src/components/Glossary.tsx b/app/src/components/Glossary.tsx new file mode 100644 index 0000000..ac6137c --- /dev/null +++ b/app/src/components/Glossary.tsx @@ -0,0 +1,87 @@ +import React, { useState, useMemo } from 'react'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Search, BookOpen } from 'lucide-react'; +import { nbr6123Glossary } from '@/data/glossary'; + +const Glossary: React.FC = () => { + const [searchTerm, setSearchTerm] = useState(''); + + const filteredTerms = useMemo(() => { + const termLower = searchTerm.toLowerCase(); + return nbr6123Glossary.filter(item => { + if (item.term.toLowerCase().includes(termLower)) return true; + if (item.definition.toLowerCase().includes(termLower)) return true; + if (item.tags.some(t => t.toLowerCase().includes(termLower))) return true; + if (item.reference.toLowerCase().includes(termLower)) return true; + return false; + }); + }, [searchTerm]); + + return ( +
+ + +
+
+ +
+
+ Glossário NBR 6123 + + Dicionário técnico com termos aerodinâmicos e variáveis normativas explicadas de forma didática. + +
+
+
+ +
+ + setSearchTerm(e.target.value)} + /> +
+
+
+ +
+ {filteredTerms.length > 0 ? ( + filteredTerms.map((item) => ( + + + + {item.term} + + + {item.reference} + + + +

+ {item.definition} +

+
+ {item.tags.map((tag, idx) => ( + + {tag} + + ))} +
+
+
+ )) + ) : ( +
+ Nenhum termo encontrado para "{searchTerm}". +
+ )} +
+
+ ); +}; + +export default Glossary; diff --git a/app/src/components/SaveModuleDialog.tsx b/app/src/components/SaveModuleDialog.tsx new file mode 100644 index 0000000..9bfbc88 --- /dev/null +++ b/app/src/components/SaveModuleDialog.tsx @@ -0,0 +1,121 @@ +import React, { useState } from 'react'; +import { Button } from './ui/button'; +import { Input } from './ui/input'; +import { Save, FileJson, FileBox } from 'lucide-react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from './ui/dialog'; +import { useProjects } from '../lib/hooks/useProjects'; +import { useWindStore } from '../store/appStore'; + +interface SaveModuleDialogProps { + moduleType: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack'; + inputs: Record; + triggerButton?: React.ReactNode; +} + +export const SaveModuleDialog: React.FC = ({ moduleType, inputs, triggerButton }) => { + const { save } = useProjects(); + const windStore = useWindStore(); + const [open, setOpen] = useState(false); + const [name, setName] = useState(''); + const [type, setType] = useState<'projeto' | 'template'>('projeto'); + + // Gera um nome sugerido ao abrir + React.useEffect(() => { + if (open && !name) { + const date = new Date().toLocaleDateString('pt-BR').replace(/\//g, ''); + const prefix = type === 'projeto' ? 'PRJ' : 'TPL'; + const modName = moduleType.charAt(0).toUpperCase() + moduleType.slice(1); + setName(`${prefix}-${modName}-${date}`); + } + }, [open, type, moduleType, name]); + + const handleSave = async () => { + if (!name.trim()) return; + + const windData = type === 'projeto' ? { + v0: windStore.v0, + s1: windStore.s1, + s3: windStore.s3, + s3Group: windStore.s3Group, + terrainCategory: windStore.terrainCategory, + largestDimension: windStore.largestDimension, + heightZ: windStore.heightZ, + } : undefined; + + await save({ + name, + type, + module: moduleType, + inputs, + windData, + createdAt: Date.now(), + updatedAt: Date.now(), + }); + + setOpen(false); + }; + + return ( + + + {triggerButton || ( + + )} + + + + Salvar Módulo + + Escolha como deseja salvar este módulo. + Projetos incluem os dados de vento atuais. Templates salvam apenas a geometria da estrutura. + + +
+
+ + +
+ +
+ + setName(e.target.value)} + placeholder="Digite um nome para identificar..." + /> +
+
+ + + + +
+
+ ); +}; diff --git a/app/src/components/three/Piperack3D.tsx b/app/src/components/three/Piperack3D.tsx index 1a7c551..62c2ce5 100644 --- a/app/src/components/three/Piperack3D.tsx +++ b/app/src/components/three/Piperack3D.tsx @@ -55,48 +55,52 @@ function PiperackModel({ ); } - // Viga Longitudinal da fileira + // Viga Longitudinal da fileira (representando a face reticulada do pórtico) frames.push( - + {/* Opacidade ajustada para atingir 90% quando a solidez for 1.0 */} + ); } - // Vigas transversais conectando as fileiras nas pontas - if (numFrames > 1) { - frames.push( - - - - - ); - frames.push( - - - - - ); - } // Gera as tubulações correndo longitudinalmente (ao longo de X) - const pipeMeshes = pipes.map((pipe, idx) => { - // Distribui os tubos ao longo do espaçamento transversal (Z) - const zPos = numFrames > 1 - ? -zOffset + 0.5 + (idx % Math.max(pipes.length, 1)) * (totalSpacing - 1) / Math.max(pipes.length - 1, 1) || 0 - : 0; - // elevationOffset varia de 0 (base da estrutura reticulada) a height (topo da estrutura) - // A base da estrutura fica em: rackTop - height, que é elevation - height/2 - const yPos = (elevation - height/2) + pipe.elevationOffset + pipe.diameter/2; - const color = pipeColors[idx % pipeColors.length]; + // Agrupa tubos pela mesma elevação para evitar colisões e distribuí-los lado a lado (ao longo de Z) + const pipeMeshes: any[] = []; + const groups: Record = {}; + + pipes.forEach((pipe, idx) => { + // Arredondamos a elevação para agrupar valores muito próximos + const key = Math.round(pipe.elevationOffset * 100) / 100; + if (!groups[key]) groups[key] = []; + groups[key].push({pipe, idx}); + }); - return ( - - - - - ); + Object.values(groups).forEach(group => { + const gap = 0.2; // 20cm de espaçamento entre tubos na mesma elevação + const totalDiam = group.reduce((sum, item) => sum + item.pipe.diameter, 0); + const totalGroupWidth = totalDiam + gap * (group.length - 1); + + // Inicia a distribuição centralizada em Z=0 + let currentZ = -totalGroupWidth / 2; + + group.forEach((item) => { + const { pipe, idx } = item; + const zPos = currentZ + pipe.diameter / 2; + currentZ += pipe.diameter + gap; + + const yPos = (elevation - height/2) + pipe.elevationOffset + pipe.diameter/2; + const color = pipeColors[idx % pipeColors.length]; + + pipeMeshes.push( + + + + + ); + }); }); return ( diff --git a/app/src/data/glossary.ts b/app/src/data/glossary.ts new file mode 100644 index 0000000..cc200e2 --- /dev/null +++ b/app/src/data/glossary.ts @@ -0,0 +1,150 @@ +export interface GlossaryTerm { + id: string; + term: string; + definition: string; + reference: string; + tags: string[]; +} + +export const nbr6123Glossary: GlossaryTerm[] = [ + { + id: 'v0', + term: 'Velocidade Básica do Vento (V₀)', + definition: 'Velocidade do vento obtida a partir de dados meteorológicos. É definida como a velocidade média de 3 segundos, excedida em média uma vez em 50 anos, a 10 metros de altura, sobre terreno plano e aberto. Usada como base para todos os cálculos aerodinâmicos na NBR 6123.', + reference: 'NBR 6123:2023 - Seção 5.2', + tags: ['velocidade', 'isopletas', 'parâmetro básico'] + }, + { + id: 's1', + term: 'Fator Topográfico (S₁)', + definition: 'Coeficiente que considera as variações do relevo do terreno. Leva em conta se o terreno é plano, fracamente acidentado, talude ou morro, aumentando a velocidade do vento em encostas e cumes.', + reference: 'NBR 6123:2023 - Seção 5.3', + tags: ['relevo', 'topografia', 'talude', 'morro'] + }, + { + id: 's2', + term: 'Fator de Rugosidade (S₂)', + definition: 'Coeficiente que leva em consideração a rugosidade do terreno (obstáculos como árvores e prédios) e as dimensões da edificação. Depende da categoria do terreno (I a V) e aumenta de acordo com a altura (z) da estrutura.', + reference: 'NBR 6123:2023 - Seção 5.4 / Tabela 3', + tags: ['rugosidade', 'altura', 'categoria', 'dimensões'] + }, + { + id: 's3', + term: 'Fator Estatístico (S₃)', + definition: 'Fator de probabilidade que considera o nível de segurança e a vida útil da edificação. Valores comuns são 1,0 para edificações normais (50 anos), mas pode ser maior (ex: hospitais) ou menor (ex: instalações temporárias).', + reference: 'NBR 6123:2023 - Seção 5.5', + tags: ['segurança', 'probabilidade', 'uso'] + }, + { + id: 'vk', + term: 'Velocidade Característica (Vₖ)', + definition: 'A velocidade do vento de projeto em uma determinada altura e situação, calculada multiplicando a velocidade básica pelos três fatores: Vₖ = V₀ × S₁ × S₂ × S₃.', + reference: 'NBR 6123:2023 - Seção 5.1', + tags: ['velocidade de projeto', 'fórmula'] + }, + { + id: 'q', + term: 'Pressão Dinâmica (q)', + definition: 'A energia cinética do vento transformada em pressão. É calculada a partir da velocidade característica: q = 0,613 × Vₖ² (em N/m²). Esta pressão é aplicada nas superfícies junto com os coeficientes aerodinâmicos.', + reference: 'NBR 6123:2023 - Seção 5.6', + tags: ['pressão', 'energia cinética'] + }, + { + id: 'cpe', + term: 'Coeficiente de Pressão Externa (Cpe)', + definition: 'Valor adimensional que indica como o vento atua nas faces externas de uma edificação (paredes e telhados). Valores positivos indicam pressão (empurrando a face), valores negativos indicam sucção (puxando a face).', + reference: 'NBR 6123:2023 - Seção 6.2 / Tabelas 6 a 12', + tags: ['pressão', 'sucção', 'externo', 'fachada', 'cobertura'] + }, + { + id: 'cpi', + term: 'Coeficiente de Pressão Interna (Cpi)', + definition: 'Valor que determina a pressão exercida pelo ar no interior da edificação, gerada devido a aberturas e frestas (portas, janelas, ventilação). Depende do índice de permeabilidade de cada face do prédio.', + reference: 'NBR 6123:2023 - Seção 6.3', + tags: ['pressão', 'interno', 'permeabilidade', 'aberturas'] + }, + { + id: 'barlavento', + term: 'Barlavento', + definition: 'Região ou face da edificação por onde o vento "chega" ou incide primeiro. Normalmente as superfícies a barlavento sofrem pressões positivas (empuxo contra a parede).', + reference: 'NBR 6123:2023 - Conceito Geral', + tags: ['incidência', 'pressão positiva', 'vento'] + }, + { + id: 'sotavento', + term: 'Sotavento', + definition: 'Região ou face da edificação que fica "escondida" ou protegida do vento incidente (a face traseira). Normalmente sofre pressões negativas (sucção), pois o vento passa e "puxa" o ar daquela região.', + reference: 'NBR 6123:2023 - Conceito Geral', + tags: ['traseira', 'sucção', 'esteira'] + }, + { + id: 'ca', + term: 'Coeficiente de Arrasto (Ca)', + definition: 'Coeficiente de força global. Diferente do Cpe, que atua pontualmente em painéis, o Ca define a força total de arrasto gerada no volume inteiro da estrutura (ex: chaminés, torres, pórticos).', + reference: 'NBR 6123:2023 - Seção 6.1.2', + tags: ['arrasto', 'força global', 'torres', 'cilindros'] + }, + { + id: 'esbeltez', + term: 'Índice de Esbeltez (λ)', + definition: 'Relação entre a altura da estrutura e a sua largura base (ou diâmetro). Estruturas muito esbeltas (finas e altas) são mais sensíveis aos efeitos do vento e exigem coeficientes de arrasto específicos e análises dinâmicas.', + reference: 'NBR 6123:2023 - Seção 6.1 / Diversas Tabelas', + tags: ['altura', 'largura', 'aerodinâmica'] + }, + { + id: 'vortex', + term: 'Desprendimento de Vórtices (Vortex Shedding)', + definition: 'Fenômeno dinâmico onde o vento, ao passar por uma estrutura (geralmente cilíndrica ou prismática), cria vórtices alternados nas laterais, causando oscilações transversais à direção do vento. Pode gerar ressonância estrutural perigosa.', + reference: 'NBR 6123:2023 - Seção 10.2', + tags: ['dinâmica', 'ressonância', 'cilindros', 'chaminés', 'vibração transversal'] + }, + { + id: 'flutter', + term: 'Esvoaçamento (Flutter)', + definition: 'Instabilidade aeroelástica dinâmica onde a estrutura (ex: tabuleiro de ponte estaiada) começa a girar e vibrar de forma autossustentada e crescente devido à interação complexa entre o vento e o formato da seção.', + reference: 'NBR 6123:2023 - Seção 11', + tags: ['pontes', 'aeroelasticidade', 'torção', 'instabilidade'] + }, + { + id: 'galloping', + term: 'Galope (Galloping)', + definition: 'Fenômeno dinâmico de instabilidade em flexão pura transversal à direção do vento. Comum em perfis não circulares ou cabos com formação de gelo/água assimétrica.', + reference: 'NBR 6123:2023 - Seção 11.4', + tags: ['dinâmica', 'pontes', 'cabos', 'vibração lateral'] + }, + { + id: 'phi', + term: 'Índice de Solidez (φ)', + definition: 'Fator que define a "porosidade" de uma estrutura vazada (como uma torre treliçada, letreiro vazado ou pipe-rack). É a área efetiva (cheia) dos perfis dividida pela área de contorno total do painel. Aumenta o coeficiente de arrasto quanto mais "sólida" for a estrutura.', + reference: 'NBR 6123:2023 - Seção 7', + tags: ['treliças', 'torres', 'permeabilidade', 'bloqueio'] + }, + { + id: 'eta', + term: 'Fator de Proteção (η)', + definition: 'Quando há pórticos ou treliças posicionados um atrás do outro (ex: pipe-racks ou torres retangulares), a face traseira sofre menor incidência do vento por estar "protegida" pela da frente. O fator η pondera essa redução.', + reference: 'NBR 6123:2023 - Seção 7 / Tabela 28', + tags: ['treliças', 'sombreamento', 'pipe-racks', 'arrasto conjunto'] + }, + { + id: 'rugosidade', + term: 'Categoria de Terreno', + definition: 'A norma define 5 categorias de rugosidade: I (superfícies lisas, mar calmo), II (campos abertos), III (terrenos planos com pequenos obstáculos, fazendas), IV (áreas urbanas, subúrbios densos) e V (centros de grandes cidades).', + reference: 'NBR 6123:2023 - Seção 5.4.1', + tags: ['terreno', 's2', 'urbanização', 'relevo'] + }, + { + id: 'forca_atrito', + term: 'Força de Atrito (Ff)', + definition: 'Força que o vento exerce tangencialmente às superfícies paralelas ao seu escoamento, devido à fricção superficial (ex: tetos muito compridos ou paredes extensas).', + reference: 'NBR 6123:2023 - Seção 6.1.5', + tags: ['fricção', 'tangencial', 'galpões compridos'] + }, + { + id: 'pressao_efetiva', + term: 'Pressão Efetiva (Δp)', + definition: 'A diferença real de pressão que atua em uma superfície (como parede ou telhado), combinando a ação externa (Cpe) e interna (Cpi). Fórmula: Δp = q × (Cpe - Cpi). Resulta na carga real em N/m² que a estrutura vai suportar.', + reference: 'NBR 6123:2023 - Seção 6.1.3', + tags: ['pressão resultante', 'cálculo final'] + } +]; diff --git a/app/src/lib/storage.ts b/app/src/lib/storage.ts index 24ea944..4c32114 100644 --- a/app/src/lib/storage.ts +++ b/app/src/lib/storage.ts @@ -8,8 +8,10 @@ export interface SavedProject { id?: number; name: string; - module: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics'; + type: 'projeto' | 'template'; + module: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack'; inputs: Record; + windData?: Record; createdAt: number; updatedAt: number; } diff --git a/app/src/pages/BridgeModule.tsx b/app/src/pages/BridgeModule.tsx index fab1a61..692a132 100644 --- a/app/src/pages/BridgeModule.tsx +++ b/app/src/pages/BridgeModule.tsx @@ -1,5 +1,8 @@ import React, { useMemo } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; + +import { useHydrationStore } from '@/store/hydrationStore'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { Slider } from '@/components/ui/slider'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; @@ -12,11 +15,32 @@ import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; + + const BridgeModule: React.FC = () => { const { v0, s1, terrainCategory, q } = useWindStore(); const [lp, setLp] = React.useState(120); const [width, setWidth] = React.useState(14); const [deckHeight, setDeckHeight] = React.useState(15); + const pendingLoad = useHydrationStore((s) => s.pendingLoad); + const clearPendingLoad = useHydrationStore((s) => s.clearPendingLoad); + + React.useEffect(() => { + if (pendingLoad && pendingLoad.module === 'bridge') { + const p = pendingLoad.inputs as any; + if (p.lp !== undefined) setLp(p.lp); + if (p.width !== undefined) setWidth(p.width); + if (p.deckHeight !== undefined) setDeckHeight(p.deckHeight); + if (p.heg !== undefined) setHeg(p.heg); + if (p.mass !== undefined) setMass(p.mass); + if (p.fv !== undefined) setFv(p.fv); + if (p.vk !== undefined) setVk(p.vk); + if (p.alpha !== undefined) setAlpha(p.alpha); + clearPendingLoad(); + } + }, [pendingLoad, clearPendingLoad]); + + // Constantes normativas de flutter da seção const [heg, setHeg] = React.useState(2.5); const [mass, setMass] = React.useState(18000); const [fv, setFv] = React.useState(0.6); @@ -246,7 +270,13 @@ const BridgeModule: React.FC = () => {
- +
+ + +
diff --git a/app/src/pages/GalpaoModule.tsx b/app/src/pages/GalpaoModule.tsx index fbb102e..348a63e 100644 --- a/app/src/pages/GalpaoModule.tsx +++ b/app/src/pages/GalpaoModule.tsx @@ -15,6 +15,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; +import { useHydrationStore } from '@/store/hydrationStore'; const GalpaoModule: React.FC = () => { const { @@ -33,6 +35,21 @@ const GalpaoModule: React.FC = () => { windAngle, setWindAngle, } = useWindStore(); + + const pendingLoad = useHydrationStore((s) => s.pendingLoad); + const clearPendingLoad = useHydrationStore((s) => s.clearPendingLoad); + + React.useEffect(() => { + if (pendingLoad && pendingLoad.module === 'galpao') { + const p = pendingLoad.inputs as any; + if (p.width !== undefined) setWidth(p.width); + if (p.length !== undefined) setLength(p.length); + if (p.height !== undefined) setHeight(p.height); + if (p.roofPitch !== undefined) setRoofPitch(p.roofPitch); + clearPendingLoad(); + } + }, [pendingLoad, clearPendingLoad, setWidth, setLength, setHeight, setRoofPitch]); + return (
{/* Coluna Esquerda: Controles */} @@ -146,7 +163,6 @@ const GalpaoModule: React.FC = () => {
-
@@ -161,6 +177,17 @@ const GalpaoModule: React.FC = () => { {/* Coluna Direita-Inferior: Cargas Lineares (M9.2) */}
+ +
+
+ + +
+
+
diff --git a/app/src/pages/PiperackModule.tsx b/app/src/pages/PiperackModule.tsx index 6103788..0281fca 100644 --- a/app/src/pages/PiperackModule.tsx +++ b/app/src/pages/PiperackModule.tsx @@ -1,5 +1,8 @@ import React, { useMemo, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +import { useHydrationStore } from '@/store/hydrationStore'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { Slider } from '@/components/ui/slider'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -24,6 +27,23 @@ const PiperackModule: React.FC = () => { const [phiStruct, setPhiStruct] = useState(0.5); const [pipes, setPipes] = useState([]); + const pendingLoad = useHydrationStore((s) => s.pendingLoad); + const clearPendingLoad = useHydrationStore((s) => s.clearPendingLoad); + + React.useEffect(() => { + if (pendingLoad && pendingLoad.module === 'piperack') { + const p = pendingLoad.inputs as any; + if (p.elevation !== undefined) setElevation(p.elevation); + if (p.width !== undefined) setWidth(p.width); + if (p.height !== undefined) setHeight(p.height); + if (p.spacing !== undefined) setSpacing(p.spacing); + if (p.numFrames !== undefined) setNumFrames(p.numFrames); + if (p.pipes !== undefined) setPipes(p.pipes); + if (p.phiStruct !== undefined) setPhiStruct(p.phiStruct); + clearPendingLoad(); + } + }, [pendingLoad, clearPendingLoad]); + const addPipe = () => { setPipes([...pipes, { id: Math.random().toString(36).substr(2, 9), diameter: 0.5, elevationOffset: height / 2 }]); }; @@ -91,7 +111,7 @@ const PiperackModule: React.FC = () => { return (
-
+
@@ -101,9 +121,6 @@ const PiperackModule: React.FC = () => {

Vento transversal (Sec. 7 - NBR 6123)

-
- -
@@ -403,6 +420,16 @@ const PiperackModule: React.FC = () => { +
+
+ + +
+
+
diff --git a/app/src/pages/SettingsModule.tsx b/app/src/pages/SettingsModule.tsx index 81d60e3..9befd3a 100644 --- a/app/src/pages/SettingsModule.tsx +++ b/app/src/pages/SettingsModule.tsx @@ -1,27 +1,31 @@ import React, { useRef, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Sun, Moon, Laptop, Save, Trash2, Upload, CheckCircle2, AlertCircle, FlaskConical } from 'lucide-react'; +import { Sun, Moon, Laptop, Save, Trash2, Upload, FlaskConical, Download, FolderOpen, FileBox, FileJson } from 'lucide-react'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useTheme } from '@/lib/theme'; import { useProjects } from '@/lib/hooks/useProjects'; import { useWindStore } from '@/store/appStore'; +import { useHydrationStore } from '@/store/hydrationStore'; import { useI18n } from '@/store/i18nStore'; import { Badge } from '@/components/ui/badge'; +import { useNavigate, useLocation } from 'react-router-dom'; import { importProjectFromText, readProjectFile, - type ImportResult, } from '@/lib/import-project'; import AuditPanel from '@/components/AuditPanel'; +import Glossary from '@/components/Glossary'; const SettingsModule: React.FC = () => { const { theme, setTheme, effectiveTheme } = useTheme(); - const { projects, loading, error, remove } = useProjects(); + const { projects, loading, error, remove, save } = useProjects(); const { t, locale } = useI18n(); + const navigate = useNavigate(); + const location = useLocation(); + const setPendingLoad = useHydrationStore((s) => s.setPendingLoad); const fileInputRef = useRef(null); - const [importResult, setImportResult] = useState(null); const [importing, setImporting] = useState(false); const exportSnapshot = () => { @@ -30,12 +34,36 @@ const SettingsModule: React.FC = () => { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); - link.setAttribute('download', 'ventoapp-state.json'); + link.setAttribute('download', 'ventoapp-snapshot.json'); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; + const handleDownloadJSON = (project: any) => { + const blob = new Blob([JSON.stringify(project, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.setAttribute('href', url); + link.setAttribute('download', `${project.name}.json`); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + const handleLoadProject = (project: any) => { + if (project.type === 'projeto' && project.windData) { + // Restore wind data + useWindStore.setState(project.windData); + } + + // Set pending load + setPendingLoad(project.module, project.inputs); + + // Navigate to module + navigate(`/${project.module === 'isolated-roof' ? 'cobertura-isolada' : project.module === 'sign' ? 'muros' : project.module === 'bar' ? 'barras' : project.module === 'bridge' ? 'pontes' : project.module === 'dynamics' ? 'dinamica' : project.module === 'vault' ? 'abobada' : project.module === 'dome' ? 'cupula' : project.module}`); + }; + const handleImportClick = () => { fileInputRef.current?.click(); }; @@ -44,29 +72,51 @@ const SettingsModule: React.FC = () => { const file = event.target.files?.[0]; if (!file) return; setImporting(true); - setImportResult(null); try { const text = await readProjectFile(file); - const result = importProjectFromText(text); - setImportResult(result); + const parsed = JSON.parse(text); + if (parsed.module && parsed.inputs) { + // Formato SavedProject (Projeto ou Template) + const newProject = { + ...parsed, + name: parsed.name || 'Importado - ' + file.name, + type: parsed.type || 'projeto', + createdAt: Date.now(), + updatedAt: Date.now(), + }; + // Ensure type is correct + if (!['projeto', 'template'].includes(newProject.type)) newProject.type = 'projeto'; + + await save(newProject as any); + } else { + // Old snapshot import fallback + importProjectFromText(text); + } } catch (e) { - setImportResult({ - ok: false, - error: e instanceof Error ? e.message : 'Falha ao ler arquivo', - }); + console.error(e); } finally { setImporting(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; + const currentTab = new URLSearchParams(location.search).get('tab') || 'config'; + const isSettingsGroup = currentTab === 'config' || currentTab === 'testes'; + return (
-

{t('nav_settings')}

+

Configurações

+

+ {currentTab === 'gerenciador' ? 'Arquivos' : + currentTab === 'glossary' ? 'Glossário' : + 'Configurações'} +

- - - Configurações + navigate(`/settings?tab=${val}`)} className="w-full"> + + Preferências + Arquivos + GLOSSÁRIO Testes @@ -98,99 +148,6 @@ const SettingsModule: React.FC = () => { - - - {t('settings_projects')} - {t('settings_projects_desc')} - - -
-

- {t('settings_projects_count', { count: projects.length })} -

-
- - - -
-
- - {importResult && ( -
- {importResult.ok ? ( - - ) : ( - - )} -
-
- {importResult.ok ? t('settings_import_success') : t('settings_import_error')} -
- {importResult.module && ( -
- {t('settings_import_module')}: {importResult.module} - {importResult.projectName && ( - {t('settings_import_project')}: {importResult.projectName} - )} -
- )} - {importResult.appliedFields && importResult.appliedFields.length > 0 && ( -
- {t('settings_import_fields', { count: importResult.appliedFields.length })}: {importResult.appliedFields.join(', ')} -
- )} - {importResult.warnings && importResult.warnings.length > 0 && ( -
- {t('settings_import_warnings')}: {importResult.warnings.join('; ')} -
- )} - {importResult.error && ( -
{importResult.error}
- )} -
-
- )} - - {loading &&

{t('common_loading')}

} - {error &&

{error}

} - {projects.length === 0 && !loading && ( -

{t('settings_no_projects')}

- )} -
- {projects.map((p) => ( -
-
-
{p.name}
-
- {p.module} · {new Date(p.updatedAt).toLocaleString(locale === 'pt-BR' ? 'pt-BR' : 'en-US')} -
-
- -
- ))} -
-
-
- - {t('settings_about')} @@ -210,10 +167,84 @@ const SettingsModule: React.FC = () => { + + + + Arquivos (Gerenciador) + Gerencie seus Projetos Completos e Templates de geometria salvos localmente. + + +
+

+ {projects.length} arquivo(s) armazenado(s). +

+
+ + +
+
+ + {loading &&

{t('common_loading')}

} + {error &&

{error}

} + {projects.length === 0 && !loading && ( +

Nenhum projeto ou template salvo ainda.

+ )} +
+ {projects.map((p) => ( +
+
+ {p.type === 'projeto' ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ {p.name} + + {p.module} + +
+
+ {p.type === 'projeto' ? 'Projeto (Vento + Geometria)' : 'Template (Apenas Geometria)'} · {new Date(p.updatedAt).toLocaleString(locale === 'pt-BR' ? 'pt-BR' : 'en-US')} +
+
+
+
+ + + +
+
+ ))} +
+
+
+
+ + + + +
+
); }; diff --git a/app/src/store/hydrationStore.ts b/app/src/store/hydrationStore.ts new file mode 100644 index 0000000..4d3e81c --- /dev/null +++ b/app/src/store/hydrationStore.ts @@ -0,0 +1,16 @@ +import { create } from 'zustand'; + +export interface HydrationState { + pendingLoad: { + module: string; + inputs: Record; + } | null; + setPendingLoad: (module: string, inputs: Record) => void; + clearPendingLoad: () => void; +} + +export const useHydrationStore = create((set) => ({ + pendingLoad: null, + setPendingLoad: (module, inputs) => set({ pendingLoad: { module, inputs } }), + clearPendingLoad: () => set({ pendingLoad: null }), +}));