Compare commits

..

14 Commits

Author SHA1 Message Date
avanzato ec3e439973 Release PROD: Revisão Logo, Favicon, Robots
Deploy PROD Avanzato / deploy-prod (push) Successful in 33s
2026-05-31 00:29:03 -03:00
avanzato bfb022866b Merge remote-tracking branch 'origin/develop' 2026-05-31 00:29:00 -03:00
avanzato 8c056b0c50 Release PROD: Revisão Logo, Favicon, Robots
Deploy PROD Avanzato / deploy-prod (push) Successful in 33s
2026-05-31 00:03:02 -03:00
avanzato b284c4d258 Merge remote-tracking branch 'origin/develop' 2026-05-31 00:02:57 -03:00
avanzato d15474365c Release PROD: Favivon - Correção
Deploy PROD Avanzato / deploy-prod (push) Successful in 33s
2026-05-30 23:15:12 -03:00
avanzato e9f47c5b1e Merge remote-tracking branch 'origin/develop' 2026-05-30 23:15:09 -03:00
avanzato af8ef2693d Release PROD: Favivon - Correção
Deploy PROD Avanzato / deploy-prod (push) Successful in 34s
2026-05-30 20:36:09 -03:00
avanzato 5b729b23be Merge remote-tracking branch 'origin/develop' 2026-05-30 20:35:32 -03:00
avanzato 41312945d9 Release PROD: Atualização e correção de diretorios
Deploy PROD Avanzato / deploy-prod (push) Successful in 30s
2026-05-30 19:30:29 -03:00
avanzato bb73783e9f Merge remote-tracking branch 'origin/develop' 2026-05-30 19:30:26 -03:00
avanzato 57c4abf055 Merge branch 'develop'
Deploy PROD Avanzato / deploy-prod (push) Successful in 50s
2026-05-29 01:15:14 -03:00
avanzato f493c55040 Merge branch 'develop'
Deploy PROD Avanzato / deploy-prod (push) Successful in 31s
2026-05-27 22:45:17 -03:00
avanzato 0a0d2382d1 Merge branch 'develop'
Deploy PROD Avanzato / deploy-prod (push) Successful in 54s
2026-05-27 16:17:46 -03:00
avanzato 47fdcab3b4 Adiciona deploy automático produção
Deploy PROD Avanzato / deploy-prod (push) Successful in 58s
2026-05-26 22:07:42 -03:00
21371 changed files with 2385327 additions and 1286052 deletions
Vendored
BIN
View File
Binary file not shown.
+37
View File
@@ -0,0 +1,37 @@
name: Deploy PROD Avanzato
on:
push:
branches:
- main
jobs:
deploy-prod:
runs-on: linux_amd64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Instalar dependências
run: npm install
- name: Build projeto
run: npm run build
- name: Compactar dist
run: tar -czf dist.tar.gz dist
- name: Enviar para servidor PROD
run: scp -o StrictHostKeyChecking=no -i /data/avz_hml_deploy dist.tar.gz root@191.252.100.174:/tmp/avz-site-prod-dist.tar.gz
- name: Publicar PROD
run: |
ssh -o StrictHostKeyChecking=no -i /data/avz_hml_deploy root@191.252.100.174 '
rm -rf /opt/avanzato/site/app/dist_old
mv /opt/avanzato/site/app/dist /opt/avanzato/site/app/dist_old
mkdir -p /opt/avanzato/site/app/dist
tar -xzf /tmp/avz-site-prod-dist.tar.gz -C /opt/avanzato/site/app
rm -f /tmp/avz-site-prod-dist.tar.gz
docker restart avanzato-site
'
+135
View File
@@ -0,0 +1,135 @@
# CI/CD - Avanzato Tecnologia
## Estrutura do Pipeline
```
Push para main/master
|
v
+------------------+ +------------------+ +------------------+
| BUILD | --> | PRESERVAR ASSETS | --> | DEPLOY |
| | | | | |
| - Checkout | | - Logo | | - SCP para VPS |
| - npm install | | - Imagens | | - Limpar cache |
| - npm run build | | - Favicon | | - Notificar |
+------------------+ +------------------+ +------------------+
```
---
## Configurar Secrets no GitHub
Va em: **Settings > Secrets and variables > Actions > New repository secret**
| Secret | Descricao | Exemplo |
|--------|-----------|---------|
| `SSH_HOST` | IP ou dominio do servidor | `201.45.120.30` ou `avanzato.com.br` |
| `SSH_USER` | Usuario SSH | `root` ou `deploy` |
| `SSH_PRIVATE_KEY` | Chave SSH privada | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
| `SSH_PORT` | Porta SSH (opcional) | `22` |
| `DEPLOY_PATH` | Caminho no servidor | `/var/www/avanzato` |
| `STAGING_PATH` | Caminho staging (opcional) | `/var/www/avanzato-staging` |
### Como gerar a chave SSH:
```bash
# No seu computador (NAO no servidor):
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_actions
# Copiar publica para o servidor:
ssh-copy-id -i ~/.ssh/github_actions.pub root@SEU_SERVIDOR
# Copiar privada para o GitHub Secret:
cat ~/.ssh/github_actions
# Cole o conteudo inteiro no secret SSH_PRIVATE_KEY
```
---
## Estrutura de Branches
```
main/master --> Deploy automatico para producao
staging --> Deploy automatico para staging (se existir)
feature/* --> Apenas build (sem deploy)
hotfix/* --> Apenas build (sem deploy)
```
---
## Como Funciona
### Cenario 1: Deploy Automatico
```
1. Voce faz push para main
2. GitHub Actions executa automaticamente
3. Build + Deploy em ~2 minutos
4. Site atualizado!
```
### Cenario 2: Deploy Manual
```
1. Va em Actions > Build e Deploy
2. Clique "Run workflow"
3. Escolha: production ou staging
4. Clique "Run"
```
### Cenario 3: Pull Request
```
1. Cria PR para main
2. CI executa build (testa se compila)
3. NAO faz deploy
4. Merge aprovado? Deploy automatico!
```
---
## Assets Customizados (Logo, Imagens)
Para preservar assets apos o deploy automatico:
### 1. Commitar assets no repositorio
```bash
git add custom-assets/logo/logo.png
git add custom-assets/images/
git commit -m "Adiciona logo e imagens customizadas"
git push origin main
```
### 2. O pipeline faz sozinho
O workflow executa automaticamente:
```bash
bash scripts/preserve-assets.sh dist/
```
---
## Monitore os Deploys
- **GitHub Actions**: https://github.com/SEU_USUARIO/avanzato-site/actions
- **Status**: Check verde = sucesso, X vermelho = falha
- **Logs**: Clique no workflow para ver detalhes
---
## Troubleshooting
### Deploy falhou?
1. **Verifique os secrets**: Settings > Secrets > Actions
2. **Verifique a chave SSH**: `ssh -i ~/.ssh/github_actions root@SEU_SERVIDOR`
3. **Verifique permissoes**: O usuario precisa de acesso de escrita no DEPLOY_PATH
4. **Verifique logs**: Actions > Clique no workflow > Veja o passo que falhou
### Assets nao preservados?
1. Verifique se `custom-assets/` esta no repositorio
2. Verifique se os arquivos estao comitados: `git ls-files custom-assets/`
3. Verifique os logs do passo "Preservar assets customizados"
---
*Configurado em: Maio 2026*
+162
View File
@@ -0,0 +1,162 @@
# ============================================================================
# CI/CD Pipeline - Avanzato Tecnologia
# ============================================================================
# Este workflow automatiza o build e deploy do site.
#
# TRIGGER: Push na branch main ou manual (workflow_dispatch)
# ============================================================================
name: Build e Deploy
on:
# Deploy automatico ao fazer push na main
push:
branches: [ main, master ]
# Deploy manual pelo botao "Run workflow" no GitHub
workflow_dispatch:
inputs:
environment:
description: 'Ambiente'
required: true
default: 'production'
type: choice
options:
- production
- staging
# Pull Request (apenas build, sem deploy)
pull_request:
branches: [ main, master ]
# Permissoes necessarias
permissions:
contents: read
jobs:
# ==========================================================================
# JOB 1: Build
# ==========================================================================
build:
name: Build
runs-on: ubuntu-latest
steps:
# Passo 1: Checkout do codigo
- name: Checkout codigo
uses: actions/checkout@v4
# Passo 2: Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Passo 3: Instalar dependencias
- name: Instalar dependencias
run: npm ci
# Passo 4: Build
- name: Build
run: npm run build
# Passo 5: Upload do artefato (para usar no job de deploy)
- name: Upload artefato
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 1
# ==========================================================================
# JOB 2: Deploy para Servidor (Producao)
# ==========================================================================
deploy-production:
name: Deploy - Producao
needs: build
runs-on: ubuntu-latest
# So executa na branch main/master ou manual com environment=production
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event.inputs.environment == 'production'
steps:
# Passo 1: Download do artefato buildado
- name: Download artefato
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
# Passo 2: Preservar assets customizados (logo, imagens)
- name: Preservar assets customizados
run: |
if [ -d "custom-assets" ]; then
echo "Preservando assets customizados..."
bash scripts/preserve-assets.sh dist/
else
echo "Nenhum asset customizado encontrado."
fi
# Passo 3: Deploy via SCP (SSH)
- name: Deploy para servidor
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || '22' }}
source: "dist/*"
target: ${{ secrets.DEPLOY_PATH || '/var/www/avanzato' }}
strip_components: 1
rm: true # Remove arquivos antigos antes de copiar
# Passo 4: Notificar (opcional - webhook Discord/Slack)
- name: Notificar sucesso
if: success()
run: |
echo "Deploy para producao concluido com sucesso!"
echo "Site: https://avanzato.com.br"
# ==========================================================================
# JOB 3: Deploy para Staging (Opcional)
# ==========================================================================
deploy-staging:
name: Deploy - Staging
needs: build
runs-on: ubuntu-latest
# So executa em push para branch staging ou manual
if: github.ref == 'refs/heads/staging' || github.event.inputs.environment == 'staging'
steps:
- name: Download artefato
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy para staging
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || '22' }}
source: "dist/*"
target: ${{ secrets.STAGING_PATH || '/var/www/avanzato-staging' }}
strip_components: 1
rm: true
# ==========================================================================
# JOB 4: Notificacao de Falha
# ==========================================================================
notify-failure:
name: Notificar Falha
needs: [build, deploy-production]
runs-on: ubuntu-latest
if: failure()
steps:
- name: Notificar falha
run: |
echo "ALERTA: O deploy falhou!"
echo "Verifique os logs em: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+42
View File
@@ -0,0 +1,42 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Build
dist
dist-ssr
*.local
# Cache
.cache
.vite
# Dependencias
node_modules
# Ambiente (NUNCA commite!)
.env
.env.local
.env.*.local
# Editor
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS
.DS_Store
Thumbs.db
# Scripts de deploy local
scripts/.env.local
+232
View File
@@ -0,0 +1,232 @@
# Como Usar a Configuracao Central - Avanzato
## O que mudou?
Agora **tudo** (logo, redes sociais, contatos, enderecos, menu, etc.) esta em um unico arquivo:
```
src/config/site.ts
```
**Nao precisa mais editar Navigation.tsx, Footer.tsx ou qualquer componente!**
---
## EDITAR APENAS ESTE ARQUIVO
### src/config/site.ts
### 1. Trocar o Logo
**Opcao A - SVG inline:**
```typescript
export const LOGO = {
svgInline: `<svg width="40" height="40">...COLE O SVG AQUI...</svg>`,
imageUrl: '', // Deixe vazio
text: 'avanzato',
textHighlight: '.',
};
```
**Opcao B - Imagem (PNG/SVG):**
```typescript
export const LOGO = {
svgInline: '', // Deixe vazio
imageUrl: '/assets/logo.png', // Ou .svg
text: '', // Deixe vazio se nao quiser texto
textHighlight: '',
};
```
### 2. Trocar Redes Sociais
```typescript
export const SOCIAL_LINKS = {
facebook: {
url: 'https://www.facebook.com/avanzatotec',
label: 'Facebook',
},
youtube: {
url: '', // Deixe vazio para OCULTAR
label: 'YouTube',
},
linkedin: {
url: 'https://www.linkedin.com/...',
label: 'LinkedIn',
},
instagram: {
url: 'https://www.instagram.com/...',
label: 'Instagram',
},
};
```
Para **adicionar uma rede nova**, adicione uma linha:
```typescript
tiktok: {
url: 'https://tiktok.com/...',
label: 'TikTok',
iconSvg: '', // Opcional: SVG customizado
},
```
### 3. Trocar Contatos
```typescript
export const CONTACT = {
phone: '(11) 4810-1704',
phoneRaw: '551148101704',
email: 'leads@avanzato.com.br',
supportUrl: 'https://suporte.avanzato.com.br/',
};
```
### 4. Trocar Enderecos
```typescript
export const ADDRESSES = [
{
label: 'Escritorio Principal',
street: 'Rua Juiz de Fora, 76',
city: 'Guarulhos',
state: 'SP',
cep: '',
},
// Adicione mais ou remova
];
```
### 5. Trocar Menu de Navegacao
```typescript
export const NAV_LINKS = [
{ name: 'Home', href: '/' },
{ name: 'Sobre', href: '/sobre' },
{ name: 'Servicos', href: '#', submenu: [...] },
];
```
### 6. Desabilitar WhatsApp Widget
```typescript
export const WHATSAPP_CONFIG = {
enabled: false, // Mude para false para ocultar
// ...
};
```
---
## APOS EDITAR
Sempre que editar `site.ts`, faca:
```bash
npm run build
```
O build vai usar os novos valores automaticamente.
---
## SISTEMA DE PRESERVACAO DE ASSETS
Para imagens que NAO devem ser sobrescritas no deploy:
### 1. Crie a pasta de assets customizados
```bash
mkdir -p custom-assets/logo
mkdir -p custom-assets/images
```
### 2. Coloque seus arquivos
```
custom-assets/
logo/
logo.png <- Seu logo (substitui o do build)
favicon.ico <- Favicon customizado
images/
hero-bg.jpg <- Imagem do hero
about.jpg <- Imagem da secao sobre
```
### 3. Execute o script apos o deploy
```bash
bash scripts/preserve-assets.sh /caminho/do/site/no/servidor
```
Ou se o site esta em `/var/www/avanzato`:
```bash
bash scripts/preserve-assets.sh /var/www/avanzato/dist
```
### O que o script faz?
- Copia `logo/logo.*` para a pasta do site
- Copia `images/*` para a pasta do site
- Copia `fonts/*` se houver
- **Nao altera** nenhum outro arquivo do build
---
## FLUXO DE TRABALHO RECOMENDADO
### Quando editar config (textos, links, etc.):
1. Edite `src/config/site.ts`
2. Rode `npm run build`
3. Deploy
### Quando trocar apenas imagens/logo:
1. Coloque a nova imagem em `custom-assets/`
2. Rode `bash scripts/preserve-assets.sh /caminho/do/site`
3. **Nao precisa rebuild!**
### Quando eu (dev) atualizar o site:
1. Eu faco as mudancas no codigo
2. Build + deploy
3. **Voce roda:** `bash scripts/preserve-assets.sh /caminho/do/site`
4. Seu logo e imagens sao restaurados automaticamente!
---
## EXEMPLO COMPLETO
### Cenario: Trocar apenas o logo
**Passo 1:** Coloque o novo logo em `custom-assets/logo/logo.png`
**Passo 2:** Execute:
```bash
bash scripts/preserve-assets.sh /var/www/avanzato/dist
```
**Resultado:**
```
[LOGO] Copiando logo.png...
Concluido! 1 arquivo(s) preservado(s).
```
Pronto! O logo foi trocado sem precisar rebuild.
---
## PERGUNTAS FREQUENTES
**Q: Preciso rebuild quando troco o logo via custom-assets?**
R: Nao! O script copia diretamente.
**Q: E se eu quiser trocar o texto do logo?**
R: Ai sim, edite `site.ts` e faca rebuild.
**Q: O script funciona em qualquer servidor?**
R: Sim! Basta passar o caminho correto como argumento.
**Q: Posso adicionar novas redes sociais?**
R: Sim! Basta adicionar em `SOCIAL_LINKS` no `site.ts`.
---
*Documento criado em: Maio 2026*
+96
View File
@@ -0,0 +1,96 @@
# Deploy com Docker - Avanzato
## O Problema
O Nginx no Docker nao vem configurado para SPA React. Ao acessar `/avaliacao` diretamente, da 404.
## A Solucao
### Opcao 1: Configurar Nginx dentro do container (RECOMENDADO)
**1. Criar um novo Dockerfile:**
```dockerfile
FROM nginx:alpine
# Copiar o site
COPY dist/ /usr/share/nginx/html/
# Copiar a configuracao customizada
COPY nginx-docker.conf /etc/nginx/conf.d/default.conf
# Expor porta 80
EXPOSE 80
# Iniciar Nginx
CMD ["nginx", "-g", "daemon off;"]
```
**2. Build e run:**
```bash
# Build
npm run build
bash scripts/build-static-routes.sh dist
docker build -t avanzato-site .
# Run
docker run -d -p 80:80 --name avanzato avanzato-site
```
### Opcao 2: Usar docker-compose
**docker-compose.yml:**
```yaml
version: '3.8'
services:
avanzato:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html:ro
- ./nginx-docker.conf:/etc/nginx/conf.d/default.conf:ro
container_name: avanzato-site
```
**Comandos:**
```bash
npm run build
bash scripts/build-static-routes.sh dist
docker-compose up -d
```
### Opcao 3: Container ja rodando (hotfix)
Se o container ja esta rodando:
```bash
# Copiar config para dentro do container
docker cp nginx-docker.conf avanzato-site:/etc/nginx/conf.d/default.conf
# Recarregar Nginx
docker exec avanzato-site nginx -s reload
```
## Verificar se funcionou
```bash
# Testar
curl -I http://localhost/avaliacao
# Deve retornar HTTP 200, nao 404
```
## A regra magica
A unica coisa que importa e esta linha no Nginx:
```nginx
location / {
try_files $uri $uri/ /index.html;
}
```
Isso diz ao Nginx: "se nao encontrar o arquivo, manda para index.html" — onde o React pega a rota.
+250
View File
@@ -0,0 +1,250 @@
# Fluxo de Desenvolvimento - Avanzato
## Visao Geral
```
+---------------------------------------------------------------+
| SEU MAC (Desenvolvimento) |
| |
| +-------------------+ bash scripts/dev.sh start |
| | Codigo Fonte | --> http://localhost:5173 |
| | app/src/ | |
| +-------------------+ |
| | |
| | bash scripts/dev.sh build-hom |
| | bash scripts/dev.sh deploy-hom |
| v |
+---------------------------------------------------------------+
|
v
+---------------------------------------------------------------+
| SERVIDOR - HOMOLOGACAO |
| |
| Gitea (git push) --> Hook --> Build --> Nginx |
| |
| https://homologacao.avanzato.com.br |
| - Mautic: desativado |
| - Google Ads: desativado |
| - Facebook Pixel: desativado |
| - noindex/nofollow (nao aparece no Google) |
| |
+---------------------------------------------------------------+
|
| Aprovado? bash scripts/dev.sh deploy-prod
v
+---------------------------------------------------------------+
| SERVIDOR - PRODUCAO |
| |
| https://avanzato.com.br |
| - Mautic: ativo (forms, tracking) |
| - Google Ads: ativo |
| - Facebook Pixel: ativo |
| - Indexado no Google |
| |
+---------------------------------------------------------------+
```
---
## 1. Primeira Configuracao (faca uma vez)
### 1.1 Instalar Node.js 20+
```bash
# Instalar via Homebrew
brew install node@20
# Verificar
node -v # v20.x.x
npm -v # 10.x.x
```
### 1.2 Instalar dependencias
```bash
cd /Users/avanzato/Projetos/Avanzato/avanzato-site/app
npm install
```
### 1.3 Configurar acesso aos servidores
```bash
# Copiar template
cp scripts/.env.local.example scripts/.env.local
# Editar com seus dados
nano scripts/.env.local
```
Conteudo do `.env.local`:
```bash
# Homologacao
SSH_HOM_HOST=192.168.1.100 # ou IP publico
SSH_HOM_USER=deploy
SSH_HOM_PATH=/var/www/avanzato-hom
# Producao
SSH_PROD_HOST=201.45.120.30 # ou IP publico
SSH_PROD_USER=deploy
SSH_PROD_PATH=/var/www/avanzato
```
### 1.4 Configurar chave SSH (se ainda nao tiver)
```bash
# Gerar chave
ssh-keygen -t ed25519 -C "dev@avanzato" -f ~/.ssh/id_ed25519
# Copiar para o servidor de homologacao
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SEU_SERVIDOR_HOM
# Copiar para o servidor de producao
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SEU_SERVIDOR_PROD
```
---
## 2. Desenvolvimento Diario
### 2.1 Iniciar servidor local
```bash
bash scripts/dev.sh start
# ou
npm run dev
```
Acesse: **http://localhost:5173**
- Hot reload funciona automaticamente
- Mautic/Google Ads/Facebook Pixel estao em modo simulacao
- Edite qualquer arquivo `.tsx` e veja a mudanca ao vivo
### 2.2 Comandos uteis durante desenvolvimento
```bash
# Ver ajuda completa
bash scripts/dev.sh help
# Build para homologacao
bash scripts/dev.sh build-hom
# Preview do build (para testar antes de deploy)
bash scripts/dev.sh preview
# Limpar caches quando algo estranho acontecer
bash scripts/dev.sh clean
```
---
## 3. Deploy para Homologacao
### Metodo 1: Script local (rapido)
```bash
# 1. Build
bash scripts/dev.sh build-hom
# 2. Deploy
bash scripts/dev.sh deploy-hom
```
### Metodo 2: Git push (CI/CD)
```bash
# Adicionar remote do Gitea (faca uma vez)
git remote add homolog ssh://git@SEU_SERVIDOR_HOM:22/gitea/avanzato-site.git
# Sempre que quiser deployar
git add .
git commit -m "descricao da mudanca"
git push homolog main
```
O Gitea vai executar o pipeline automaticamente e deployar.
---
## 4. Deploy para Producao
**ATENCAO: Sempre teste em homologacao primeiro!**
```bash
# 1. Build de producao
bash scripts/dev.sh build
# 2. Deploy (pede confirmacao)
bash scripts/dev.sh deploy-prod
# Digite 'sim' para confirmar
```
---
## 5. Estrutura de Comandos
| Comando | O que faz | URL |
|---------|-----------|-----|
| `bash scripts/dev.sh start` | Servidor dev local | http://localhost:5173 |
| `bash scripts/dev.sh build` | Build producao | - |
| `bash scripts/dev.sh build-hom` | Build homologacao | - |
| `bash scripts/dev.sh preview` | Preview do build | http://localhost:4173 |
| `bash scripts/dev.sh deploy-hom` | Deploy homologacao | https://homologacao.avanzato.com.br |
| `bash scripts/dev.sh deploy-prod` | Deploy producao | https://avanzato.com.br |
| `bash scripts/dev.sh clean` | Limpa caches | - |
| `bash scripts/dev.sh help` | Ajuda | - |
---
## 6. Checklist antes de cada deploy
### Homologacao
- [ ] Testei localmente (`npm run dev`)
- [ ] Build funciona (`bash scripts/dev.sh build-hom`)
- [ ] Nao ha erros no console do navegador
### Producao
- [ ] Testei em homologacao
- [ ] Aprovado pelo time
- [ ] Build de producao (`bash scripts/dev.sh build`)
- [ ] Backup esta funcionando no servidor
---
## 7. Solucao de Problemas
### Erro: "Cannot find module"
```bash
npm install
```
### Erro: "dist/ nao encontrado"
```bash
bash scripts/dev.sh build
```
### Erro: "Permission denied" no deploy
```bash
# Verificar SSH
ssh deploy@SEU_SERVIDOR "echo OK"
# Se nao funcionar, recopiar chave
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SEU_SERVIDOR
```
### Site nao carrega depois do deploy
```bash
# Verificar no servidor
ssh deploy@SEU_SERVIDOR "ls -la /var/www/avanzato"
ssh deploy@SEU_SERVIDOR "sudo systemctl status nginx"
```
---
## 8. Dicas
1. **Nunca edite diretamente no servidor** - Sempre edite local e deploy
2. **Sempre teste em homolog primeiro** - Antes de ir pra prod
3. **Commits frequentes** - Facilita rollback se algo der errado
4. **Mantenha o .env.local seguro** - Nunca commite este arquivo
+116
View File
@@ -0,0 +1,116 @@
# Guia Rapido - Configurar Nginx (SPA React)
## O Problema
URLs como `avanzato.com.br/avaliacao` dao 404 quando acessadas diretamente.
Funcionam apenas quando clica no menu (navegacao interna do React).
## A Solucao
O Nginx precisa redirecionar todas as rotas para `index.html`.
## Passo a Passo
### 1. Copiar a config do projeto para o servidor
No seu Mac, envie o arquivo `nginx.conf` do projeto para o servidor:
```bash
# Enviar o arquivo nginx.conf do projeto para o servidor
scp /Users/avanzato/Projetos/Avanzato/avanzato-site/app/nginx.conf root@SEU_SERVIDOR:/etc/nginx/sites-available/avanzato
```
Ou, se ja estiver logado no servidor:
```bash
# Logar no servidor
ssh root@SEU_SERVIDOR
# Criar o arquivo de config
cat > /etc/nginx/sites-available/avanzato << 'EOF'
server {
listen 80;
server_name avanzato.com.br;
root /var/www/avanzato;
index index.html;
# ESSA LINHA E A CHAVE - redireciona tudo para index.html
location / {
try_files $uri $uri/ /index.html;
}
# Cache para assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 6M;
add_header Cache-Control "public";
}
# Seguranca
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Bloquear arquivos ocultos
location ~ /\. {
deny all;
}
}
EOF
```
### 2. Ativar o site
```bash
# Criar link simbolico (ativa o site)
ln -s /etc/nginx/sites-available/avanzato /etc/nginx/sites-enabled/avanzato
# Remover o default (se existir)
rm -f /etc/nginx/sites-enabled/default
```
### 3. Testar e aplicar
```bash
# Testar a configuracao (mostra erros se houver)
nginx -t
# Se der OK, recarregar o Nginx
systemctl reload nginx
```
### 4. Verificar se funcionou
```bash
# Testar uma rota direta
curl -I https://avanzato.com.br/avaliacao
# Deve retornar HTTP 200, nao 404
```
---
## Comandos Uteis
| Comando | O que faz |
|---------|-----------|
| `nginx -t` | Testa a configuracao (sem aplicar) |
| `systemctl reload nginx` | Recarrega config sem derrubar o site |
| `systemctl restart nginx` | Reinicia o Nginx |
| `systemctl status nginx` | Verifica se esta rodando |
| `tail -f /var/log/nginx/error.log` | Ver erros em tempo real |
---
## Importante
Se voce usa **Apache** em vez de Nginx, a config esta no arquivo `.htaccess` que ja vai no `dist/`:
```apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.html [L]
```
Basta garantir que o `.htaccess` esta na raiz do site e o `mod_rewrite` esta ativado.
+244
View File
@@ -0,0 +1,244 @@
# 📁 Guia do .htaccess - Avanzato Tecnologia
## O que é o .htaccess?
O `.htaccess` é um arquivo de configuração do Apache (servidor web) que controla:
- ✅ Redirecionamentos
- ✅ Compressão de arquivos
- ✅ Cache do navegador
- ✅ Segurança
- ✅ Performance
---
## 📂 Onde colocar o arquivo?
```
/public_html/ ← Raiz do seu site
├── .htaccess ← COLOQUE AQUI
├── index.html
├── assets/
└── ...
```
**IMPORTANTE:** O arquivo deve estar na **mesma pasta** do `index.html`
---
## 📋 Arquivos disponíveis
| Arquivo | Quando usar |
|---------|-------------|
| `.htaccess` | Versão **completa** (recomendada) |
| `.htaccess-simples` | Se o servidor der erro 500 |
---
## 🚀 Como instalar
### Método 1: FTP/cPanel
1. Acesse seu servidor via FTP ou cPanel
2. Vá para a pasta `public_html/` (ou `www/`)
3. Faça upload do arquivo `.htaccess`
4. Pronto!
### Método 2: SSH
```bash
# Conecte ao servidor
ssh usuario@seudominio.com.br
# Vá para a pasta do site
cd /var/www/html
# ou
cd /home/usuario/public_html
# Crie o arquivo
nano .htaccess
# Cole o conteúdo do .htaccess
# Salve: Ctrl+O, Enter, Ctrl+X
```
---
## ⚙️ Configurações incluídas
### 1. **SPA (Single Page Application)**
```apache
RewriteRule ^ index.html [L]
```
→ Todas as URLs vão para `index.html` (necessário para React)
### 2. **Compressão GZIP**
```apache
AddOutputFilterByType DEFLATE text/css application/javascript
```
→ Reduz tamanho dos arquivos em ~70%
### 3. **Cache do Navegador**
```apache
ExpiresByType application/javascript "access plus 1 year"
```
→ Arquivos JS/CSS ficam no cache por 1 ano
### 4. **Segurança**
- Proteção contra clickjacking
- Proteção contra MIME sniffing
- Bloqueio de arquivos ocultos (.git, .env)
---
## 🔧 Configurações opcionais
### Ativar HTTPS (quando tiver SSL)
Descomente estas linhas no `.htaccess`:
```apache
# Forçar HTTPS
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
# HSTS (segurança extra)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
```
### Forçar SEM www
```apache
<IfModule mod_rewrite.c>
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
</IfModule>
```
### Forçar COM www
```apache
<IfModule mod_rewrite.c>
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
```
---
## ❌ Problemas comuns
### Erro 500 (Internal Server Error)
**Causa:** Seu servidor não tem todos os módulos habilitados
**Solução:** Use o `.htaccess-simples`
```bash
# Renomeie o arquivo
mv .htaccess .htaccess-backup
mv .htaccess-simples .htaccess
```
### Página em branco
**Causa:** O mod_rewrite pode estar desativado
**Solução:** Contate seu provedor de hospedagem para ativar:
- `mod_rewrite`
- `mod_deflate`
- `mod_expires`
- `mod_headers`
### Rotas não funcionam (404 em /sobre, /contato)
**Causa:** O redirecionamento para index.html não está funcionando
**Solução:** Verifique se o arquivo .htaccess está na pasta correta
---
## 📊 Testar se está funcionando
### 1. Testar compressão GZIP
```bash
curl -H "Accept-Encoding: gzip" -I https://seudominio.com.br
```
Deve mostrar: `Content-Encoding: gzip`
### 2. Testar headers de segurança
```bash
curl -I https://seudominio.com.br
```
Deve mostrar:
- `X-Frame-Options: SAMEORIGIN`
- `X-Content-Type-Options: nosniff`
### 3. Testar cache
```bash
curl -I https://seudominio.com.br/assets/index-xxx.js
```
Deve mostrar: `Cache-Control: max-age=31536000`
---
## 🌐 Servidores específicos
### cPanel (Hostgator, Locaweb, etc)
1. Acesse: **Arquivos****Gerenciador de Arquivos**
2. Vá para `public_html/`
3. Clique em **Upload** e envie o `.htaccess`
### AWS S3 + CloudFront
O S3 não usa .htaccess. Configure:
- **Error Document:** `index.html`
- CloudFront: **Custom Error Response** 404 → 200 para `index.html`
### Vercel/Netlify
Não precisa de .htaccess. Use `vercel.json` ou `_redirects`:
```
/* /index.html 200
```
### Nginx (servidor diferente)
Se usar Nginx, crie um arquivo de configuração:
```nginx
location / {
try_files $uri $uri/ /index.html;
}
gzip on;
gzip_types text/css application/javascript;
```
---
## 📞 Suporte
Se tiver problemas, verifique:
1. O arquivo está na pasta correta?
2. O nome do arquivo é exatamente `.htaccess` (com ponto)?
3. As permissões do arquivo são 644?
```bash
# Corrigir permissões
chmod 644 .htaccess
```
---
## ✅ Checklist de instalação
- [ ] Arquivo `.htaccess` na pasta `public_html/`
- [ ] Site carrega normalmente (sem erro 500)
- [ ] Navegação entre páginas funciona
- [ ] Atualizar página (F5) não dá erro 404
- [ ] GZIP está ativo (teste com GTmetrix)
- [ ] Cache está funcionando
---
**Última atualização:** 2024
**Versão:** 1.0
+73
View File
@@ -0,0 +1,73 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
-22
View File
@@ -1,22 +0,0 @@
import * as cookie from "cookie";
import { Session } from "@contracts/constants";
import { getSessionCookieOptions } from "./lib/cookies";
import { createRouter, authedQuery } from "./middleware";
export const authRouter = createRouter({
me: authedQuery.query((opts) => opts.ctx.user),
logout: authedQuery.mutation(async ({ ctx }) => {
const opts = getSessionCookieOptions(ctx.req.headers);
ctx.resHeaders.append(
"set-cookie",
cookie.serialize(Session.cookieName, "", {
httpOnly: opts.httpOnly,
path: opts.path,
sameSite: opts.sameSite?.toLowerCase() as "lax" | "none",
secure: opts.secure,
maxAge: 0,
}),
);
return { success: true };
}),
});
-36
View File
@@ -1,36 +0,0 @@
import { Hono } from "hono";
import { bodyLimit } from "hono/body-limit";
import type { HttpBindings } from "@hono/node-server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "./router";
import { createContext } from "./context";
import { env } from "./lib/env";
import { createOAuthCallbackHandler } from "./kimi/auth";
import { Paths } from "@contracts/constants";
const app = new Hono<{ Bindings: HttpBindings }>();
app.use(bodyLimit({ maxSize: 50 * 1024 * 1024 }));
app.get(Paths.oauthCallback, createOAuthCallbackHandler());
app.use("/api/trpc/*", async (c) => {
return fetchRequestHandler({
endpoint: "/api/trpc",
req: c.req.raw,
router: appRouter,
createContext,
});
});
app.all("/api/*", (c) => c.json({ error: "Not Found" }, 404));
export default app;
if (env.isProduction) {
const { serve } = await import("@hono/node-server");
const { serveStaticFiles } = await import("./lib/vite");
serveStaticFiles(app);
const port = parseInt(process.env.PORT || "3000");
serve({ fetch: app.fetch, port }, () => {
console.log(`Server running on http://localhost:${port}/`);
});
}
-21
View File
@@ -1,21 +0,0 @@
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
import type { User } from "@db/schema";
import { authenticateRequest } from "./kimi/auth";
export type TrpcContext = {
req: Request;
resHeaders: Headers;
user?: User;
};
export async function createContext(
opts: FetchCreateContextFnOptions,
): Promise<TrpcContext> {
const ctx: TrpcContext = { req: opts.req, resHeaders: opts.resHeaders };
try {
ctx.user = await authenticateRequest(opts.req.headers);
} catch {
// Authentication is optional here
}
return ctx;
}
-211
View File
@@ -1,211 +0,0 @@
// ============================================================================
// FORM ROUTER - Area do Cliente Avanzato
// ============================================================================
// Routers para gestao de formularios e respostas
// ============================================================================
import { z } from "zod";
import { createRouter, publicQuery, authedQuery } from "./middleware";
import { getDb } from "./queries/connection";
import { clientForms, formSections, formQuestions, formAnswers, formSubmissions } from "../db/schema";
import { eq, desc, and } from "drizzle-orm";
// Gerar codigo aleatorio de 8 caracteres
function generateCode(): string {
return Math.random().toString(36).substring(2, 10).toUpperCase();
}
export const formRouter = createRouter({
// ==========================================================================
// ADMIN - Gerenciamento de Formularios
// ==========================================================================
// Listar todos os formularios
list: publicQuery.query(async () => {
const db = getDb();
const forms = await db.select().from(clientForms).orderBy(desc(clientForms.createdAt));
return forms;
}),
// Criar novo formulario
create: publicQuery
.input(
z.object({
title: z.string().min(1),
description: z.string().optional(),
clientName: z.string().min(1),
clientEmail: z.string().email().optional(),
})
)
.mutation(async ({ input }) => {
const db = getDb();
const code = generateCode();
await db.insert(clientForms).values({
title: input.title,
description: input.description || "",
clientName: input.clientName,
clientEmail: input.clientEmail || "",
status: "active",
accessCode: code,
createdBy: "Avanzato",
});
return { code, message: "Formulario criado com sucesso" };
}),
// Atualizar status (active/inactive/archived)
updateStatus: publicQuery
.input(
z.object({
accessCode: z.string(),
status: z.enum(["active", "inactive", "archived"]),
})
)
.mutation(async ({ input }) => {
const db = getDb();
await db
.update(clientForms)
.set({ status: input.status })
.where(eq(clientForms.accessCode, input.accessCode));
return { message: "Status atualizado" };
}),
// Excluir formulario
delete: publicQuery
.input(z.object({ accessCode: z.string() }))
.mutation(async ({ input }) => {
const db = getDb();
// Primeiro excluir respostas e perguntas
await db.delete(formAnswers).where(eq(formAnswers.formId, input.accessCode));
await db.delete(formQuestions).where(eq(formQuestions.formId, input.accessCode));
await db.delete(formSections).where(eq(formSections.formId, input.accessCode));
await db.delete(formSubmissions).where(eq(formSubmissions.formId, input.accessCode));
await db.delete(clientForms).where(eq(clientForms.accessCode, input.accessCode));
return { message: "Formulario excluido" };
}),
// Renovar codigo de acesso
regenerateCode: publicQuery
.input(z.object({ accessCode: z.string() }))
.mutation(async ({ input }) => {
const db = getDb();
const newCode = generateCode();
await db
.update(clientForms)
.set({ accessCode: newCode })
.where(eq(clientForms.accessCode, input.accessCode));
return { newCode };
}),
// ==========================================================================
// PUBLICO - Acesso ao Formulario via Codigo
// ==========================================================================
// Buscar formulario por codigo (publico)
getByCode: publicQuery
.input(z.object({ code: z.string() }))
.query(async ({ input }) => {
const db = getDb();
const [form] = await db
.select()
.from(clientForms)
.where(eq(clientForms.accessCode, input.code));
if (!form) return null;
if (form.status !== "active") return { ...form, _blocked: true };
// Buscar perguntas agrupadas por secao
const questions = await db
.select()
.from(formQuestions)
.where(eq(formQuestions.formId, input.code))
.orderBy(formQuestions.id);
const sections = await db
.select()
.from(formSections)
.where(eq(formSections.formId, input.code))
.orderBy(formSections.id);
return { ...form, questions, sections };
}),
// Submeter respostas do formulario
submit: publicQuery
.input(
z.object({
formId: z.string(),
code: z.string(),
answers: z.array(
z.object({
questionId: z.string(),
answer: z.string(),
})
),
submittedBy: z.string().optional(),
submittedByEmail: z.string().optional(),
})
)
.mutation(async ({ input }) => {
const db = getDb();
// Verificar se formulario esta ativo
const [form] = await db
.select()
.from(clientForms)
.where(eq(clientForms.accessCode, input.code));
if (!form || form.status !== "active") {
throw new Error("Formulario nao encontrado ou inativo");
}
// Inserir submissao
await db.insert(formSubmissions).values({
formId: input.formId,
submittedBy: input.submittedBy || "Anonimo",
submittedByEmail: input.submittedByEmail || "",
});
// Inserir respostas
for (const a of input.answers) {
await db.insert(formAnswers).values({
formId: input.formId,
questionId: a.questionId,
answer: a.answer,
});
}
return { message: "Respostas submetidas com sucesso" };
}),
// ==========================================================================
// ADMIN - Visualizar Respostas
// ==========================================================================
// Listar submissoes de um formulario
getSubmissions: publicQuery
.input(z.object({ accessCode: z.string() }))
.query(async ({ input }) => {
const db = getDb();
const submissions = await db
.select()
.from(formSubmissions)
.where(eq(formSubmissions.formId, input.accessCode))
.orderBy(desc(formSubmissions.createdAt));
return submissions;
}),
// Obter todas as respostas de uma submissao
getAnswers: publicQuery
.input(z.object({ formId: z.string() }))
.query(async ({ input }) => {
const db = getDb();
const answers = await db
.select()
.from(formAnswers)
.where(eq(formAnswers.formId, input.formId))
.orderBy(formAnswers.id);
return answers;
}),
});
-130
View File
@@ -1,130 +0,0 @@
import type { Context } from "hono";
import { setCookie } from "hono/cookie";
import * as jose from "jose";
import * as cookie from "cookie";
import { env } from "../lib/env";
import { getSessionCookieOptions } from "../lib/cookies";
import { Session } from "@contracts/constants";
import { Errors } from "@contracts/errors";
import { signSessionToken, verifySessionToken } from "./session";
import { users as kimiUsers } from "./platform";
import { findUserByUnionId, upsertUser } from "../queries/users";
import type { TokenResponse } from "./types";
async function exchangeAuthCode(
code: string,
redirectUri: string,
): Promise<TokenResponse> {
const body = new URLSearchParams({
grant_type: "authorization_code",
code,
client_id: env.appId,
redirect_uri: redirectUri,
client_secret: env.appSecret,
});
const resp = await fetch(`${env.kimiAuthUrl}/api/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`Token exchange failed (${resp.status}): ${text}`);
}
return resp.json() as Promise<TokenResponse>;
}
const jwks = jose.createRemoteJWKSet(
new URL(`${env.kimiAuthUrl}/api/.well-known/jwks.json`),
);
async function verifyAccessToken(
accessToken: string,
): Promise<{ userId: string; clientId: string }> {
const { payload } = await jose.jwtVerify(accessToken, jwks);
const userId = payload.user_id as string;
const clientId = payload.client_id as string;
if (!userId) {
throw new Error("user_id missing from access token");
}
return { userId, clientId };
}
export async function authenticateRequest(headers: Headers) {
const cookies = cookie.parse(headers.get("cookie") || "");
const token = cookies[Session.cookieName];
if (!token) {
console.warn("[auth] No session cookie found in request.");
throw Errors.forbidden("Invalid authentication token.");
}
const claim = await verifySessionToken(token);
if (!claim) {
throw Errors.forbidden("Invalid authentication token.");
}
const user = await findUserByUnionId(claim.unionId);
if (!user) {
throw Errors.forbidden("User not found. Please re-login.");
}
return user;
}
export function createOAuthCallbackHandler() {
return async (c: Context) => {
const code = c.req.query("code");
const state = c.req.query("state");
const error = c.req.query("error");
const errorDescription = c.req.query("error_description");
if (error) {
if (error === "access_denied") {
return c.redirect("/", 302);
}
return c.json(
{ error, error_description: errorDescription },
400,
);
}
if (!code || !state) {
return c.json({ error: "code and state are required" }, 400);
}
try {
const redirectUri = atob(state);
const tokenResp = await exchangeAuthCode(code, redirectUri);
const { userId } = await verifyAccessToken(tokenResp.access_token);
const userProfile = await kimiUsers.getProfile(tokenResp.access_token);
if (!userProfile) {
throw new Error("Failed to fetch user profile from Kimi Open");
}
await upsertUser({
unionId: userId,
name: userProfile.name,
avatar: userProfile.avatar_url,
lastSignInAt: new Date(),
});
const token = await signSessionToken({
unionId: userId,
clientId: env.appId,
});
const cookieOpts = getSessionCookieOptions(c.req.raw.headers);
setCookie(c, Session.cookieName, token, {
...cookieOpts,
maxAge: Session.maxAgeMs / 1000,
});
return c.redirect("/", 302);
} catch (error) {
console.error("[OAuth] Callback failed", error);
return c.json({ error: "OAuth callback failed" }, 500);
}
};
}
export { exchangeAuthCode, verifyAccessToken };
-30
View File
@@ -1,30 +0,0 @@
import { env } from "../lib/env";
import type { UserProfile } from "./types";
async function kimiRequest<T>(
path: string,
token: string,
init?: RequestInit,
): Promise<T | null> {
const resp = await fetch(`${env.kimiOpenUrl}${path}`, {
...init,
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
...init?.headers,
},
});
if (!resp.ok) {
const text = await resp.text();
console.warn(
`[kimi] Request to ${path} failed (${resp.status}): ${text}`,
);
return null;
}
return resp.json() as Promise<T>;
}
export const users = {
getProfile: (token: string) =>
kimiRequest<UserProfile>("/v1/users/me/profile", token),
};
-40
View File
@@ -1,40 +0,0 @@
import * as jose from "jose";
import { env } from "../lib/env";
import type { SessionPayload } from "./types";
const JWT_ALG = "HS256";
export async function signSessionToken(
payload: SessionPayload,
): Promise<string> {
const secret = new TextEncoder().encode(env.appSecret);
return new jose.SignJWT(payload)
.setProtectedHeader({ alg: JWT_ALG })
.setIssuedAt()
.setExpirationTime("1 year")
.sign(secret);
}
export async function verifySessionToken(
token: string,
): Promise<SessionPayload | null> {
if (!token) {
console.warn("[session] No token provided for verification.");
return null;
}
try {
const secret = new TextEncoder().encode(env.appSecret);
const { payload } = await jose.jwtVerify(token, secret, {
algorithms: [JWT_ALG],
});
const { unionId, clientId } = payload;
if (!unionId || !clientId) {
console.warn("[session] JWT payload missing required fields.");
return null;
}
return { unionId, clientId } as SessionPayload;
} catch (error) {
console.warn("[session] JWT verification failed:", error);
return null;
}
}
-18
View File
@@ -1,18 +0,0 @@
export type TokenResponse = {
access_token: string;
token_type: string;
expires_in: number;
refresh_token?: string;
scope: string;
};
export type SessionPayload = {
unionId: string;
clientId: string;
};
export type UserProfile = {
user_id: string;
name: string;
avatar_url: string;
};
-17
View File
@@ -1,17 +0,0 @@
import type { CookieOptions } from "hono/utils/cookie";
function isLocalhost(headers: Headers): boolean {
const host = headers.get("host") || "";
return host.startsWith("localhost:") || host.startsWith("127.0.0.1:");
}
export function getSessionCookieOptions(headers: Headers): CookieOptions {
const localhost = isLocalhost(headers);
return {
httpOnly: true,
path: "/",
sameSite: localhost ? "Lax" : "None",
secure: !localhost,
};
}
-19
View File
@@ -1,19 +0,0 @@
import "dotenv/config";
function required(name: string): string {
const value = process.env[name];
if (!value && process.env.NODE_ENV === "production") {
throw new Error(`Missing required environment variable: ${name}`);
}
return value ?? "";
}
export const env = {
appId: required("APP_ID"),
appSecret: required("APP_SECRET"),
isProduction: process.env.NODE_ENV === "production",
databaseUrl: required("DATABASE_URL"),
kimiAuthUrl: required("KIMI_AUTH_URL"),
kimiOpenUrl: required("KIMI_OPEN_URL"),
ownerUnionId: process.env.OWNER_UNION_ID ?? "",
};
-77
View File
@@ -1,77 +0,0 @@
interface RequestConfig extends RequestInit {
baseUrl?: string;
params?: Record<string, string | number>;
timeout?: number;
}
export class HttpClient {
private baseUrl: string;
private defaultHeaders: Record<string, string>;
constructor(baseURL: string, opts?: { headers?: Record<string, string> }) {
this.baseUrl = baseURL;
this.defaultHeaders = {
"Content-Type": "application/json",
...opts?.headers,
};
}
async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {
const {
method = "GET",
params,
body,
headers,
timeout = 30000,
...rest
} = config;
const url = new URL(`${this.baseUrl}${endpoint}`);
if (params) {
Object.entries(params).forEach(([key, value]) =>
url.searchParams.append(key, value.toString()),
);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url.toString(), {
...rest,
method,
headers: { ...this.defaultHeaders, ...headers },
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = (await response
.json()
.catch(() => ({}))) as Record<string, string>;
throw new Error(errorData.message || `HTTP Error: ${response.status}`);
}
return (await response.json()) as T;
} catch (error: any) {
if (error.name === "AbortError") {
throw new Error("Request timeout");
}
throw error;
}
}
get<T>(
url: string,
params?: RequestConfig["params"],
config?: RequestConfig,
) {
return this.request<T>(url, { ...config, method: "GET", params });
}
post<T>(url: string, body?: any, config?: RequestConfig) {
return this.request<T>(url, { ...config, method: "POST", body });
}
}
-23
View File
@@ -1,23 +0,0 @@
import type { Hono } from "hono";
import type { HttpBindings } from "@hono/node-server";
import { serveStatic } from "@hono/node-server/serve-static";
import fs from "fs";
import path from "path";
type App = Hono<{ Bindings: HttpBindings }>;
export function serveStaticFiles(app: App) {
const distPath = path.resolve(import.meta.dirname, "../dist/public");
app.use("*", serveStatic({ root: "./dist/public" }));
app.notFound((c) => {
const accept = c.req.header("accept") ?? "";
if (!accept.includes("text/html")) {
return c.json({ error: "Not Found" }, 404);
}
const indexPath = path.resolve(distPath, "index.html");
const content = fs.readFileSync(indexPath, "utf-8");
return c.html(content);
});
}
-42
View File
@@ -1,42 +0,0 @@
import { ErrorMessages } from "@contracts/constants";
import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
import type { TrpcContext } from "./context";
const t = initTRPC.context<TrpcContext>().create({
transformer: superjson,
});
export const createRouter = t.router;
export const publicQuery = t.procedure;
const requireAuth = t.middleware(async (opts) => {
const { ctx, next } = opts;
if (!ctx.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: ErrorMessages.unauthenticated,
});
}
return next({ ctx: { ...ctx, user: ctx.user } });
});
function requireRole(role: string) {
return t.middleware(async (opts) => {
const { ctx, next } = opts;
if (!ctx.user || ctx.user.role !== role) {
throw new TRPCError({
code: "FORBIDDEN",
message: ErrorMessages.insufficientRole,
});
}
return next({ ctx: { ...ctx, user: ctx.user } });
});
}
export const authedQuery = t.procedure.use(requireAuth);
export const adminQuery = authedQuery.use(requireRole("admin"));
-18
View File
@@ -1,18 +0,0 @@
import { drizzle } from "drizzle-orm/mysql2";
import { env } from "../lib/env";
import * as schema from "@db/schema";
import * as relations from "@db/relations";
const fullSchema = { ...schema, ...relations };
let instance: ReturnType<typeof drizzle<typeof fullSchema>>;
export function getDb() {
if (!instance) {
instance = drizzle(env.databaseUrl, {
mode: "planetscale",
schema: fullSchema,
});
}
return instance;
}
-36
View File
@@ -1,36 +0,0 @@
import { eq } from "drizzle-orm";
import * as schema from "@db/schema";
import type { InsertUser } from "@db/schema";
import { getDb } from "./connection";
import { env } from "../lib/env";
export async function findUserByUnionId(unionId: string) {
const rows = await getDb()
.select()
.from(schema.users)
.where(eq(schema.users.unionId, unionId))
.limit(1);
return rows.at(0);
}
export async function upsertUser(data: InsertUser) {
const values = { ...data };
const updateSet: Partial<InsertUser> = {
lastSignInAt: new Date(),
...data,
};
if (
values.role === undefined &&
values.unionId &&
values.unionId === env.ownerUnionId
) {
values.role = "admin";
updateSet.role = "admin";
}
await getDb()
.insert(schema.users)
.values(values)
.onDuplicateKeyUpdate({ set: updateSet });
}
-11
View File
@@ -1,11 +0,0 @@
import { authRouter } from "./auth-router";
import { formRouter } from "./form-router";
import { createRouter, publicQuery } from "./middleware";
export const appRouter = createRouter({
ping: publicQuery.query(() => ({ ok: true, ts: Date.now() })),
auth: authRouter,
form: formRouter,
});
export type AppRouter = typeof appRouter;
+185
View File
@@ -0,0 +1,185 @@
# Setup do Ambiente CI/CD - Avanzato
## Arquitetura
```
+------------------------------------------------------------------+
| SEU SERVIDOR |
| |
| +-------------------+ +-------------------+ +-----------+ |
| | Git Bare Repo | | Pipeline | | Site | |
| | /var/git/site |--->| /var/ci/ |--->| Nginx | |
| | | | | | | |
| | - hooks/post-receive | - build.sh | | /var/www | |
| | - hooks/pre-receive | - deploy.sh | | | |
| +-------------------+ +-------------------+ +-----------+ |
| ^ | |
| | v |
| +-------------------+ +-------------------+ |
| | Seu Computador | | Mautic | |
| | | | mkt.avanzato | |
| | git push | | | |
| +-------------------+ +-------------------+ |
+------------------------------------------------------------------+
```
## 1. Instalar dependencias no servidor
```bash
# Atualizar sistema
apt update && apt upgrade -y
# Instalar Git
apt install -y git curl
# Instalar Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# Verificar
node -v # v20.x.x
npm -v # 10.x.x
# Instalar Nginx
apt install -y nginx
# Instalar PM2 (opcional, para gerenciar processos)
npm install -g pm2
```
## 2. Criar usuario de deploy
```bash
# Criar usuario dedicado
useradd -m -s /bin/bash deploy
# Adicionar ao grupo www-data
usermod -aG www-data deploy
# Criar chave SSH (como usuario deploy)
su - deploy
ssh-keygen -t ed25519 -C "deploy" -f ~/.ssh/id_ed25519 -N ""
# Ver chave publica (para autorizar pushes)
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
```
## 3. Criar repositorio Git bare
```bash
# Criar pasta
mkdir -p /var/git/site.git
cd /var/git/site.git
git init --bare
# Permissoes
chown -R deploy:deploy /var/git/site.git
chmod -R 755 /var/git/site.git
```
## 4. Configurar hooks Git
```bash
# Copiar hooks
sudo su - deploy
cp /var/ci/hooks/post-receive /var/git/site.git/hooks/post-receive
chmod +x /var/git/site.git/hooks/post-receive
```
## 5. Configurar Nginx
```bash
# Criar config
nano /etc/nginx/sites-available/avanzato
# Ver link: CI/CD > nginx-config.conf
# Ativar
ln -s /etc/nginx/sites-available/avanzato /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
# SSL com Let's Encrypt
certbot --nginx -d avanzato.com.br -d www.avanzato.com.br
```
## 6. Criar estrutura de pastas
```bash
mkdir -p /var/www/avanzato # Site em producao
mkdir -p /var/www/avanzato-staging # Site em staging (opcional)
mkdir -p /var/ci # Pipeline scripts
mkdir -p /var/ci/logs # Logs de build/deploy
mkdir -p /var/ci/backups # Backups automaticos
mkdir -p /var/ci/assets # Assets preservados
chown -R deploy:www-data /var/www
chmod -R 755 /var/www
```
## 7. Configurar pipeline
```bash
# Copiar scripts do pipeline
cp /var/ci/scripts/* /var/ci/
chmod +x /var/ci/*.sh
# Configurar variaveis
nano /var/ci/config.env
# Conteudo:
DEPLOY_PATH=/var/www/avanzato
STAGING_PATH=/var/www/avanzato-staging
BACKUP_PATH=/var/ci/backups
LOG_PATH=/var/ci/logs
MAUTIC_URL=https://mkt.avanzato.com.br
```
## 8. Configurar seu computador local
```bash
# Adicionar remote no seu projeto local
cd /caminho/do/projeto/avanzato-site
git remote add servidor ssh://deploy@SEU_SERVIDOR/var/git/site.git
# Testar conexao
ssh deploy@SEU_SERVIDOR "echo 'Conectado!'"
# Push inicial
git push servidor main
```
## Comandos de uso diario
| Comando | Resultado |
|---------|-----------|
| `git push servidor main` | Deploy automatico para producao |
| `git push servidor staging` | Deploy para staging |
| `ssh deploy@SERVIDOR "cat /var/ci/logs/last-deploy.log"` | Ver ultimo log |
| `ssh deploy@SERVIDOR "ls /var/ci/backups"` | Listar backups |
## Estrutura final no servidor
```
/var/
git/
site.git/ # Repo bare (recebe pushes)
hooks/
post-receive # Hook de deploy automatico
ci/
scripts/
build.sh # Compila o site
deploy.sh # Envia para producao
preserve-assets.sh # Preserva logo/imagens
backup.sh # Cria backup
hooks/
post-receive # Template do hook
logs/ # Logs de execucao
backups/ # Backups automaticos
assets/ # Assets preservados
config.env # Variaveis de ambiente
www/
avanzato/ # Site em producao
index.html
assets/
backups/ # Backups antigos
```
+57
View File
@@ -0,0 +1,57 @@
# ============================================================================
# Configuracao do Pipeline CI/CD - Avanzato
# ============================================================================
# Edite estas variaveis conforme seu ambiente.
# ============================================================================
# --- CAMINHOS ---
# Caminho onde o site fica em producao
DEPLOY_PATH=/var/www/avanzato
# Caminho do site em staging (opcional)
STAGING_PATH=/var/www/avanzato-staging
# Caminho para backups
BACKUP_PATH=/var/ci/backups
# Caminho para logs
LOG_PATH=/var/ci/logs
# Caminho para assets preservados (logo, imagens)
ASSETS_PATH=/var/ci/assets
# --- SERVIDOR ---
# URL do site
SITE_URL=https://avanzato.com.br
# URL do site em staging
STAGING_URL=https://staging.avanzato.com.br
# --- INTEGRACOES ---
# URL do Mautic
MAUTIC_URL=https://mkt.avanzato.com.br
# URL do EspoCRM
ESPOCRM_URL=https://crm.avanzato.com.br
# Chave API do Brevo (opcional)
BREVO_API_KEY=
# Google Ads ID
GOOGLE_ADS_ID=AW-16714813566
# --- E-MAIL ---
# E-mail para notificacoes
NOTIFY_EMAIL=leads@avanzato.com.br
# --- CONFIGURACOES ---
# Numero de backups a manter
MAX_BACKUPS=10
# Timeout do build (segundos)
BUILD_TIMEOUT=300
+240
View File
@@ -0,0 +1,240 @@
#!/bin/bash
# ============================================================================
# Git Hook: post-receive
# ============================================================================
# Este hook executa automaticamente apos cada git push.
# Ele faz build e deploy do site sem intervencao manual.
#
# Instalacao:
# cp ci/hooks/post-receive /var/git/site.git/hooks/
# chmod +x /var/git/site.git/hooks/post-receive
# ============================================================================
set -e
# Cores
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuracoes
REPO_DIR="$(pwd)"
DEPLOY_DIR="/var/tmp/site-deploy-$(date +%s)"
CI_DIR="/var/ci"
LOG_FILE="/var/ci/logs/deploy-$(date +%Y%m%d-%H%M%S).log"
LAST_LOG="/var/ci/logs/last-deploy.log"
CONFIG_FILE="/var/ci/config.env"
# Criar pasta de logs
mkdir -p "$(dirname "$LOG_FILE")"
# Funcao de log
log() {
echo -e "$1" | tee -a "$LOG_FILE"
}
# Redirect output to log
exec > >(tee -a "$LOG_FILE")
exec 2>&1
log "${BLUE}============================================================${NC}"
log "${BLUE} Avanzato CI/CD - Deploy Automatico${NC}"
log "${BLUE}============================================================${NC}"
log ""
log "Data: $(date)"
log "Branch recebida: $1"
log "Commit: $2"
log ""
# Carregar configuracoes
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
log "${GREEN}[OK]${NC} Configuracoes carregadas."
else
log "${YELLOW}[AVISO]${NC} Arquivo config.env nao encontrado. Usando defaults."
DEPLOY_PATH="/var/www/avanzato"
STAGING_PATH="/var/www/avanzato-staging"
BACKUP_PATH="/var/ci/backups"
fi
# Detectar branch
while read oldrev newrev refname; do
BRANCH=$(git rev-parse --symbolic --abbrev-ref "$refname")
log "Processando branch: ${YELLOW}${BRANCH}${NC}"
case "$BRANCH" in
main|master)
TARGET="production"
DEPLOY_TARGET="$DEPLOY_PATH"
;;
staging)
TARGET="staging"
DEPLOY_TARGET="$STAGING_PATH"
;;
*)
log "${YELLOW}[IGNORADO]${NC} Branch '${BRANCH}' nao configurada para deploy."
log "Branches validas: main, master, staging"
exit 0
;;
esac
done
log ""
log "${BLUE}------------------------------------------------------------${NC}"
log "Ambiente: ${YELLOW}${TARGET}${NC}"
log "Destino: ${DEPLOY_TARGET}"
log "${BLUE}------------------------------------------------------------${NC}"
log ""
# ==========================================================================
# PASSO 1: Checkout do codigo
# ==========================================================================
log "${BLUE}[1/6]${NC} Checkout do codigo..."
mkdir -p "$DEPLOY_DIR"
git --git-dir="$REPO_DIR" --work-tree="$DEPLOY_DIR" checkout -f "$BRANCH" 2>&1
log "${GREEN}[OK]${NC} Codigo extraido para ${DEPLOY_DIR}"
log ""
# ==========================================================================
# PASSO 2: Instalar dependencias
# ==========================================================================
log "${BLUE}[2/6]${NC} Instalando dependencias..."
cd "$DEPLOY_DIR"
if [ -f "package.json" ]; then
npm ci --silent 2>&1 || {
log "${RED}[ERRO]${NC} Falha ao instalar dependencias."
exit 1
}
log "${GREEN}[OK]${NC} Dependencias instaladas."
else
log "${YELLOW}[AVISO]${NC} package.json nao encontrado. Pulando."
fi
log ""
# ==========================================================================
# PASSO 3: Build
# ==========================================================================
log "${BLUE}[3/6]${NC} Executando build..."
cd "$DEPLOY_DIR"
if [ -f "package.json" ]; then
npm run build 2>&1 || {
log "${RED}[ERRO]${NC} Falha no build."
exit 1
}
# Verificar se a pasta dist foi criada
if [ ! -d "dist" ]; then
log "${RED}[ERRO]${NC} Pasta dist/ nao encontrada apos build."
exit 1
fi
log "${GREEN}[OK]${NC} Build concluido com sucesso."
log "Tamanho: $(du -sh dist/ | cut -f1)"
else
# Se nao tem package.json, usar o proprio diretorio
log "${YELLOW}[AVISO]${NC} Projeto estatico detectado. Usando arquivos diretamente."
fi
log ""
# ==========================================================================
# PASSO 4: Preservar assets
# ==========================================================================
log "${BLUE}[4/6]${NC} Preservando assets customizados..."
cd "$DEPLOY_DIR"
# Se existir pasta custom-assets, preservar
if [ -d "custom-assets" ]; then
log "Assets customizados encontrados. Preservando..."
bash "${DEPLOY_DIR}/scripts/preserve-assets.sh" "${DEPLOY_DIR}/dist" 2>&1 || {
log "${YELLOW}[AVISO]${NC} Erro ao preservar assets (nao critico)."
}
else
log "${YELLOW}[AVISO]${NC} Nenhum asset customizado encontrado."
fi
# Se existem assets preservados no /var/ci/assets, copiar
if [ -d "/var/ci/assets" ] && [ "$(ls -A /var/ci/assets)" ]; then
log "Copiando assets preservados..."
cp -r /var/ci/assets/* "${DEPLOY_DIR}/dist/" 2>/dev/null || true
fi
log "${GREEN}[OK]${NC} Assets processados."
log ""
# ==========================================================================
# PASSO 5: Backup do site atual
# ==========================================================================
log "${BLUE}[5/6]${NC} Criando backup..."
if [ -d "$DEPLOY_TARGET" ] && [ "$(ls -A "$DEPLOY_TARGET" 2>/dev/null)" ]; then
BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_PATH"
# Criar backup (excluindo backups antigos para nao duplicar)
tar -czf "${BACKUP_PATH}/${BACKUP_NAME}.tar.gz" \
-C "$(dirname "$DEPLOY_TARGET")" \
--exclude="backups" \
"$(basename "$DEPLOY_TARGET")" 2>/dev/null || true
# Limpar backups antigos (manter 10)
ls -t "${BACKUP_PATH}"/*.tar.gz 2>/dev/null | tail -n +11 | xargs -r rm -f
log "${GREEN}[OK]${NC} Backup criado: ${BACKUP_NAME}.tar.gz"
else
log "${YELLOW}[AVISO]${NC} Nada para fazer backup (primeiro deploy)."
fi
log ""
# ==========================================================================
# PASSO 6: Deploy
# ==========================================================================
log "${BLUE}[6/6]${NC} Fazendo deploy..."
# Criar diretorio se nao existir
mkdir -p "$DEPLOY_TARGET"
# Limpar conteudo antigo (exceto backups)
find "$DEPLOY_TARGET" -mindepth 1 -not -path "${DEPLOY_TARGET}/backups/*" -not -name "backups" -delete 2>/dev/null || rm -rf "${DEPLOY_TARGET:?}"/*
# Copiar novos arquivos
if [ -d "${DEPLOY_DIR}/dist" ]; then
cp -r "${DEPLOY_DIR}/dist/"* "$DEPLOY_TARGET/"
else
# Se nao tem dist, copiar tudo
cp -r "${DEPLOY_DIR}/"* "$DEPLOY_TARGET/"
fi
# Permissoes
chown -R www-data:www-data "$DEPLOY_TARGET"
chmod -R 755 "$DEPLOY_TARGET"
# Limpar pasta temporaria
rm -rf "$DEPLOY_DIR"
log "${GREEN}[OK]${NC} Deploy concluido!"
log ""
# ==========================================================================
# FINAL
# ==========================================================================
log "${BLUE}============================================================${NC}"
log "${GREEN} Deploy concluido com sucesso!${NC}"
log "${BLUE}============================================================${NC}"
log ""
log "Ambiente: ${TARGET}"
log "Caminho: ${DEPLOY_TARGET}"
log "Backup: ${BACKUP_PATH}"
log "Log: ${LOG_FILE}"
log "Data: $(date)"
log ""
# Atualizar link do ultimo log
ln -sf "$LOG_FILE" "$LAST_LOG"
# Notificar via webhook (opcional)
# curl -X POST "https://hooks.slack.com/services/..." \
# -H 'Content-Type: application/json' \
# -d "{\"text\":\"Deploy ${TARGET} concluido!\"}" 2>/dev/null || true
exit 0
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# ============================================================================
# Git Hook: pre-receive
# ============================================================================
# Executado ANTES do push ser aceito.
# Pode bloquear pushes que nao atendem criterios.
# ============================================================================
set -e
# Rejeitar pushes diretos na main (forcar via PR)
# Descomente se quiser forcar code review:
# while read oldrev newrev refname; do
# BRANCH=$(git rev-parse --symbolic --abbrev-ref "$refname")
# if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
# echo "Push direto na main bloqueado. Use pull request."
# exit 1
# fi
# done
# Aceitar todos os pushes por padrao
exit 0
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
# ============================================================================
# Script de Backup - Avanzato
# ============================================================================
# Uso: bash ci/scripts/backup.sh [ambiente]
#
# Faz backup manual do site.
# ============================================================================
set -e
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# Carregar config
CONFIG_FILE="/var/ci/config.env"
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/avanzato}"
BACKUP_PATH="${BACKUP_PATH:-/var/ci/backups}"
AMBIENTE="${1:-production}"
# Determinar diretorio
if [ "$AMBIENTE" = "staging" ]; then
DEPLOY_PATH="${STAGING_PATH:-$DEPLOY_PATH-staging}"
fi
BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_PATH"
if [ ! -d "$DEPLOY_PATH" ] || [ ! "$(ls -A "$DEPLOY_PATH" 2>/dev/null)" ]; then
echo -e "${RED}Nada para fazer backup.${NC}"
exit 1
fi
echo -e "${GREEN}Criando backup...${NC}"
echo "Origem: $DEPLOY_PATH"
echo "Destino: $BACKUP_PATH/${BACKUP_NAME}.tar.gz"
tar -czf "${BACKUP_PATH}/${BACKUP_NAME}.tar.gz" \
-C "$(dirname "$DEPLOY_PATH")" \
--exclude="backups" \
"$(basename "$DEPLOY_PATH")"
echo -e "${GREEN}Backup criado: ${BACKUP_NAME}.tar.gz${NC}"
# Limpar backups antigos
MAX_BACKUPS="${MAX_BACKUPS:-10}"
echo "Limpando backups antigos (manter $MAX_BACKUPS)..."
ls -t "${BACKUP_PATH}"/*.tar.gz 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm -f
echo -e "${GREEN}Concluido!${NC}"
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# ============================================================================
# Script de Rollback - Avanzato
# ============================================================================
# Uso: bash ci/scripts/rollback.sh [numero]
#
# Restaura um backup anterior.
# ============================================================================
set -e
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
CONFIG_FILE="/var/ci/config.env"
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/avanzato}"
BACKUP_PATH="${BACKUP_PATH:-/var/ci/backups}"
AMBIENTE="${1:-production}"
if [ "$AMBIENTE" = "staging" ]; then
DEPLOY_PATH="${STAGING_PATH:-$DEPLOY_PATH-staging}"
fi
echo -e "${BLUE}============================================================${NC}"
echo -e "${BLUE} Avanzato - Rollback${NC}"
echo -e "${BLUE}============================================================${NC}"
echo ""
# Listar backups disponiveis
echo "Backups disponiveis:"
echo ""
BACKUPS=()
i=1
for backup in $(ls -t "${BACKUP_PATH}"/*.tar.gz 2>/dev/null); do
nome=$(basename "$backup")
tamanho=$(du -h "$backup" | cut -f1)
data=$(stat -c %y "$backup" | cut -d'.' -f1)
printf " ${GREEN}%2d.${NC} %s (${tamanho}) %s\n" "$i" "$nome" "$data"
BACKUPS+=("$backup")
i=$((i + 1))
done
if [ ${#BACKUPS[@]} -eq 0 ]; then
echo -e "${RED}Nenhum backup encontrado.${NC}"
exit 1
fi
echo ""
read -p "Digite o numero do backup para restaurar: " NUM
if ! [[ "$NUM" =~ ^[0-9]+$ ]] || [ "$NUM" -lt 1 ] || [ "$NUM" -gt ${#BACKUPS[@]} ]; then
echo -e "${RED}Numero invalido.${NC}"
exit 1
fi
BACKUP="${BACKUPS[$((NUM - 1))]}"
echo ""
echo -e "Selecionado: ${YELLOW}$(basename "$BACKUP")${NC}"
echo "Destino: $DEPLOY_PATH"
read -p "Confirma? (s/N): " CONFIRM
if [ "$CONFIRM" != "s" ] && [ "$CONFIRM" != "S" ]; then
echo "Cancelado."
exit 0
fi
echo ""
echo -e "${YELLOW}Fazendo backup do estado atual antes do rollback...${NC}"
bash "$(dirname "$0")/backup.sh" "$AMBIENTE"
echo ""
echo -e "${YELLOW}Restaurando backup...${NC}"
rm -rf "${DEPLOY_PATH:?}"/*
tar -xzf "$BACKUP" -C "$(dirname "$DEPLOY_PATH")" --strip-components=1
echo -e "${GREEN}Rollback concluido!${NC}"
echo ""
echo "Verifique: ${DEPLOY_PATH}"
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
# ============================================================================
# Script de Status - Avanzato
# ============================================================================
# Uso: bash ci/scripts/status.sh
#
# Mostra status do site, ultimos deploys e backups.
# ============================================================================
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
CONFIG_FILE="/var/ci/config.env"
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/avanzato}"
BACKUP_PATH="${BACKUP_PATH:-/var/ci/backups}"
LOG_PATH="${LOG_PATH:-/var/ci/logs}"
echo -e "${BLUE}============================================================${NC}"
echo -e "${BLUE} Avanzato - Status do Ambiente${NC}"
echo -e "${BLUE}============================================================${NC}"
echo ""
# Status do site
echo -e "${GREEN}SITUACAO DO SITE${NC}"
echo " URL: ${SITE_URL:-https://avanzato.com.br}"
echo " Caminho: $DEPLOY_PATH"
echo " Tamanho: $(du -sh "$DEPLOY_PATH" 2>/dev/null | cut -f1 || echo 'N/A')"
echo ""
# Status do Nginx
echo -e "${GREEN}NGINX${NC}"
systemctl is-active nginx 2>/dev/null && \
echo -e " Status: ${GREEN}Ativo${NC}" || \
echo -e " Status: ${RED}Inativo${NC}"
echo ""
# Ultimo deploy
echo -e "${GREEN}ULTIMOS DEPLOYS${NC}"
if [ -f "${LOG_PATH}/last-deploy.log" ]; then
echo " Log: ${LOG_PATH}/last-deploy.log"
echo " Data: $(stat -c %y "${LOG_PATH}/last-deploy.log" | cut -d'.' -f1)"
else
echo -e " ${YELLOW}Nenhum deploy registrado.${NC}"
fi
echo ""
# Backups
echo -e "${GREEN}BACKUPS${NC}"
echo " Caminho: $BACKUP_PATH"
echo " Quantidade: $(ls "${BACKUP_PATH}"/*.tar.gz 2>/dev/null | wc -l)"
echo " Tamanho total: $(du -sh "$BACKUP_PATH" 2>/dev/null | cut -f1 || echo '0')"
echo ""
# Ultimos logs
echo -e "${GREEN}ULTIMOS LOGS${NC}"
ls -t "${LOG_PATH}"/*.log 2>/dev/null | head -5 | while read log; do
echo " $(basename "$log") - $(stat -c %y "$log" | cut -d'.' -f1)"
done
echo ""
# Assets preservados
echo -e "${GREEN}ASSETS PRESERVADOS${NC}"
echo " Caminho: ${ASSETS_PATH:-/var/ci/assets}"
echo " Conteudo:"
ls -la "${ASSETS_PATH:-/var/ci/assets}" 2>/dev/null || echo " (vazio)"
echo ""
echo -e "${BLUE}============================================================${NC}"
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "postcss.config.js",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
-14
View File
@@ -1,14 +0,0 @@
export const Session = {
cookieName: "kimi_sid",
maxAgeMs: 365 * 24 * 60 * 60 * 1000,
} as const;
export const ErrorMessages = {
unauthenticated: "Authentication required",
insufficientRole: "Insufficient permissions",
} as const;
export const Paths = {
login: "/login",
oauthCallback: "/api/oauth/callback",
} as const;
-15
View File
@@ -1,15 +0,0 @@
type AppError = { tag: "app_error"; status: number; message: string };
function appError(status: number, message: string): AppError {
return { tag: "app_error", status, message };
}
export const Errors = {
badRequest: (msg: string) => appError(400, msg),
unauthorized: (msg: string) => appError(401, msg),
forbidden: (msg: string) => appError(403, msg),
notFound: (msg: string) => appError(404, msg),
internal: (msg: string) => appError(500, msg),
} as const;
export type { AppError };
-2
View File
@@ -1,2 +0,0 @@
export type * from "../db/schema";
export * from "./errors";
-1
View File
@@ -1 +0,0 @@
import {} from "./schema";
-109
View File
@@ -1,109 +0,0 @@
import {
mysqlTable,
mysqlEnum,
serial,
varchar,
text,
timestamp,
// bigint,
} from "drizzle-orm/mysql-core";
export const users = mysqlTable("users", {
id: serial("id").primaryKey(),
unionId: varchar("unionId", { length: 255 }).notNull().unique(),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 320 }),
avatar: text("avatar"),
role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt")
.defaultNow()
.notNull()
.$onUpdate(() => new Date()),
lastSignInAt: timestamp("lastSignInAt").defaultNow().notNull(),
});
export type User = typeof users.$inferSelect;
export type InsertUser = typeof users.$inferInsert;
// ============================================================================
// SISTEMA DE FORMULARIOS - Area do Cliente Avanzato
// ============================================================================
// Formularios criados pela Avanzato para clientes
export const clientForms = mysqlTable("client_forms", {
id: serial("id").primaryKey(),
title: varchar("title", { length: 255 }).notNull(),
description: text("description"),
clientName: varchar("clientName", { length: 255 }).notNull(),
clientEmail: varchar("clientEmail", { length: 320 }),
status: mysqlEnum("status", ["active", "inactive", "archived"]).default("inactive").notNull(),
accessCode: varchar("access_code", { length: 32 }).notNull().unique(),
createdBy: varchar("created_by", { length: 255 }),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
});
export type ClientForm = typeof clientForms.$inferSelect;
export type InsertClientForm = typeof clientForms.$inferInsert;
// Secoes de cada formulario
export const formSections = mysqlTable("form_sections", {
id: serial("id").primaryKey(),
formId: varchar("form_id", { length: 32 }).notNull(),
title: varchar("title", { length: 255 }).notNull(),
description: text("description"),
orderNum: serial("order_num"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export type FormSection = typeof formSections.$inferSelect;
// Perguntas/Campos de cada secao
export const formQuestions = mysqlTable("form_questions", {
id: serial("id").primaryKey(),
formId: varchar("form_id", { length: 32 }).notNull(),
sectionId: varchar("section_id", { length: 32 }),
question: text("question").notNull(),
fieldType: mysqlEnum("field_type", ["text", "textarea", "number", "select", "multiselect", "date", "file"]).default("text").notNull(),
options: text("options"), // JSON string para select/multiselect
required: mysqlEnum("required", ["yes", "no"]).default("no").notNull(),
orderNum: serial("order_num"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export type FormQuestion = typeof formQuestions.$inferSelect;
// Respostas submetidas pelos clientes
export const formAnswers = mysqlTable("form_answers", {
id: serial("id").primaryKey(),
formId: varchar("form_id", { length: 32 }).notNull(),
questionId: varchar("question_id", { length: 32 }).notNull(),
answer: text("answer"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export type FormAnswer = typeof formAnswers.$inferSelect;
// Submissoes completas (uma por formulario preenchido)
export const formSubmissions = mysqlTable("form_submissions", {
id: serial("id").primaryKey(),
formId: varchar("form_id", { length: 32 }).notNull(),
submittedBy: varchar("submitted_by", { length: 255 }),
submittedByEmail: varchar("submitted_by_email", { length: 320 }),
notes: text("notes"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export type FormSubmission = typeof formSubmissions.$inferSelect;
//
// Example:
// export const posts = mysqlTable("posts", {
// id: serial("id").primaryKey(),
// title: varchar("title", { length: 255 }).notNull(),
// content: text("content"),
// createdAt: timestamp("created_at").notNull().defaultNow(),
// });
//
// Note: FK columns referencing a serial() PK must use:
// bigint("columnName", { mode: "number", unsigned: true }).notNull()
-181
View File
@@ -1,181 +0,0 @@
// ============================================================================
// SEED - Formulario UEA-PRODEM-ANGOLA
// ============================================================================
// Executar: npx tsx db/seed.ts
// ============================================================================
import { getDb } from "../api/queries/connection";
import { clientForms, formSections, formQuestions } from "./schema";
const FORM_ID = "uea-prodem-angola";
const sections = [
{ id: "s1", title: "1. DADOS INSTITUCIONAIS", description: "Informacoes sobre a estrutura atual da UEA", order: 1 },
{ id: "s2", title: "2. PROJECAO DE CRESCIMENTO", description: "Estimativas de crescimento para os proximos 36 meses", order: 2 },
{ id: "s3", title: "3. REGISTO E VALIDACAO", description: "Processo de aprovacao e validacao de membros", order: 3 },
{ id: "s4", title: "4. ESTRUTURA DE MEMBROS", description: "Categorias, niveis e beneficios", order: 4 },
{ id: "s5", title: "5. PAGAMENTOS", description: "Plataformas e regras de faturacao", order: 5 },
{ id: "s6", title: "6. INCUBADORA EMPRESARIAL", description: "Funil de evolucao e acompanhamento", order: 6 },
{ id: "s7", title: "7. FORMACAO E CERTIFICACAO", description: "Cursos, conteudos e certificados", order: 7 },
{ id: "s8", title: "8. MARKETPLACE B2B", description: "Produtos, servicos e intermedicao", order: 8 },
{ id: "s9", title: "9. EVENTOS", description: "Gestao e transmissao de eventos", order: 9 },
{ id: "s10", title: "10. BANCO CENTRAL DE DADOS (BCD)", description: "Objetivos, dados e dashboards", order: 10 },
{ id: "s11", title: "11. DOCUMENTACAO LEGAL", description: "Documentos necessarios para o projeto", order: 11 },
{ id: "s12", title: "12. INFRAESTRUTURA E HOSPEDAGEM", description: "Ambiente, cloud e suporte tecnico", order: 12 },
{ id: "s13", title: "13. RELATORIOS EXECUTIVOS", description: "Relatorios para a Direcao", order: 13 },
];
const questions = [
// SECAO 1 - DADOS INSTITUCIONAIS
{ sectionId: "s1", q: "1.1 Quantidade atual de membros individuais ativos", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.2 Quantidade atual de empresas associadas", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.3 Quantidade total de cadastros existentes", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.4 Quantidade de membros pagantes", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.5 Quantidade de membros inadimplentes", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.6 Quantidade de provincias atualmente representadas", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.7 Quantidade de colaboradores administrativos da UEA", type: "number" as const, required: "yes" as const },
{ sectionId: "s1", q: "1.8 Quantidade de utilizadores internos que terao acesso administrativo ao sistema", type: "number" as const, required: "yes" as const },
// SECAO 2 - PROJECAO DE CRESCIMENTO
{ sectionId: "s2", q: "2.1 Membros previstos para 12 meses", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.1 Membros previstos para 24 meses", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.1 Membros previstos para 36 meses", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.2 Empresas previstas para 12 meses", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.2 Empresas previstas para 24 meses", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.2 Empresas previstas para 36 meses", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.3 Numero medio de novos cadastros esperado por mes", type: "number" as const, required: "yes" as const },
{ sectionId: "s2", q: "2.4 Numero medio de acessos simultaneos esperado durante eventos ou campanhas nacionais", type: "number" as const, required: "yes" as const },
// SECAO 3 - REGISTO E VALIDACAO
{ sectionId: "s3", q: "3.1 Quem aprovara os novos membros?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s3", q: "3.2 Existirao administradores provinciais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s3", q: "3.3 Existirao administradores municipais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s3", q: "3.4 O processo de validacao sera centralizado ou descentralizado?", type: "select" as const, options: JSON.stringify(["Centralizado", "Descentralizado", "Misto", "A definir"]), required: "yes" as const },
{ sectionId: "s3", q: "3.5 Quais documentos serao obrigatorios para cada categoria de membro?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s3", q: "3.6 Sera necessaria assinatura digital dos documentos?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s3", q: "3.7 O certificado digital do membro devera possuir QR Code de validacao publica?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
// SECAO 4 - ESTRUTURA DE MEMBROS
{ sectionId: "s4", q: "4.1 Confirmar todas as categorias de membros", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s4", q: "4.2 Confirmar niveis de adesao: Bronze, Prata, Ouro, Platina - havera outros?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s4", q: "4.3 Quais beneficios especificos existirao em cada categoria?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s4", q: "4.4 Cada categoria tera valor de quota diferente?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s4", q: "4.5 O associado podera alterar sua categoria diretamente pela plataforma?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
// SECAO 5 - PAGAMENTOS
{ sectionId: "s5", q: "5.1 Quais plataformas de pagamento serao utilizadas? (Multicaixa Express, Referencia Bancaria, Transferencia, Cartao, Outras)", type: "multiselect" as const, options: JSON.stringify(["Multicaixa Express", "Referencia Bancaria", "Transferencia Bancaria", "Cartao", "Outras"]), required: "yes" as const },
{ sectionId: "s5", q: "5.2 Existe fornecedor ja homologado para integracao de pagamentos?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s5", q: "5.3 O sistema de faturacao ja foi definido?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "Em desenvolvimento"]), required: "yes" as const },
{ sectionId: "s5", q: "5.4 Sera necessaria emissao automatica de: Faturas, Recibos, Declaracoes de pagamento?", type: "multiselect" as const, options: JSON.stringify(["Faturas", "Recibos", "Declaracoes de pagamento"]), required: "yes" as const },
{ sectionId: "s5", q: "5.5 Qual sera a regra de bloqueio por inadimplencia?", type: "textarea" as const, required: "yes" as const },
// SECAO 6 - INCUBADORA EMPRESARIAL
{ sectionId: "s6", q: "6.1 Quais etapas existirao no funil de evolucao das empresas? (Ideia, Planeamento, MVP, Operacao, Crescimento, Escala)", type: "multiselect" as const, options: JSON.stringify(["Ideia", "Planeamento", "MVP", "Operacao", "Crescimento", "Escala"]), required: "yes" as const },
{ sectionId: "s6", q: "6.2 Quem acompanhara os incubados?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s6", q: "6.3 Havera metas obrigatorias?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s6", q: "6.4 Havera indicadores de desempenho (KPIs)?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s6", q: "6.5 Quais relatorios deverao ser produzidos?", type: "textarea" as const, required: "yes" as const },
// SECAO 7 - FORMACAO E CERTIFICACAO
{ sectionId: "s7", q: "7.1 Quantidade estimada de cursos", type: "number" as const, required: "yes" as const },
{ sectionId: "s7", q: "7.2 Os cursos serao hospedados na propria plataforma ou em ambiente externo?", type: "select" as const, options: JSON.stringify(["Plataforma propria", "Ambiente externo", "Misto", "A definir"]), required: "yes" as const },
{ sectionId: "s7", q: "7.3 Quem produzira os conteudos?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s7", q: "7.4 Os certificados possuirao validacao online?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s7", q: "7.5 Sera necessario historico academico dos membros?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
// SECAO 8 - MARKETPLACE B2B
{ sectionId: "s8", q: "8.1 Quais tipos de produtos e servicos poderao ser anunciados?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s8", q: "8.2 Existira intermediacao financeira pela UEA?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s8", q: "8.3 Existirao avaliacoes dos fornecedores?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s8", q: "8.4 Existira sistema de mensagens entre membros?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
// SECAO 9 - EVENTOS
{ sectionId: "s9", q: "9.1 Quantidade media de eventos por ano", type: "number" as const, required: "yes" as const },
{ sectionId: "s9", q: "9.2 Existira venda de ingressos?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s9", q: "9.3 Existira transmissao online?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s9", q: "9.4 Existira emissao de certificados por participacao?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
// SECAO 10 - BANCO CENTRAL DE DADOS
{ sectionId: "s10", q: "10.1 Qual o objetivo operacional do BCD?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s10", q: "10.2 Quais dados deverao ser consolidados?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s10", q: "10.3 Quem tera acesso aos dados?", type: "textarea" as const, required: "yes" as const },
{ sectionId: "s10", q: "10.4 Existirao dashboards provinciais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s10", q: "10.5 Existirao dashboards nacionais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
{ sectionId: "s10", q: "10.6 Quais indicadores deverao ser acompanhados em tempo real?", type: "textarea" as const, required: "yes" as const },
// SECAO 11 - DOCUMENTACAO LEGAL
{ sectionId: "s11", q: "11.1 Estatuto completo da UEA - sera enviado?", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.2 Certidao de Registo", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.3 Regulamentos internos", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.4 Organograma oficial", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.5 Politica de privacidade", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.6 Politica de protecao de dados", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.7 Manual de identidade visual", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
{ sectionId: "s11", q: "11.8 Logotipos oficiais em formato vetorial", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
// SECAO 12 - INFRAESTRUTURA
{ sectionId: "s12", q: "12.1 O ambiente sera hospedado em Angola ou internacionalmente?", type: "select" as const, options: JSON.stringify(["Angola", "Internacional", "Hibrido", "A definir"]), required: "yes" as const },
{ sectionId: "s12", q: "12.2 Existe preferencia por provedor cloud? (AWS, Azure, Google Cloud, Digital Ocean, Infraestrutura propria)", type: "multiselect" as const, options: JSON.stringify(["AWS", "Azure", "Google Cloud", "Digital Ocean", "Infraestrutura propria", "A definir"]), required: "yes" as const },
{ sectionId: "s12", q: "12.3 Qual o orcamento previsto para hospedagem mensal?", type: "text" as const, required: "no" as const },
{ sectionId: "s12", q: "12.4 Existe equipa tecnica interna para suporte?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "Parcial"]), required: "yes" as const },
// SECAO 13 - RELATORIOS EXECUTIVOS
{ sectionId: "s13", q: "13.1 Relatorios desejados: Novos associados por periodo", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.2 Relatorios desejados: Associados por provincia", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.3 Relatorios desejados: Empresas incubadas", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.4 Relatorios desejados: Quotas recebidas", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.5 Relatorios desejados: Quotas em atraso", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.6 Relatorios desejados: Participacao em cursos", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.7 Relatorios desejados: Participacao em eventos", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.8 Relatorios desejados: Crescimento por sector economico", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.9 Relatorios desejados: Crescimento por provincia", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.10 Relatorios desejados: Indicadores de empregabilidade", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
{ sectionId: "s13", q: "13.11 Relatorios desejados: Indicadores de impacto economico", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
];
async function seed() {
const db = getDb();
console.log("[Seed] Inserindo formulario UEA-PRODEM-ANGOLA...");
// Criar formulario
const code = Math.random().toString(36).substring(2, 10).toUpperCase();
await db.insert(clientForms).values({
id: 1,
title: "Levantamento de Requisitos - Portal e Plataforma Digital UEA-PRODEM-ANGOLA",
description: "Formulario completo para levantamento de requisitos do ecossistema digital composto por Portal Institucional, Gestao de Associados, BCD, Area Restrita, Sistema de Pagamento, Biblioteca Digital, Plataforma de Formacao, Sistema de Certificacao, Incubadora Empresarial, Gestao de Mentorias, Marketplace B2B, Dashboard Executivo, Gestao de Eventos e Relatorios Gerenciais.",
clientName: "UEA-PRODEM-ANGOLA",
clientEmail: "",
status: "active",
accessCode: code,
createdBy: "Avanzato",
});
console.log(`[Seed] Formulario criado com codigo: ${code}`);
// Inserir secoes
for (const s of sections) {
await db.insert(formSections).values({
formId: FORM_ID,
title: s.title,
description: s.description,
});
}
console.log(`[Seed] ${sections.length} secoes inseridas`);
// Inserir perguntas
let count = 0;
for (const q of questions) {
await db.insert(formQuestions).values({
formId: FORM_ID,
sectionId: q.sectionId,
question: q.q,
fieldType: q.type,
options: q.options || null,
required: q.required,
});
count++;
}
console.log(`[Seed] ${count} perguntas inseridas`);
console.log("[Seed] Concluido! Acesse o formulario em: /formulario/" + code);
}
seed().catch(console.error);
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{y as n,K as i,G as s,J as e,h as d,a as l,s as m,S as x}from"./index-DLMsdH5y.js";import{S as h}from"./SEO-BQEmBC0N.js";import{F as g}from"./file-text-etdcuywr.js";const u=[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",key:"1gvzjb"}],["path",{d:"M9 18h6",key:"x1upvd"}],["path",{d:"M10 22h4",key:"ceow96"}]],p=n("lightbulb",u);s.registerPlugin(x);const b=[{titulo:"Diagnóstico",descricao:"Análise completa da sua TI atual.",icon:m},{titulo:"Planejamento",descricao:"Roadmap estratégico tecnológico.",icon:p},{titulo:"Auditoria",descricao:"Avaliação de processos e infraestrutura.",icon:g}],v=()=>{const c=i.useRef(null),r=i.useRef(null);return i.useEffect(()=>{const t=s.context(()=>{const a=c.current?.querySelectorAll(".animate-item");a?.length&&s.fromTo(a,{y:50,opacity:0},{y:0,opacity:1,duration:.8,stagger:.1,ease:"expo.out"});const o=r.current?.querySelectorAll(".servico-card");o?.length&&s.fromTo(o,{y:50,opacity:0},{y:0,opacity:1,duration:.6,stagger:.1,ease:"back.out(1.7)",scrollTrigger:{trigger:r.current,start:"top 80%"}})});return()=>t.revert()},[]),e.jsxs("div",{className:"bg-[#0e0e0e] min-h-screen",children:[e.jsx(h,{title:"Consultoria em TI",description:"Consultoria estrategica em tecnologia: planejamento, gestao de projetos, outsourcing de TI. Diagnostico gratuito."}),e.jsxs("section",{ref:c,className:"pt-32 pb-20 relative overflow-hidden",children:[e.jsx("div",{className:"absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2"}),e.jsx("div",{className:"container-custom relative z-10",children:e.jsxs("div",{className:"grid lg:grid-cols-2 gap-12 items-center",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"animate-item flex items-center gap-2 mb-6",children:[e.jsx(d,{className:"w-5 h-5 text-[#cbf400]"}),e.jsx("span",{className:"text-[#cbf400] font-semibold",children:"Serviços"}),e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("span",{className:"text-white",children:"Consultoria"})]}),e.jsxs("h1",{className:"animate-item text-4xl md:text-5xl lg:text-6xl font-black text-white mb-6",children:["Consultoria em ",e.jsx("span",{className:"text-gradient",children:"TI"})]}),e.jsx("p",{className:"animate-item text-xl text-gray-400 mb-8 leading-relaxed",children:"Planejamento estratégico tecnológico."}),e.jsx("div",{className:"animate-item flex flex-wrap gap-4",children:e.jsxs("a",{href:"https://api.whatsapp.com/send?phone=551148101704&text=Olá!%20Gostaria%20de%20consultoria",target:"_blank",rel:"noopener noreferrer",className:"btn-primary inline-flex items-center gap-2",children:["Agendar reunião",e.jsx(l,{className:"w-5 h-5"})]})})]}),e.jsxs("div",{className:"animate-item relative",children:[e.jsx("div",{className:"absolute -inset-4 bg-gradient-to-r from-[#cbf400]/20 to-transparent rounded-2xl blur-2xl"}),e.jsx("img",{src:"/about-image.jpg",alt:"Consultoria",className:"relative w-full h-auto animate-float"})]})]})})]}),e.jsx("section",{ref:r,className:"py-20 bg-[#1a1a1a]",children:e.jsxs("div",{className:"container-custom",children:[e.jsx("div",{className:"text-center mb-16",children:e.jsx("h2",{className:"text-3xl md:text-4xl font-black text-white mb-4",children:"Nossos Serviços"})}),e.jsx("div",{className:"grid md:grid-cols-3 gap-6",children:b.map((t,a)=>e.jsxs("div",{className:"servico-card bg-[#0e0e0e] rounded-2xl p-8 border border-white/5 hover:border-[#cbf400]/30 transition-colors text-center",children:[e.jsx("div",{className:"w-16 h-16 rounded-xl bg-[#cbf400]/10 flex items-center justify-center mx-auto mb-6 text-[#cbf400]",children:e.jsx(t.icon,{className:"w-8 h-8"})}),e.jsx("h4",{className:"text-xl font-bold text-white mb-3",children:t.titulo}),e.jsx("p",{className:"text-gray-400",children:t.descricao})]},a))})]})}),e.jsx("section",{className:"py-20 bg-[#0e0e0e]",children:e.jsx("div",{className:"container-custom",children:e.jsxs("div",{className:"bg-gradient-to-r from-[#cbf400]/10 to-transparent rounded-2xl p-12 text-center border border-[#cbf400]/20",children:[e.jsx("h2",{className:"text-3xl md:text-4xl font-black text-white mb-4",children:"Transforme sua TI"}),e.jsx("p",{className:"text-gray-400 mb-8",children:"Agende uma reunião estratégica."}),e.jsxs("a",{href:"https://api.whatsapp.com/send?phone=551148101704&text=Olá!%20Gostaria%20de%20reunião%20consultoria",target:"_blank",rel:"noopener noreferrer",className:"btn-primary inline-flex items-center gap-2",children:["Agendar reunião",e.jsx(l,{className:"w-5 h-5"})]})]})})})]})};export{v as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{J as a,M as o}from"./index-DLMsdH5y.js";import{F as i,c as r}from"./dnsApi-B1ISl2yG.js";import{S as s}from"./SEO-BQEmBC0N.js";import{a as t,c}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const e=[{q:"O que e DKIM?",a:"DKIM e uma assinatura digital criptografica que garante que um e-mail nao foi alterado durante o transporte e realmente veio do servidor autorizado."},{q:"Como ativar DKIM no Microsoft 365?",a:"No centro de administracao do Microsoft 365, va em Politicas de e-mail > DKIM. Selecione seu dominio e ative as assinaturas. Os selectors serao selector1 e selector2."},{q:"DKIM e obrigatorio?",a:"Nao e obrigatorio, mas altamente recomendado. Gmail, Microsoft e outros provedores priorizam e-mails com DKIM autenticado."},{q:"Quais selectors a ferramenta verifica?",a:"Verificamos os selectors mais comuns: default, google, selector1, selector2, mail e dkim."}],m=t(e),n=c([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"DKIM Checker",url:"https://avanzato.com.br/ferramentas/dkim-checker"}]),d={"@context":"https://schema.org","@type":"SoftwareApplication",name:"DKIM Checker",description:"Ferramenta gratuita para verificar configuracao DKIM de dominios.",url:"https://avanzato.com.br/ferramentas/dkim-checker",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[m,n,d],g=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(s,{title:"DKIM Checker Gratuito | Avanzato Tools",description:"Valide a assinatura digital DKIM do seu dominio. Garanta que seus e-mails sejam autenticos. Ferramenta gratuita da Avanzato.",keywords:"dkim checker, verificar dkim, domainkeys, assinatura digital email, autenticacao email",schema:u}),a.jsx(i,{nome:"DKIM Checker Gratuito",descricao:"Valide a assinatura digital DKIM do seu dominio para garantir a autenticidade dos e-mails enviados.",icone:a.jsx(o,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"DKIM (DomainKeys Identified Mail) e uma assinatura digital que seu servidor de e-mail adiciona a cada mensagem enviada. O destinatario pode verificar essa assinatura consultando uma chave publica no DNS do seu dominio. Sem DKIM, provedores de e-mail como Gmail e Outlook tem dificuldade para confirmar que a mensagem realmente veio do seu servidor, o que aumenta a chance de seus e-mails irem para a pasta de spam.",comoFunciona:["Digite o dominio que deseja verificar no campo acima.","Nossa ferramenta testa os selectors DKIM mais comuns.","Consultamos os registros TXT nos servidores DNS publicos.","Analisamos a chave publica e configuracao do DKIM.","Voce recebe um score e recomendacoes personalizadas."],perguntasUteis:e.slice(0,4),onConsultar:r})]});export{g as default};
-1
View File
@@ -1 +0,0 @@
import{J as a}from"./index-DLMsdH5y.js";import{F as o,a as r}from"./dnsApi-B1ISl2yG.js";import{S as t}from"./SEO-BQEmBC0N.js";import{a as i,c as n}from"./schema-B9cmWQ6T.js";import{L as s}from"./lock-i-9JWRKi.js";import"./circle-check-big-BcmvPvF4.js";const e=[{q:"O que e DMARC?",a:"DMARC e um protocolo que instrui provedores de e-mail a rejeitar ou colocar em quarentena mensagens que nao passam na autenticacao SPF ou DKIM."},{q:"Qual politica DMARC devo usar?",a:"Comece com p=none para monitorar, depois mude para p=quarantine e finalmente p=reject para protecao maxima."},{q:"O que sao relatorios DMARC?",a:"Sao e-mails automaticos enviados por provedores (Google, Microsoft) informando quem esta tentando usar seu dominio para enviar e-mails."},{q:"DMARC sozinho protege contra phishing?",a:"Nao. DMARC funciona junto com SPF e DKIM. Os tres juntos formam a triade de seguranca de e-mail."}],c=i(e),m=n([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"DMARC Checker",url:"https://avanzato.com.br/ferramentas/dmarc-checker"}]),p={"@context":"https://schema.org","@type":"SoftwareApplication",name:"DMARC Checker",description:"Ferramenta gratuita para analisar politica DMARC de dominios.",url:"https://avanzato.com.br/ferramentas/dmarc-checker",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[c,m,p],A=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(t,{title:"DMARC Checker Gratuito | Avanzato Tools",description:"Analise a politica DMARC do seu dominio. Proteja contra phishing e spoofing. Ferramenta gratuita da Avanzato.",keywords:"dmarc checker, verificar dmarc, politica dmarc, protecao email, anti phishing",schema:u}),a.jsx(o,{nome:"DMARC Checker Gratuito",descricao:"Analise a politica DMARC do seu dominio e descubra se voce esta protegido contra phishing e spoofing.",icone:a.jsx(s,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"DMARC (Domain-based Message Authentication, Reporting, and Conformance) e um protocolo que instrui provedores de e-mail sobre como tratar mensagens que falham na autenticacao SPF ou DKIM. Com DMARC, voce pode instruir provedores a rejeitar e-mails falsos e ainda recebe relatorios sobre tentativas de uso nao autorizado do seu dominio.",comoFunciona:["Digite o dominio que deseja analisar no campo acima.","Consultamos o registro DMARC nos servidores DNS.","Analisamos a politica configurada (none, quarantine, reject).","Verificamos se voce esta recebendo relatorios (rua).","Receba recomendacoes para fortalecer sua protecao."],perguntasUteis:e.slice(0,4),onConsultar:r})]});export{A as default};
-1
View File
@@ -1 +0,0 @@
import{J as a}from"./index-DLMsdH5y.js";import{F as o,b as r}from"./dnsApi-B1ISl2yG.js";import{S as i}from"./SEO-BQEmBC0N.js";import{a as c,c as m}from"./schema-B9cmWQ6T.js";import{C as t}from"./chart-column-D1WP4JxN.js";import"./circle-check-big-BcmvPvF4.js";const e=[{q:"O que o Email Security Score avalia?",a:"Avaliamos 4 componentes: SPF (autorizacao de servidores), DKIM (assinatura digital), DMARC (politica de conformidade) e MX (infraestrutura de e-mail)."},{q:"Qual score minimo recomendado?",a:"Recomendamos score minimo de 80. Abaixo de 70, sua empresa esta vulneravel a spoofing e phishing."},{q:"Como melhorar meu score?",a:"Comece corrigindo os componentes com nota mais baixa. Configurar DMARC com p=reject e o impacto mais significativo."},{q:"O score e atualizado em tempo real?",a:"Sim. Cada consulta verifica o estado atual dos seus registros DNS publicos."}],s=c(e),n=m([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"Email Security Score",url:"https://avanzato.com.br/ferramentas/email-security-score"}]),l={"@context":"https://schema.org","@type":"SoftwareApplication",name:"Email Security Score",description:"Ferramenta gratuita para avaliar seguranca completa de e-mail de dominios.",url:"https://avanzato.com.br/ferramentas/email-security-score",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[s,n,l],v=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(i,{title:"Email Security Score | Avanzato Tools",description:"Avalie a seguranca de e-mail do seu dominio. Score de 0 a 100 com recomendacoes. SPF, DKIM, DMARC e MX. Gratuito.",keywords:"email security score, seguranca email, score email, avaliar email, protecao email",schema:u}),a.jsx(o,{nome:"Email Security Score",descricao:"Avalie a seguranca completa do e-mail do seu dominio com um score de 0 a 100 e recomendacoes detalhadas.",icone:a.jsx(t,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"Nossa ferramenta Email Security Score analisa todos os aspectos da seguranca de e-mail do seu dominio em uma unica consulta: SPF, DKIM, DMARC e registros MX. Cada componente e avaliado individualmente e voce recebe um score global de 0 a 100, com recomendacoes praticas e priorizadas para melhorar a seguranca.",comoFunciona:["Digite o dominio que deseja avaliar no campo acima.","Analisamos SPF, DKIM, DMARC e MX simultaneamente.","Cada componente recebe uma nota de 0 a 100.","Calculamos o score global de seguranca de e-mail.","Voce recebe um plano de acao priorizado para melhorias."],perguntasUteis:e.slice(0,4),onConsultar:r})]});export{v as default};
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{J as a,B as e}from"./index-DLMsdH5y.js";import{F as r,e as i}from"./dnsApi-B1ISl2yG.js";import{S as s}from"./SEO-BQEmBC0N.js";import{a as c,c as t}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const o=[{q:"A ferramenta funciona so para Microsoft 365?",a:"Sim, esta ferramenta e especifica para Microsoft 365. Para analise generica de e-mail, use nosso Email Security Score."},{q:"O que verifica no Microsoft 365?",a:"Verificamos: registros MX apontando para Microsoft, SPF com include:_spf.microsoft.com, DKIM ativado e DMARC configurado."},{q:"Como ativar DKIM no Microsoft 365?",a:"No Centro de Administracao > Seguranca > DKIM. Selecione o dominio e ative. Os selectors sao selector1 e selector2."},{q:"A Avanzato oferece suporte Microsoft 365?",a:"Sim. Somos especialistas em Microsoft 365 e oferecemos migracao, configuracao, suporte e seguranca."}],n=c(o),m=t([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"Microsoft 365 Analyzer",url:"https://avanzato.com.br/ferramentas/microsoft-365-analyzer"}]),f={"@context":"https://schema.org","@type":"SoftwareApplication",name:"Microsoft 365 Analyzer",description:"Ferramenta gratuita para analisar configuracao Microsoft 365 de dominios.",url:"https://avanzato.com.br/ferramentas/microsoft-365-analyzer",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},d=[n,m,f],h=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(s,{title:"Microsoft 365 Analyzer | Avanzato Tools",description:"Analise a configuracao do Microsoft 365 do seu dominio. Verifique seguranca e otimizacao. Ferramenta gratuita da Avanzato.",keywords:"microsoft 365 analyzer, verificar microsoft 365, configuracao m365, seguranca microsoft 365",schema:d}),a.jsx(r,{nome:"Microsoft 365 Analyzer",descricao:"Analise a configuracao do Microsoft 365 do seu dominio e descubra oportunidades de melhoria e seguranca.",icone:a.jsx(e,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"O Microsoft 365 e a suite de produtividade mais usada por empresas no mundo, mas muitas configuracoes de seguranca ficam desativadas por padrao. Nossa ferramenta analisa se seu dominio esta corretamente configurado com o Microsoft 365, verifica SPF, DKIM e DMARC especificos para o ambiente Microsoft, e identifica oportunidades de melhoria.",comoFunciona:["Digite o dominio configurado no Microsoft 365.","Verificamos se os registros MX apontam para a Microsoft.","Analisamos SPF, DKIM e DMARC especificos para Microsoft 365.","Identificamos configuracoes de seguranca recomendadas.","Receba um relatorio com acoes priorizadas."],perguntasUteis:o.slice(0,4),onConsultar:i})]});export{h as default};
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{J as e,u as o}from"./index-DLMsdH5y.js";import{F as r,d as s}from"./dnsApi-B1ISl2yG.js";import{S as i}from"./SEO-BQEmBC0N.js";import{a as t,c as n}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const a=[{q:"O que sao registros MX?",a:"Registros MX sao entradas DNS que indicam quais servidores de e-mail recebem mensagens enviadas ao seu dominio."},{q:"Quantos servidores MX devo ter?",a:"Recomendamos pelo menos 2 servidores MX com prioridades diferentes para redundancia. Se o primeiro falhar, o segundo assume."},{q:"Como migrar e-mail para Microsoft 365?",a:"Voce precisa alterar os registros MX para apontar para os servidores Microsoft. A Avanzato pode fazer essa migracao sem perda de e-mails."},{q:"MX afeta envio de e-mails?",a:"Nao. Registros MX controlam apenas o recebimento. Para envio, use SPF, DKIM e DMARC."}],m=t(a),c=n([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"MX Lookup",url:"https://avanzato.com.br/ferramentas/mx-lookup"}]),d={"@context":"https://schema.org","@type":"SoftwareApplication",name:"MX Lookup",description:"Ferramenta gratuita para consultar registros MX de dominios.",url:"https://avanzato.com.br/ferramentas/mx-lookup",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},p=[m,c,d],M=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[e.jsx(i,{title:"MX Lookup Gratuito | Avanzato Tools",description:"Consulte os registros MX do seu dominio. Verifique a infraestrutura de e-mail. Ferramenta gratuita da Avanzato.",keywords:"mx lookup, consultar mx, registros mx, servidor email, infraestrutura email",schema:p}),e.jsx(r,{nome:"MX Lookup Gratuito",descricao:"Consulte os registros MX do seu dominio e verifique a infraestrutura de recebimento de e-mails.",icone:e.jsx(o,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"Registros MX (Mail Exchange) definem quais servidores sao responsaveis por receber e-mails enviados ao seu dominio. Sem registros MX configurados corretamente, seu dominio nao pode receber e-mails. Empresas que migram para Microsoft 365 ou Google Workspace precisam atualizar seus registros MX para apontar para os servidores corretos.",comoFunciona:["Digite o dominio que deseja consultar no campo acima.","Nossa ferramenta consulta os registros MX nos servidores DNS.","Identificamos o provedor de e-mail (Microsoft 365, Google, etc).","Mostramos a prioridade de cada servidor MX.","Verificamos redundancia e configuracao."],perguntasUteis:a.slice(0,4),onConsultar:s})]});export{M as default};
-1
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{J as e,L as s,p as r,a as o,P as t}from"./index-DLMsdH5y.js";import{S as i}from"./SEO-BQEmBC0N.js";import{o as n,c}from"./schema-B9cmWQ6T.js";const d=c([{name:"Home",url:"https://avanzato.com.br/"},{name:"Onde Atuamos",url:"https://avanzato.com.br/onde-atuamos"}]),l=[n,d],m=[{cidade:"Guarulhos",slug:"guarulhos",regiao:"Grande SP",desc:"Matriz da Avanzato. Atendimento completo com equipe local."},{cidade:"Mogi das Cruzes",slug:"mogi-das-cruzes",regiao:"Alto Tiete",desc:"Suporte tecnico para industrias e comercios da regiao."},{cidade:"Ipiranga",slug:"ipiranga",regiao:"Zona Sul SP",desc:"Atendimento a clinicas, escolas e comercios do bairro."},{cidade:"Vila Formosa",slug:"vila-formosa",regiao:"Zona Leste SP",desc:"Suporte TI para industrias e logistica da regiao leste."},{cidade:"Cerqueira Cesar",slug:"cerqueira-cesar",regiao:"Zona Oeste SP",desc:"Atendimento a escritorios e empresas de servicos."},{cidade:"Santana",slug:"santana",regiao:"Zona Norte SP",desc:"Suporte para comercios e prestadores de servicos do norte."},{cidade:"Bras",slug:"bras",regiao:"Centro SP",desc:"Atendimento a lojas e empresas do polo comercial do Bras."},{cidade:"Ribeirao Preto",slug:"ribeirao-preto",regiao:"Interior SP",desc:"Suporte remoto e visitas periodicas para empresas do interior."}],g=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e]",children:[e.jsx(i,{title:"Onde Atuamos - Suporte de TI em Sao Paulo",description:"Avanzato Tecnologia atende em Guarulhos, Mogi das Cruzes, Ipiranga, Zona Leste, Zona Norte, Centro e Ribeirao Preto. Suporte presencial e remoto.",keywords:"suporte ti sao paulo, ti guarulhos, ti mogi das cruzes, terceirizacao ti sp",schema:l}),e.jsxs("div",{className:"max-w-7xl mx-auto px-6 py-16",children:[e.jsxs("nav",{className:"text-sm text-gray-400 mb-6",children:[e.jsx(s,{to:"/",className:"hover:text-[#cbf400]",children:"Home"}),e.jsx("span",{className:"mx-2",children:"/"}),e.jsx("span",{className:"text-white",children:"Onde Atuamos"})]}),e.jsxs("h1",{className:"text-4xl md:text-5xl font-bold text-white mb-4",children:["Onde a ",e.jsx("span",{className:"text-[#cbf400]",children:"Avanzato"})," Atua"]}),e.jsx("p",{className:"text-xl text-gray-300 mb-12 max-w-3xl",children:"Presente em toda a regiao metropolitana de Sao Paulo e interior. Atendimento presencial e remoto para empresas de todos os portes."}),e.jsx("div",{className:"grid md:grid-cols-2 lg:grid-cols-4 gap-6 mb-16",children:m.map(a=>e.jsxs(s,{to:`/suporte-ti-${a.slug}`,className:"bg-[#1a1a1a] rounded-2xl p-6 border border-[#2a2a2a] hover:border-[#cbf400]/50 transition-all group",children:[e.jsx(r,{className:"w-8 h-8 text-[#cbf400] mb-4"}),e.jsx("h3",{className:"text-xl font-semibold text-white mb-1 group-hover:text-[#cbf400] transition-colors",children:a.cidade}),e.jsx("p",{className:"text-sm text-gray-500 mb-2",children:a.regiao}),e.jsx("p",{className:"text-gray-400 text-sm",children:a.desc}),e.jsxs("div",{className:"mt-4 flex items-center gap-2 text-[#cbf400] text-sm font-medium",children:["Ver servicos ",e.jsx(o,{className:"w-4 h-4"})]})]},a.slug))}),e.jsxs("div",{className:"bg-gradient-to-r from-[#cbf400]/10 to-transparent rounded-2xl p-8 border border-[#cbf400]/30 text-center",children:[e.jsx("h2",{className:"text-3xl font-bold text-white mb-4",children:"Sua cidade nao esta na lista?"}),e.jsx("p",{className:"text-gray-300 mb-6 max-w-2xl mx-auto",children:"Atendemos toda a Grande Sao Paulo e interior com suporte remoto e visitas periodicas. Entre em contato para verificar disponibilidade na sua regiao."}),e.jsxs("div",{className:"flex flex-col sm:flex-row gap-4 justify-center",children:[e.jsxs("a",{href:"https://wa.me/551148101704",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-2 bg-[#cbf400] text-[#0e0e0e] px-8 py-4 rounded-xl font-semibold hover:bg-[#b8e000] transition-colors",children:[e.jsx(t,{className:"w-5 h-5"}),"Falar no WhatsApp"]}),e.jsx(s,{to:"/contato",className:"inline-flex items-center gap-2 border border-[#cbf400] text-[#cbf400] px-8 py-4 rounded-xl font-semibold hover:bg-[#cbf400]/10 transition-colors",children:"Entrar em Contato"})]})]})]})]});export{g as default};
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-77
View File
@@ -1,77 +0,0 @@
import{K as a,G as r,J as e,v as t,M as d}from"./index-DLMsdH5y.js";import{S as c}from"./SEO-BQEmBC0N.js";import{F as n}from"./file-text-etdcuywr.js";import{U as l}from"./user-D1fYDUoc.js";import{E as m}from"./eye-CsU633qe.js";import{L as u}from"./lock-i-9JWRKi.js";import{S as p}from"./share-2-B1apKnnQ.js";const x=[{icon:e.jsx(n,{className:"w-6 h-6"}),title:"1. Introdução",content:`
A Avanzato Tecnologia valoriza a privacidade de seus usuários e está comprometida em proteger
as informações pessoais coletadas. Esta Política de Privacidade descreve como coletamos,
usamos, armazenamos e protegemos seus dados.
Ao utilizar nossos serviços, você concorda com os termos desta política. Recomendamos a
leitura atenta deste documento.
`},{icon:e.jsx(l,{className:"w-6 h-6"}),title:"2. Dados Coletados",content:`
Podemos coletar os seguintes tipos de informações:
• Dados de identificação: nome, email, telefone, CNPJ
• Dados de navegação: endereço IP, cookies, páginas visitadas
• Dados de uso: informações sobre como você utiliza nossos serviços
• Dados técnicos: informações sobre seus dispositivos e sistemas
A coleta ocorre quando você:
- Preenche formulários em nosso site
- Entra em contato conosco
- Contrata nossos serviços
- Navega em nosso site
`},{icon:e.jsx(m,{className:"w-6 h-6"}),title:"3. Uso das Informações",content:`
Utilizamos seus dados para:
• Fornecer e melhorar nossos serviços
• Responder solicitações e dúvidas
• Enviar comunicações sobre produtos e serviços
• Cumprir obrigações legais e regulatórias
• Prevenir fraudes e garantir segurança
• Realizar análises e pesquisas de mercado
Não utilizamos seus dados para fins não relacionados aos nossos serviços
sem seu consentimento explícito.
`},{icon:e.jsx(u,{className:"w-6 h-6"}),title:"4. Proteção de Dados",content:`
Implementamos medidas de segurança técnicas e organizacionais para proteger
suas informações:
• Criptografia de dados em trânsito e em repouso
• Firewalls e sistemas de detecção de intrusão
• Controles de acesso rigorosos
• Treinamento de colaboradores em segurança
• Auditorias regulares de segurança
• Backups automatizados e seguros
Apesar de todos os esforços, nenhum sistema é 100% seguro. Em caso de
incidente de segurança, notificaremos você conforme exigido por lei.
`},{icon:e.jsx(p,{className:"w-6 h-6"}),title:"5. Compartilhamento de Dados",content:`
Podemos compartilhar seus dados com:
• Prestadores de serviços: empresas que nos auxiliam na operação
• Autoridades legais: quando exigido por lei ou ordem judicial
• Parceiros comerciais: com seu consentimento explícito
Não vendemos seus dados pessoais a terceiros. Qualquer compartilhamento
é feito com garantias de proteção e conformidade com a LGPD.
`},{icon:e.jsx(t,{className:"w-6 h-6"}),title:"6. Seus Direitos (LGPD)",content:`
De acordo com a Lei Geral de Proteção de Dados (LGPD), você tem direito a:
• Acessar seus dados pessoais
• Corrigir dados incompletos ou desatualizados
• Solicitar a exclusão de seus dados
• Revogar seu consentimento
• Solicitar a portabilidade dos dados
• Opor-se ao processamento de dados
• Solicitar informações sobre o compartilhamento
Para exercer seus direitos, entre em contato pelo email:
privacidade@avanzato.com.br
`},{icon:e.jsx(d,{className:"w-6 h-6"}),title:"7. Contato",content:`
Para questões relacionadas à privacidade e proteção de dados:
Email: privacidade@avanzato.com.br
Telefone: (11) 4810-1704
Endereço: Av. Dr. Carlos de Campos, 43 - Guarulhos/SP
Nosso Encarregado de Proteção de Dados (DPO) está disponível para
atender suas solicitações.
`}],j=()=>{const s=a.useRef(null);return a.useEffect(()=>{s.current&&r.fromTo(s.current.querySelectorAll(".section-item"),{y:30,opacity:0},{y:0,opacity:1,duration:.5,stagger:.1,ease:"expo.out"})},[]),e.jsxs("div",{className:"bg-[#0e0e0e] min-h-screen",children:[e.jsx(c,{title:"Politica de Privacidade",description:"Politica de Privacidade da Avanzato Tecnologia. Conformidade com a LGPD. Protecao de dados pessoais.",noindex:!0}),e.jsxs("section",{className:"pt-32 pb-20 relative overflow-hidden",children:[e.jsx("div",{className:"absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2"}),e.jsx("div",{className:"container-custom relative z-10",children:e.jsxs("div",{className:"text-center max-w-3xl mx-auto",children:[e.jsx("span",{className:"inline-block px-4 py-2 rounded-full bg-[#cbf400]/10 text-[#cbf400] text-sm font-semibold mb-6 border border-[#cbf400]/20",children:"Legal"}),e.jsxs("h1",{className:"text-4xl md:text-5xl lg:text-6xl font-black text-white mb-6",children:["Política de ",e.jsx("span",{className:"text-gradient",children:"Privacidade"})]}),e.jsx("p",{className:"text-xl text-gray-400",children:"Última atualização: 15 de Janeiro de 2024"})]})})]}),e.jsx("section",{className:"py-20",children:e.jsx("div",{className:"container-custom",children:e.jsxs("div",{className:"max-w-4xl mx-auto",children:[e.jsx("div",{ref:s,className:"space-y-8",children:x.map((o,i)=>e.jsxs("div",{className:"section-item bg-[#1a1a1a] rounded-2xl p-8 border border-white/5",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[e.jsx("div",{className:"w-12 h-12 rounded-xl bg-[#cbf400]/10 flex items-center justify-center text-[#cbf400]",children:o.icon}),e.jsx("h2",{className:"text-xl font-bold text-white",children:o.title})]}),e.jsx("div",{className:"text-gray-400 whitespace-pre-line leading-relaxed",children:o.content})]},i))}),e.jsx("div",{className:"mt-12 p-6 bg-[#cbf400]/5 rounded-xl border border-[#cbf400]/20 text-center",children:e.jsx("p",{className:"text-gray-400",children:"Ao utilizar nossos serviços, você concorda com esta Política de Privacidade. Para mais informações, entre em contato conosco."})})]})})})]})};export{j as default};
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{V as j,J as t,m as g,E as u}from"./index-DLMsdH5y.js";const d="https://avanzato.com.br",y="Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca",a="Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7 em Guarulhos e regiao.",m="https://avanzato.com.br/og-image.jpg";function _({title:r,description:e,keywords:s,ogImage:c,ogType:p="website",noindex:l=!1,schema:i}){const x=j().pathname,n=`${d}${x}`,o=r?`${r} | Avanzato`:y;return t.jsxs(g,{children:[t.jsx("title",{children:o}),t.jsx("meta",{name:"title",content:o}),e&&t.jsx("meta",{name:"description",content:e}),!e&&t.jsx("meta",{name:"description",content:a}),s&&t.jsx("meta",{name:"keywords",content:s}),t.jsx("link",{rel:"canonical",href:n}),l||!u.allowIndexing?t.jsx("meta",{name:"robots",content:"noindex, nofollow"}):t.jsx("meta",{name:"robots",content:"index, follow"}),t.jsx("meta",{property:"og:type",content:p}),t.jsx("meta",{property:"og:url",content:n}),t.jsx("meta",{property:"og:title",content:o}),t.jsx("meta",{property:"og:description",content:e||a}),t.jsx("meta",{property:"og:image",content:c||m}),t.jsx("meta",{property:"og:locale",content:"pt_BR"}),t.jsx("meta",{property:"og:site_name",content:"Avanzato Tecnologia"}),t.jsx("meta",{property:"twitter:card",content:"summary_large_image"}),t.jsx("meta",{property:"twitter:url",content:n}),t.jsx("meta",{property:"twitter:title",content:o}),t.jsx("meta",{property:"twitter:description",content:e||a}),t.jsx("meta",{property:"twitter:image",content:c||m}),i&&t.jsx("script",{type:"application/ld+json",children:JSON.stringify(i)})]})}export{_ as S};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{J as e,v as s}from"./index-DLMsdH5y.js";import{F as t,f as n}from"./dnsApi-B1ISl2yG.js";import{S as c}from"./SEO-BQEmBC0N.js";import{a as m,c as u}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const a=[{q:"O que e SPF?",a:"SPF (Sender Policy Framework) e um registro DNS que define quais servidores de e-mail estao autorizados a enviar mensagens em nome do seu dominio. Ele ajuda a prevenir spoofing e phishing."},{q:"Por que o SPF e importante para empresas?",a:"Sem SPF, qualquer pessoa pode enviar e-mails falsos usando o nome do seu dominio. Isso facilita ataques de phishing que usam sua marca para enganar clientes e funcionarios."},{q:"Como configurar SPF no Microsoft 365?",a:"Adicione um registro TXT no DNS com: v=spf1 include:_spf.microsoft.com ~all. Depois de testar, mude ~all para -all para protecao total."},{q:"Qual a diferenca entre ~all e -all?",a:"~all (softfail) marca e-mails nao autorizados como suspeitos. -all (hardfail) instrui provedores a rejeitar e-mails de servidores nao listados."},{q:"Quantos includes posso ter no SPF?",a:"O SPF permite no maximo 10 lookups DNS. Mais que isso gera erro e o SPF nao funciona. Use mecanismos ip4/ip6 quando possivel para reduzir lookups."},{q:"O SPF sozinho e suficiente?",a:"Nao. SPF deve ser usado junto com DKIM e DMARC para protecao completa contra spoofing e phishing. Recomendamos usar nossa ferramenta Email Security Score para verificar tudo."}],p=m(a),l=u([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"SPF Checker",url:"https://avanzato.com.br/ferramentas/spf-checker"}]),d=S("SPF Checker","Ferramenta gratuita para verificar configuracao SPF de dominios.","https://avanzato.com.br/ferramentas/spf-checker"),f=[p,l,d],P=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[e.jsx(c,{title:"SPF Checker Gratuito | Avanzato Tools",description:"Verifique se o SPF do seu dominio esta configurado corretamente. Proteja contra spoofing e phishing. Ferramenta gratuita da Avanzato.",keywords:"spf checker, verificar spf, configurar spf, sender policy framework, seguranca email",schema:f}),e.jsx(t,{nome:"SPF Checker Gratuito",descricao:"Verifique se o dominio da sua empresa esta protegido contra falsificacao de e-mails.",icone:e.jsx(s,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"O SPF (Sender Policy Framework) e um registro DNS que lista os servidores autorizados a enviar e-mails em nome do seu dominio. Quando um e-mail chega ao destinatario, o servidor dele consulta o SPF do seu dominio para verificar se o remetente e legitimo. Sem SPF, qualquer servidor pode enviar e-mails como se fosse voce, facilitando phishing e spoofing. Empresas que usam Microsoft 365, Google Workspace ou qualquer servico de e-mail devem ter SPF configurado corretamente.",comoFunciona:["Digite o dominio que deseja verificar no campo acima.","Nossa ferramenta consulta os servidores DNS publicos em tempo real.","Analisamos o registro SPF, mecanismos e politica de falha.","Voce recebe um score de 0 a 100 com recomendacoes praticas.","Cadastre-se gratuitamente para receber um relatorio completo por e-mail."],perguntasUteis:a.slice(0,4),onConsultar:n})]});function S(o,r,i){return{"@context":"https://schema.org","@type":"SoftwareApplication",name:o,description:r,url:i,applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}}}export{P as default};
-1
View File
@@ -1 +0,0 @@
import{J as e}from"./index-DLMsdH5y.js";import{F as o,g as r}from"./dnsApi-B1ISl2yG.js";import{S as s}from"./SEO-BQEmBC0N.js";import{a as t,c as i}from"./schema-B9cmWQ6T.js";import{L as c}from"./lock-i-9JWRKi.js";import"./circle-check-big-BcmvPvF4.js";const a=[{q:"O que e SSL/TLS?",a:"SSL e um protocolo de criptografia que protege a comunicacao entre navegador e servidor. TLS e a versao mais moderna e segura."},{q:"Meu site precisa de SSL?",a:"Sim. Todo site precisa de SSL. O Google penaliza sites sem HTTPS e navegadores exibem aviso de Nao Seguro."},{q:"SSL e gratuito?",a:"Sim. Let's Encrypt oferece certificados SSL gratuitos com renovacao automatica."},{q:"O que acontece se o SSL expirar?",a:"Seu site ficara inacessivel ou mostrara aviso de seguranca. E crucial renovar antes da expiracao."}],n=t(a),m=i([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"SSL Checker",url:"https://avanzato.com.br/ferramentas/ssl-checker"}]),d={"@context":"https://schema.org","@type":"SoftwareApplication",name:"SSL Checker",description:"Ferramenta gratuita para verificar certificados SSL de sites.",url:"https://avanzato.com.br/ferramentas/ssl-checker",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[n,m,d],h=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[e.jsx(s,{title:"SSL Checker Gratuito | Avanzato Tools",description:"Verifique a validade do certificado SSL do seu site. Garanta seguranca e confianca. Ferramenta gratuita da Avanzato.",keywords:"ssl checker, verificar ssl, certificado ssl, seguranca site, https",schema:u}),e.jsx(o,{nome:"SSL Checker Gratuito",descricao:"Verifique a validade e configuracao do certificado SSL do seu site. Garanta seguranca para seus visitantes.",icone:e.jsx(c,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"Um certificado SSL criptografa a comunicacao entre o navegador do visitante e seu servidor web. Sem SSL, dados sensiveis como senhas, cartoes de credito e informacoes pessoais transitam em texto puro pela internet. Alem disso, o Google penaliza sites sem SSL nos resultados de busca e navegadores exibem aviso de Nao Seguro.",comoFunciona:["Digite o dominio do site que deseja verificar.","Nossa ferramenta consulta o banco de dados de certificados publicos.","Verificamos o emissor, validade e configuracao do certificado.","Calculamos quantos dias faltam para a expiracao.","Receba alertas e recomendacoes de seguranca."],perguntasUteis:a.slice(0,4),onConsultar:r})]});export{h as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-68
View File
@@ -1,68 +0,0 @@
import{y as i,K as a,G as t,J as e}from"./index-DLMsdH5y.js";import{S as c}from"./SEO-BQEmBC0N.js";import{F as d}from"./file-text-etdcuywr.js";import{C as n}from"./circle-check-big-BcmvPvF4.js";import{C as l}from"./circle-alert-CGKZzOIC.js";import{S as m}from"./scale-DoSnSg-M.js";import{R as p}from"./refresh-cw-Cbsf85Yz.js";const u=[["path",{d:"m14 13-8.381 8.38a1 1 0 0 1-3.001-3l8.384-8.381",key:"pgg06f"}],["path",{d:"m16 16 6-6",key:"vzrcl6"}],["path",{d:"m21.5 10.5-8-8",key:"a17d9x"}],["path",{d:"m8 8 6-6",key:"18bi4p"}],["path",{d:"m8.5 7.5 8 8",key:"1oyaui"}]],x=i("gavel",u),v=[{icon:e.jsx(d,{className:"w-6 h-6"}),title:"1. Aceitação dos Termos",content:`
Ao acessar e utilizar o site e os serviços da Avanzato Tecnologia, você concorda
em cumprir e estar vinculado a estes Termos de Uso. Se você não concordar com
qualquer parte destes termos, não deverá utilizar nossos serviços.
Reservamo-nos o direito de modificar estes termos a qualquer momento. As alterações
entrarão em vigor imediatamente após a publicação no site. O uso continuado dos
serviços após as modificações constitui aceitação dos novos termos.
`},{icon:e.jsx(n,{className:"w-6 h-6"}),title:"2. Descrição dos Serviços",content:`
A Avanzato Tecnologia oferece serviços de:
• TI Gerenciada (Managed IT Services)
• Cloud Computing e Migração para Nuvem
• Segurança da Informação
• Suporte Técnico
• Consultoria em TI
Os serviços são prestados conforme contrato específico firmado com cada cliente,
incluindo escopo, SLAs (Service Level Agreements) e condições comerciais.
`},{icon:e.jsx(l,{className:"w-6 h-6"}),title:"3. Obrigações do Cliente",content:`
O cliente se compromete a:
• Fornecer informações verdadeiras e atualizadas
• Manter a confidencialidade de credenciais de acesso
• Utilizar os serviços de forma ética e legal
• Notificar prontamente sobre qualquer problema ou incidente
• Efetuar pagamentos conforme acordo comercial
• Cooperar com a equipe técnica durante implementações e suporte
O cliente é responsável por manter backups de dados críticos, exceto quando
o serviço de backup estiver explicitamente contratado.
`},{icon:e.jsx(m,{className:"w-6 h-6"}),title:"4. Limitação de Responsabilidade",content:`
A Avanzato Tecnologia se esforça para garantir a qualidade e disponibilidade
dos serviços, porém:
• Não nos responsabilizamos por perdas de dados não cobertas por contrato de backup
• Não garantimos que o site estará livre de erros ou interrupções
• Não somos responsáveis por danos indiretos ou consequenciais
• Nossa responsabilidade está limitada ao valor pago pelos serviços no período
O cliente reconhece que a segurança da informação é responsabilidade compartilhada
e que deve adotar boas práticas de segurança.
`},{icon:e.jsx(x,{className:"w-6 h-6"}),title:"5. Propriedade Intelectual",content:`
Todo o conteúdo do site, incluindo textos, imagens, logotipos, software e
materiais, é propriedade da Avanzato Tecnologia ou de seus licenciadores e
está protegido por leis de direitos autorais.
É proibido:
• Copiar, reproduzir ou distribuir o conteúdo sem autorização
• Modificar ou criar trabalhos derivados
• Utilizar nossos logotipos sem permissão expressa
• Realizar engenharia reversa de nossos sistemas
O cliente mantém a propriedade de seus dados e informações.
`},{icon:e.jsx(p,{className:"w-6 h-6"}),title:"6. Rescisão",content:`
O contrato de prestação de serviços pode ser rescindido:
• Pelo cliente: mediante aviso prévio conforme cláusula contratual
• Pela Avanzato: em caso de violação dos termos ou inadimplência
• Por comum acordo: a qualquer momento
Em caso de rescisão:
• Os dados do cliente serão devolvidos ou excluídos conforme solicitação
• O cliente deve quitar valores pendentes
• Cláusulas de confidencialidade permanecem em vigor
O prazo para devolução de dados é de até 30 dias após a rescisão.
`}],z=()=>{const o=a.useRef(null);return a.useEffect(()=>{o.current&&t.fromTo(o.current.querySelectorAll(".section-item"),{y:30,opacity:0},{y:0,opacity:1,duration:.5,stagger:.1,ease:"expo.out"})},[]),e.jsxs("div",{className:"bg-[#0e0e0e] min-h-screen",children:[e.jsx(c,{title:"Termos de Uso",description:"Termos de Uso do site da Avanzato Tecnologia. Condicoes de acesso e utilizacao dos servicos.",noindex:!0}),e.jsxs("section",{className:"pt-32 pb-20 relative overflow-hidden",children:[e.jsx("div",{className:"absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2"}),e.jsx("div",{className:"container-custom relative z-10",children:e.jsxs("div",{className:"text-center max-w-3xl mx-auto",children:[e.jsx("span",{className:"inline-block px-4 py-2 rounded-full bg-[#cbf400]/10 text-[#cbf400] text-sm font-semibold mb-6 border border-[#cbf400]/20",children:"Legal"}),e.jsxs("h1",{className:"text-4xl md:text-5xl lg:text-6xl font-black text-white mb-6",children:["Termos de ",e.jsx("span",{className:"text-gradient",children:"Uso"})]}),e.jsx("p",{className:"text-xl text-gray-400",children:"Última atualização: 15 de Janeiro de 2024"})]})})]}),e.jsx("section",{className:"py-20",children:e.jsx("div",{className:"container-custom",children:e.jsxs("div",{className:"max-w-4xl mx-auto",children:[e.jsx("div",{ref:o,className:"space-y-8",children:v.map((s,r)=>e.jsxs("div",{className:"section-item bg-[#1a1a1a] rounded-2xl p-8 border border-white/5",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[e.jsx("div",{className:"w-12 h-12 rounded-xl bg-[#cbf400]/10 flex items-center justify-center text-[#cbf400]",children:s.icon}),e.jsx("h2",{className:"text-xl font-bold text-white",children:s.title})]}),e.jsx("div",{className:"text-gray-400 whitespace-pre-line leading-relaxed",children:s.content})]},r))}),e.jsxs("div",{className:"mt-12 space-y-6",children:[e.jsxs("div",{className:"p-6 bg-[#1a1a1a] rounded-xl border border-white/5",children:[e.jsx("h3",{className:"text-lg font-bold text-white mb-3",children:"Lei Aplicável"}),e.jsx("p",{className:"text-gray-400",children:"Estes termos são regidos pelas leis da República Federativa do Brasil. Qualquer disputa será resolvida nos tribunais de Guarulhos/SP."})]}),e.jsxs("div",{className:"p-6 bg-[#1a1a1a] rounded-xl border border-white/5",children:[e.jsx("h3",{className:"text-lg font-bold text-white mb-3",children:"Contato"}),e.jsxs("p",{className:"text-gray-400",children:["Para questões sobre estes termos, entre em contato:",e.jsx("br",{}),"Email: juridico@avanzato.com.br",e.jsx("br",{}),"Telefone: (11) 4810-1704"]})]})]}),e.jsx("div",{className:"mt-12 p-6 bg-[#cbf400]/5 rounded-xl border border-[#cbf400]/20 text-center",children:e.jsx("p",{className:"text-gray-400",children:"Ao utilizar nossos serviços, você confirma que leu, entendeu e concorda com estes Termos de Uso."})})]})})})]})};export{z as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{y as e}from"./index-DLMsdH5y.js";const t=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]],c=e("calendar",t);export{c as C};
-1
View File
@@ -1 +0,0 @@
import{y as a}from"./index-DLMsdH5y.js";const c=[["path",{d:"M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",key:"18u6gg"}],["circle",{cx:"12",cy:"13",r:"3",key:"1vg3eu"}]],o=a("camera",c);export{o as C};
-1
View File
@@ -1 +0,0 @@
import{y as t}from"./index-DLMsdH5y.js";const a=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]],c=t("chart-column",a);export{c as C};
-1
View File
@@ -1 +0,0 @@
import{y as o}from"./index-DLMsdH5y.js";const t=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],c=o("chevron-right",t);export{c as C};
-1
View File
@@ -1 +0,0 @@
import{y as e}from"./index-DLMsdH5y.js";const c=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],y=e("circle-alert",c);export{y as C};
-1
View File
@@ -1 +0,0 @@
import{y as c}from"./index-DLMsdH5y.js";const e=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],t=c("circle-check-big",e);export{t as C};
-1
View File
@@ -1 +0,0 @@
import{y as e}from"./index-DLMsdH5y.js";const a=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]],t=e("database",a);export{t as D};
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{y as c}from"./index-DLMsdH5y.js";const e=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],r=c("eye",e);export{r as E};
-1
View File
@@ -1 +0,0 @@
import{y as e}from"./index-DLMsdH5y.js";const a=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"m9 15 2 2 4-4",key:"1grp1n"}]],o=e("file-check",a);export{o as F};
-1
View File
@@ -1 +0,0 @@
import{y as e}from"./index-DLMsdH5y.js";const a=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]],o=e("file-text",a);export{o as F};
-1
View File
@@ -1 +0,0 @@
import{y as e}from"./index-DLMsdH5y.js";const o=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],a=e("globe",o);export{a as G};
File diff suppressed because one or more lines are too long
+246
View File
File diff suppressed because one or more lines are too long
-41
View File
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More