From 99530200b414fef63c74a23b6aecc0cd2278b48e Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 29 Nov 2025 12:22:56 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adicionar=20sistema=20de=20backup=20e?= =?UTF-8?q?=20badge=20edit=C3=A1vel=20na=20p=C3=A1gina=20inicial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/BACKUP_SYSTEM.md | 353 ++++++++++++++++++ docs/diario-de-bordo/resumo-25-27.md | 160 ++++++++ frontend/src/app/[locale]/page.tsx | 16 +- .../src/app/[locale]/privacidade/page.tsx | 38 +- frontend/src/app/[locale]/termos/page.tsx | 30 +- frontend/src/app/admin/configuracoes/page.tsx | 15 + frontend/src/app/admin/paginas/home/page.tsx | 74 +++- frontend/src/app/api/backup/download/route.ts | 70 ++++ frontend/src/app/api/backup/route.ts | 334 +++++++++++++++++ .../src/components/admin/BackupManager.tsx | 270 ++++++++++++++ frontend/src/locales/en.json | 64 ++++ frontend/src/locales/es.json | 64 ++++ frontend/src/locales/pt.json | 64 ++++ 13 files changed, 1511 insertions(+), 41 deletions(-) create mode 100644 docs/BACKUP_SYSTEM.md create mode 100644 frontend/src/app/api/backup/download/route.ts create mode 100644 frontend/src/app/api/backup/route.ts create mode 100644 frontend/src/components/admin/BackupManager.tsx diff --git a/docs/BACKUP_SYSTEM.md b/docs/BACKUP_SYSTEM.md new file mode 100644 index 0000000..616e7a6 --- /dev/null +++ b/docs/BACKUP_SYSTEM.md @@ -0,0 +1,353 @@ +# Backup & Restore System - Guia de Implementação + +## 📋 Visão Geral + +Este sistema permite criar, gerenciar e fazer download de backups completos do banco de dados PostgreSQL e arquivos MinIO através de uma interface web integrada ao painel administrativo. + +## 🎯 Características + +- ✅ Backup completo do PostgreSQL com `pg_dump` +- ✅ Backup dos dados do MinIO +- ✅ Compactação automática em `tar.gz` +- ✅ Interface intuitiva no painel admin +- ✅ Download de backups +- ✅ Remoção de backups antigos +- ✅ Histórico com datas e tamanhos +- ✅ Funcionamento multi-ambiente (Docker, Local, Dokploy) + +## 📁 Estrutura de Arquivos + +``` +frontend/ +├── src/ +│ ├── app/ +│ │ ├── api/ +│ │ │ └── backup/ +│ │ │ ├── route.ts # POST/GET/DELETE - CRUD de backups +│ │ │ └── download/ +│ │ │ └── route.ts # GET - Download de backups +│ │ └── admin/ +│ │ └── configuracoes/ +│ │ └── page.tsx # Interface de configurações com backup +│ └── components/ +│ └── admin/ +│ └── BackupManager.tsx # Componente UI do gerenciador +└── .backups/ # Diretório onde os backups são salvos +``` + +## 🔧 Variáveis de Ambiente Necessárias + +Adicione ao seu `.env` ou ao `docker-compose.yml`: + +```env +# PostgreSQL +POSTGRES_USER=admin +POSTGRES_PASSWORD=adminpassword +POSTGRES_DB=occto_db +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 + +# MinIO +MINIO_ENDPOINT=minio +MINIO_PORT=9000 +MINIO_ACCESS_KEY=admin +MINIO_SECRET_KEY=adminpassword +MINIO_BUCKET_NAME=occto-images +MINIO_USE_SSL=false +``` + +## 🚀 Como Usar + +### 1. **No Painel Admin** + +1. Acesse `/admin/configuracoes` +2. Desça até a seção **"Backup & Restauração"** +3. Clique em **"Criar Backup Agora"** para iniciar um novo backup +4. Visualize o histórico de backups salvos +5. Download de um backup específico: clique no ícone de download +6. Remover um backup: clique no ícone de lixeira + +### 2. **Via API (Programaticamente)** + +```javascript +// Criar backup +const response = await fetch('/api/backup', { + method: 'POST', + headers: { + 'Authorization': 'Bearer seu_token_aqui', + 'Content-Type': 'application/json' + } +}); + +// Listar backups +const response = await fetch('/api/backup', { + headers: { + 'Authorization': 'Bearer seu_token_aqui' + } +}); + +// Download de backup +const response = await fetch('/api/backup/download?file=backup-2025-11-28.tar.gz', { + headers: { + 'Authorization': 'Bearer seu_token_aqui' + } +}); + +// Remover backup +const response = await fetch('/api/backup?id=backup-2025-11-28', { + method: 'DELETE', + headers: { + 'Authorization': 'Bearer seu_token_aqui' + } +}); +``` + +## 🏗️ Replicação em Outro Projeto Next.js + +Se você quer implementar este sistema em outro projeto Next.js: + +### Passo 1: Copiar os Arquivos + +```bash +# Copiar as rotas da API +cp -r frontend/src/app/api/backup seu_projeto/src/app/api/ + +# Copiar o componente UI +cp frontend/src/components/admin/BackupManager.tsx seu_projeto/src/components/admin/ +``` + +### Passo 2: Adicionar ao Docker Compose + +Se estiver usando Docker: + +```yaml +services: + postgres: + image: postgres:12-alpine + container_name: seu_postgres + environment: + POSTGRES_USER: admin + POSTGRES_PASSWORD: adminpassword + POSTGRES_DB: seu_db + volumes: + - postgres_data:/var/lib/postgresql/data + # Montar diretório de backups + - ./backups:/app/.backups + networks: + - seu_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U admin"] + interval: 10s + timeout: 5s + retries: 5 + + seu_frontend: + # ... suas configurações + volumes: + # Compartilhar diretório de backups + - ./backups:/app/.backups + depends_on: + postgres: + condition: service_healthy +``` + +### Passo 3: Integrar na Página de Configurações + +```tsx +// Sua página de configurações +import { BackupManager } from '@/components/admin/BackupManager'; + +export default function ConfiguracoesPage() { + return ( +
+ {/* Suas outras configurações */} + + {/* Adicionar seção de backup */} +
+

Backup & Restauração

+ +
+
+ ); +} +``` + +### Passo 4: Autenticação + +O sistema espera um token no header `Authorization: Bearer token`. + +Você pode: + +**Opção A:** Usar o token do usuário logado + +```typescript +// No componente BackupManager +const token = localStorage.getItem('auth_token'); +``` + +**Opção B:** Criar um middleware de autenticação + +```typescript +// api/backup/route.ts +function authenticateRequest(request: NextRequest): boolean { + const token = request.headers.get('authorization'); + + // Implementar sua lógica de autenticação + return validateToken(token); +} + +export async function POST(request: NextRequest) { + if (!authenticateRequest(request)) { + return NextResponse.json({ error: 'Não autorizado' }, { status: 401 }); + } + // ... resto do código +} +``` + +**Opção C:** Usar autenticação de sessão (exemplo com NextAuth) + +```typescript +import { getServerSession } from 'next-auth/next'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; + +export async function POST(request: NextRequest) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ error: 'Não autorizado' }, { status: 401 }); + } + // ... resto do código +} +``` + +## 📊 Estrutura do Backup + +Um backup `.tar.gz` contém: + +``` +backup-2025-11-28/ +├── database.sql # Dump completo do PostgreSQL +├── minio-data/ # Cópia de todos os arquivos do MinIO +│ └── [arquivos...] +└── metadata.json # Informações do backup + { + "timestamp": "2025-11-28T10:30:45.123Z", + "database": "occto_db", + "hostname": "postgres", + "minioEndpoint": "minio", + "version": "1.0" + } +``` + +## 🔐 Segurança + +### Recomendações: + +1. **Autenticação** + - Sempre valide a autenticação nas rotas de backup + - Use tokens JWT ou sessões seguras + +2. **Autorização** + - Apenas administradores devem ter acesso + - Adicione verificação de roles/permissões + +3. **Armazenamento de Backups** + - Considere armazenar em local protegido + - Implemente limpeza automática de backups antigos + - Criptografe backups sensíveis + +4. **Path Traversal** + - O código já implementa validação com `path.resolve()` + - Nunca permita caminhos arbitrários + +### Exemplo de Middleware de Autorização: + +```typescript +import { NextRequest, NextResponse } from 'next/server'; + +function isAdmin(request: NextRequest): boolean { + const token = request.headers.get('authorization'); + // Validar se o token corresponde a um admin + return validateAdminToken(token); +} + +export async function POST(request: NextRequest) { + if (!isAdmin(request)) { + return NextResponse.json( + { error: 'Apenas administradores podem criar backups' }, + { status: 403 } + ); + } + // ... resto do código +} +``` + +## 🐛 Troubleshooting + +### Erro: "pg_dump: comando não encontrado" + +**Solução:** Certifique-se de que PostgreSQL Tools está instalado no container ou host. + +```dockerfile +FROM node:18-alpine + +# Adicionar PostgreSQL client +RUN apk add --no-cache postgresql-client + +WORKDIR /app +# ... resto do Dockerfile +``` + +### Erro: "MinIO data não encontrado" + +**Solução:** Verifique o caminho do volume do MinIO no docker-compose. + +```yaml +volumes: + - ./minio_data:/data # Caminho correto no container + - ./backups:/app/.backups +``` + +### Erro 401 "Não autorizado" + +**Solução:** Verifique se o token está sendo enviado corretamente. + +```javascript +// Sempre enviar token +const token = localStorage.getItem('auth_token'); +if (!token) { + console.error('Token não encontrado'); + return; +} + +fetch('/api/backup', { + headers: { + 'Authorization': `Bearer ${token}` + } +}); +``` + +## 📈 Melhorias Futuras + +- [ ] Agendamento automático de backups (cron) +- [ ] Envio automático para S3/Cloud Storage +- [ ] Compressão em background +- [ ] Restauração de backups via interface +- [ ] Notificações via email +- [ ] Verificação de integridade de backup +- [ ] Versionamento de backups +- [ ] Limpeza automática de backups antigos + +## 📞 Suporte + +Para mais informações ou dúvidas sobre implementação, consulte a documentação oficial: + +- [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) +- [PostgreSQL pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) +- [MinIO Client](https://docs.min.io/minio/baremetal/) + +--- + +**Versão:** 1.0 +**Data:** Novembro 2025 +**Compatibilidade:** Next.js 13.4+ com App Router diff --git a/docs/diario-de-bordo/resumo-25-27.md b/docs/diario-de-bordo/resumo-25-27.md index e9e82b4..32710f9 100644 --- a/docs/diario-de-bordo/resumo-25-27.md +++ b/docs/diario-de-bordo/resumo-25-27.md @@ -166,3 +166,163 @@ frontend/src/app/api/projects/[id]/ **Branch**: `cms-1.1` **Status**: ✅ Produção + +--- + +## CMS 1.2 - Atualizações (28/11/2025) + +### 📱 WhatsApp Dinâmico + +#### API de Informações de Contato (`/api/contact-info`) +- Nova rota que busca número do WhatsApp dinamicamente do CMS +- Busca dados da página `contato` slug +- Cache de 1 minuto para otimizar performance +- Fallback para número padrão `(35) 9882-9445` com link `https://wa.me/5535988229445` +- Retorna JSON: `{ whatsapp: string, whatsappLink: string }` + +#### Integração no Botão Flutuante +- `WhatsAppButton.tsx` agora busca número da API `/api/contact-info` +- Abre WhatsApp diretamente com número do CMS +- Exibe label traduzido `whatsapp.label` + +#### Integração no Header +- Botão "Fale Conosco" no header desktop agora abre WhatsApp diretamente +- Menu mobile também integrado +- Ambos buscam número da API em tempo real + +#### Tradução do Label +- Adicionada chave `whatsapp.label` em todos os locales: + - PT: "Fale Conosco" + - EN: "Contact Us" + - ES: "Contáctenos" +- Adicionada no `LanguageContext.tsx` para fallback + +--- + +### 🌙 Dark Mode no Painel Admin + +#### Novo Botão de Tema +- Adicionado botão sol/lua no header do painel admin +- Localizado ao lado das notificações +- Mesmo comportamento do botão no site público +- Integrado com `useTheme` hook + +#### Funcionalidade +- Toggle claro/escuro funcional em todo o admin +- Persistência de preferência via `next-themes` +- Ícone muda conforme tema: `ri-sun-line` (dark) / `ri-moon-line` (light) + +--- + +### 🔗 Correção de Links do Dashboard + +#### Links das Mensagens +- Card "Mensagens" → `/admin/mensagens` +- Botão "Ver todas" → `/admin/mensagens` +- Cada item de mensagem → `/admin/mensagens` +- Antes: apontavam para `/admin/contatos` (rota inexistente) + +#### Estrutura de Rotas +- Confirmado que rota correta é `/admin/mensagens` +- Não existe `/admin/contatos` no projeto + +--- + +### ✅ Correções Finais de WhatsApp + +#### Formato Correto do Número +- **Número fornecido**: `+55 35 9882-9445` +- **Formato wa.me**: `5535988229445` + - `55` = código Brasil + - `35` = DDD + - `988229445` = número com 9 dígitos (padrão celular BR) + +#### Atualização em Todos os Arquivos +- API `/api/contact-info/route.ts` +- Componente `WhatsAppButton.tsx` +- Componente `Header.tsx` +- Todos agora usam `5535988229445` como padrão + +--- + +### 📁 Arquivos Modificados (28/11) + +``` +frontend/src/app/api/ +└── contact-info/ + └── route.ts # ✨ NOVO - API WhatsApp dinâmico + +frontend/src/components/ +├── WhatsAppButton.tsx # Integração API contact-info +└── Header.tsx # Integração API contact-info + +frontend/src/app/admin/ +├── layout.tsx # Dark mode + links corrigidos +└── page.tsx # Links mensagens corrigidos + +frontend/src/contexts/ +└── LanguageContext.tsx # Adicionado whatsapp.label + +frontend/src/locales/ +├── pt.json # Adicionado whatsapp.label +├── en.json # Adicionado whatsapp.label +└── es.json # Adicionado whatsapp.label +``` + +--- + +## Commits Realizados (28/11/2025) + +### Commit 1: WhatsApp Dinâmico +``` +feat: WhatsApp dinâmico do CMS +- Criada API /api/contact-info que busca número do CMS +- Header e botão flutuante agora puxam número dinamicamente +- Número padrão: (35) 9882-9445 +``` + +### Commit 2: Tradução WhatsApp +``` +fix: WhatsApp label tradução e número correto (35) 9882-9445 +- Adicionada chave whatsapp.label nos arquivos de locale (pt, en, es) +- Adicionada chave whatsapp.label no LanguageContext +``` + +### Commit 3: Formato Correto +``` +fix: número WhatsApp correto 5535988229445 +- Corrigido número padrão em todos os arquivos +- Formato correto: 55 (Brasil) + 35 (DDD) + 988229445 (número) +``` + +### Commit 4: Dark Mode e Links +``` +fix: dark mode no admin, links mensagens dashboard, WhatsApp correto +- Adicionado botão de dark mode no header do painel admin +- Corrigido links do dashboard: /admin/contatos -> /admin/mensagens +- Corrigido número WhatsApp: 5535988229445 (formato correto BR) +``` + +--- + +## 🚀 Status de Deployment + +### Ambiente de Produção +- **Domínio**: www.octtoengenharia.com.br +- **Docker Compose**: `docker-compose.yml` (production) +- **Banco**: PostgreSQL `occto_db` +- **Storage**: MinIO `occto_minio` +- **Frontend**: `occto_frontend` +- **Network**: `dokploy-network` + +### Infraestrutura +- **Versão PostgreSQL**: 12-alpine +- **Versão MinIO**: RELEASE.2023-09-04T19-57-37Z +- **Framework**: Next.js 15.1 +- **ORM**: Prisma +- **Deploy Platform**: Dokploy (com auto-deploy) + +--- + +**Branch**: `cms-1.1` +**Status**: ✅ Produção (Deploy 28/11/2025) diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx index 2a6adaa..384b9a7 100644 --- a/frontend/src/app/[locale]/page.tsx +++ b/frontend/src/app/[locale]/page.tsx @@ -54,7 +54,11 @@ export default function Home() { const hero = content?.hero || { title: 'Engenharia de Excelência para Seus Projetos', subtitle: 'Soluções completas em engenharia veicular, mecânica e segurança do trabalho com mais de 15 anos de experiência.', - buttonText: 'Conheça Nossos Serviços' + buttonText: 'Conheça Nossos Serviços', + badge: { + text: 'Coca-Cola', + show: true + } }; const features = content?.features || { @@ -163,10 +167,12 @@ export default function Home() {
-
- - {t('home.officialProvider')} Coca-Cola -
+ {hero.badge?.show && ( +
+ + {t('home.officialProvider')} {hero.badge.text} +
+ )}

{hero.title} diff --git a/frontend/src/app/[locale]/privacidade/page.tsx b/frontend/src/app/[locale]/privacidade/page.tsx index 69b840c..3476d0d 100644 --- a/frontend/src/app/[locale]/privacidade/page.tsx +++ b/frontend/src/app/[locale]/privacidade/page.tsx @@ -8,51 +8,51 @@ export default function PrivacyPolicy() { return (
-

{t('footer.privacyPolicy')}

+

{t('privacy.title')}

- A Octto Engenharia valoriza a privacidade de seus usuários e clientes. Esta Política de Privacidade descreve como coletamos, usamos e protegemos suas informações pessoais ao utilizar nosso site e serviços. + {t('privacy.intro')}

-

1. Coleta de Informações

+

{t('privacy.section1.title')}

- Coletamos informações que você nos fornece diretamente, como quando preenche nosso formulário de contato, solicita um orçamento ou se inscreve em nossa newsletter. As informações podem incluir nome, e-mail, telefone e detalhes sobre sua empresa ou projeto. + {t('privacy.section1.content')}

-

2. Uso das Informações

+

{t('privacy.section2.title')}

- Utilizamos as informações coletadas para: + {t('privacy.section2.intro')}

    -
  • Responder a suas consultas e solicitações de orçamento;
  • -
  • Fornecer informações sobre nossos serviços de engenharia e laudos técnicos;
  • -
  • Melhorar a experiência do usuário em nosso site;
  • -
  • Cumprir obrigações legais e regulatórias.
  • +
  • {t('privacy.section2.items.0')}
  • +
  • {t('privacy.section2.items.1')}
  • +
  • {t('privacy.section2.items.2')}
  • +
  • {t('privacy.section2.items.3')}
-

3. Proteção de Dados

+

{t('privacy.section3.title')}

- Adotamos medidas de segurança técnicas e organizacionais adequadas para proteger seus dados pessoais contra acesso não autorizado, alteração, divulgação ou destruição. + {t('privacy.section3.content')}

-

4. Compartilhamento de Informações

+

{t('privacy.section4.title')}

- Não vendemos, trocamos ou transferimos suas informações pessoais para terceiros, exceto quando necessário para a prestação de nossos serviços (ex: parceiros técnicos envolvidos em um projeto específico) ou quando exigido por lei. + {t('privacy.section4.content')}

-

5. Cookies

+

{t('privacy.section5.title')}

- Nosso site pode utilizar cookies para melhorar a navegação e entender como os visitantes interagem com nosso conteúdo. Você pode desativar os cookies nas configurações do seu navegador, se preferir. + {t('privacy.section5.content')}

-

6. Contato

+

{t('privacy.section6.title')}

- Se você tiver dúvidas sobre esta Política de Privacidade, entre em contato conosco através do e-mail: contato@octto.com.br. + {t('privacy.section6.content')}

- Última atualização: Novembro de 2025. + {t('privacy.lastUpdate')}

diff --git a/frontend/src/app/[locale]/termos/page.tsx b/frontend/src/app/[locale]/termos/page.tsx index 7a7c6b0..7425dbc 100644 --- a/frontend/src/app/[locale]/termos/page.tsx +++ b/frontend/src/app/[locale]/termos/page.tsx @@ -8,45 +8,45 @@ export default function TermsOfUse() { return (
-

{t('footer.termsOfUse')}

+

{t('terms.title')}

- Bem-vindo ao site da Octto Engenharia. Ao acessar e utilizar este site, você concorda em cumprir e estar vinculado aos seguintes Termos de Uso. Se você não concordar com qualquer parte destes termos, por favor, não utilize nosso site. + {t('terms.intro')}

-

1. Uso do Site

+

{t('terms.section1.title')}

- O conteúdo deste site é apenas para fins informativos gerais sobre nossos serviços de engenharia mecânica, laudos e projetos. Reservamo-nos o direito de alterar ou descontinuar qualquer aspecto do site a qualquer momento. + {t('terms.section1.content')}

-

2. Propriedade Intelectual

+

{t('terms.section2.title')}

- Todo o conteúdo presente neste site, incluindo textos, gráficos, logotipos, ícones, imagens e software, é propriedade da Octto Engenharia ou de seus fornecedores de conteúdo e é protegido pelas leis de direitos autorais do Brasil e internacionais. + {t('terms.section2.content')}

-

3. Limitação de Responsabilidade

+

{t('terms.section3.title')}

- A Octto Engenharia não se responsabiliza por quaisquer danos diretos, indiretos, incidentais ou consequenciais resultantes do uso ou da incapacidade de uso deste site ou de qualquer informação nele contida. As informações técnicas fornecidas no site não substituem a consulta profissional e a emissão de laudos técnicos específicos para cada caso. + {t('terms.section3.content')}

-

4. Links para Terceiros

+

{t('terms.section4.title')}

- Nosso site pode conter links para sites de terceiros. Estes links são fornecidos apenas para sua conveniência. A Octto Engenharia não tem controle sobre o conteúdo desses sites e não assume responsabilidade por eles. + {t('terms.section4.content')}

-

5. Alterações nos Termos

+

{t('terms.section5.title')}

- Podemos revisar estes Termos de Uso a qualquer momento. Ao utilizar este site, você concorda em ficar vinculado à versão atual desses Termos de Uso. + {t('terms.section5.content')}

-

6. Legislação Aplicável

+

{t('terms.section6.title')}

- Estes termos são regidos e interpretados de acordo com as leis da República Federativa do Brasil. Qualquer disputa relacionada a estes termos será submetida à jurisdição exclusiva dos tribunais competentes. + {t('terms.section6.content')}

- Última atualização: Novembro de 2025. + {t('terms.lastUpdate')}

diff --git a/frontend/src/app/admin/configuracoes/page.tsx b/frontend/src/app/admin/configuracoes/page.tsx index 95c96c3..af58695 100644 --- a/frontend/src/app/admin/configuracoes/page.tsx +++ b/frontend/src/app/admin/configuracoes/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useToast } from '@/contexts/ToastContext'; +import { BackupManager } from '@/components/admin/BackupManager'; const PRESET_COLORS = [ { name: 'Laranja (Padrão)', value: '#FF6B35', gradient: 'from-orange-500 to-orange-600' }, @@ -252,6 +253,20 @@ export default function ConfiguracoesPage() {

+ + {/* Backup Manager Section */} +
+
+
+ +
+
+

Backup & Restauração

+

Gerencie backups completos do seu banco de dados e arquivos

+
+
+ +
); } diff --git a/frontend/src/app/admin/paginas/home/page.tsx b/frontend/src/app/admin/paginas/home/page.tsx index 71564dc..dff35e1 100644 --- a/frontend/src/app/admin/paginas/home/page.tsx +++ b/frontend/src/app/admin/paginas/home/page.tsx @@ -250,6 +250,10 @@ interface HomeContent { subtitle: string; buttonText: string; imageUrl?: string; + badge?: { + text: string; + show: boolean; + }; }; features: { pretitle: string; @@ -307,7 +311,11 @@ export default function EditHomePage() { hero: { title: 'Engenharia de Ponta para Seus Projetos', subtitle: 'Soluções completas em engenharia veicular, mecânica e segurança do trabalho.', - buttonText: 'Conheça Nossos Serviços' + buttonText: 'Conheça Nossos Serviços', + badge: { + text: 'Coca-Cola', + show: true + } }, features: { pretitle: 'Diferenciais', @@ -371,7 +379,11 @@ export default function EditHomePage() { if (data.content) { // Mesclar com valores padrão para garantir que todas as propriedades existam setFormData(prevData => ({ - hero: data.content.hero || prevData.hero, + hero: { + ...prevData.hero, + ...data.content.hero, + badge: data.content.hero?.badge || prevData.hero.badge + }, features: data.content.features || prevData.features, services: data.content.services || prevData.services, about: data.content.about || prevData.about, @@ -602,6 +614,64 @@ export default function EditHomePage() { className="w-full px-4 py-3 bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" /> + +
+

+ + Badge (Crachá) no Banner +

+ +
+
+ setFormData({ + ...formData, + hero: { + ...formData.hero, + badge: { + ...(formData.hero.badge || { text: '', show: false }), + show: e.target.checked + } + } + })} + className="w-5 h-5 accent-primary cursor-pointer" + /> + +
+ + {formData.hero.badge?.show && ( +
+ + setFormData({ + ...formData, + hero: { + ...formData.hero, + badge: { + ...(formData.hero.badge || { text: '', show: false }), + text: e.target.value + } + } + })} + placeholder="Digite o nome da empresa ou parceiro" + maxLength={50} + className="w-full px-4 py-3 bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl text-gray-900 dark:text-white focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" + /> +

+ {formData.hero.badge?.text?.length || 0}/50 caracteres +

+
+ )} +
+
)} diff --git a/frontend/src/app/api/backup/download/route.ts b/frontend/src/app/api/backup/download/route.ts new file mode 100644 index 0000000..4a2d32b --- /dev/null +++ b/frontend/src/app/api/backup/download/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from 'next/server'; +import * as fs from 'fs'; +import * as path from 'path'; +import { createReadStream } from 'fs'; + +const BACKUP_DIR = path.join(process.cwd(), '.backups'); + +/** + * GET /api/backup/download?file=backup-filename.tar.gz + * Faz download de um backup específico + */ +export async function GET(request: NextRequest) { + try { + const authHeader = request.headers.get('authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json( + { success: false, error: 'Não autorizado' }, + { status: 401 } + ); + } + + const { searchParams } = new URL(request.url); + const filename = searchParams.get('file'); + + if (!filename) { + return NextResponse.json( + { success: false, error: 'Arquivo não especificado' }, + { status: 400 } + ); + } + + // Validar que o arquivo está dentro do diretório de backups (prevenir path traversal) + const backupPath = path.resolve(path.join(BACKUP_DIR, filename)); + const resolvedBackupDir = path.resolve(BACKUP_DIR); + + if (!backupPath.startsWith(resolvedBackupDir)) { + return NextResponse.json( + { success: false, error: 'Acesso negado' }, + { status: 403 } + ); + } + + if (!fs.existsSync(backupPath)) { + return NextResponse.json( + { success: false, error: 'Arquivo não encontrado' }, + { status: 404 } + ); + } + + const stat = fs.statSync(backupPath); + const fileStream = createReadStream(backupPath); + + return new NextResponse(fileStream as any, { + headers: { + 'Content-Type': 'application/gzip', + 'Content-Disposition': `attachment; filename="${filename}"`, + 'Content-Length': stat.size.toString(), + }, + }); + } catch (error) { + console.error('[BACKUP] Erro ao fazer download:', error); + return NextResponse.json( + { + success: false, + error: (error as Error).message + }, + { status: 500 } + ); + } +} diff --git a/frontend/src/app/api/backup/route.ts b/frontend/src/app/api/backup/route.ts new file mode 100644 index 0000000..602cbd3 --- /dev/null +++ b/frontend/src/app/api/backup/route.ts @@ -0,0 +1,334 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { createReadStream } from 'fs'; + +// Variáveis de ambiente +const POSTGRES_USER = process.env.POSTGRES_USER || 'admin'; +const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD || 'adminpassword'; +const POSTGRES_DB = process.env.POSTGRES_DB || 'occto_db'; +const POSTGRES_HOST = process.env.POSTGRES_HOST || 'postgres'; +const POSTGRES_PORT = process.env.POSTGRES_PORT || '5432'; + +const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'minio'; +const MINIO_PORT = process.env.MINIO_PORT || '9000'; +const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || 'admin'; +const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY || 'adminpassword'; +const MINIO_BUCKET_NAME = process.env.MINIO_BUCKET_NAME || 'occto-images'; +const MINIO_USE_SSL = process.env.MINIO_USE_SSL === 'true'; + +// Diretório para armazenar backups +const BACKUP_DIR = path.join(process.cwd(), '.backups'); + +// Criar diretório de backups se não existir +if (!fs.existsSync(BACKUP_DIR)) { + fs.mkdirSync(BACKUP_DIR, { recursive: true }); +} + +interface BackupInfo { + id: string; + timestamp: string; + date: string; + size: number; + filename: string; + status: 'success' | 'error'; + message: string; +} + +interface BackupResponse { + success: boolean; + message: string; + backup?: BackupInfo; + error?: string; +} + +/** + * POST /api/backup + * Cria um backup completo do PostgreSQL e MinIO + */ +export async function POST(request: NextRequest) { + try { + // Validar autenticação (você pode adicionar verificação de token) + const authHeader = request.headers.get('authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json( + { success: false, error: 'Não autorizado' }, + { status: 401 } + ); + } + + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('-').slice(0, 4).join('-'); + const backupId = `backup-${timestamp}`; + const backupPath = path.join(BACKUP_DIR, backupId); + + // Criar pasta do backup + fs.mkdirSync(backupPath, { recursive: true }); + + const backupInfo: BackupInfo = { + id: backupId, + timestamp: new Date().toISOString(), + date: new Date().toLocaleDateString('pt-BR'), + size: 0, + filename: backupId, + status: 'success', + message: '' + }; + + // 1. Fazer backup do PostgreSQL + try { + console.log('[BACKUP] Iniciando backup do PostgreSQL...'); + const pgBackupPath = path.join(backupPath, 'database.sql'); + + const pgCommand = `PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump -h ${POSTGRES_HOST} -U ${POSTGRES_USER} -d ${POSTGRES_DB} > "${pgBackupPath}"`; + execSync(pgCommand, { stdio: 'pipe', env: { ...process.env, PGPASSWORD: POSTGRES_PASSWORD } }); + + console.log('[BACKUP] PostgreSQL backup concluído'); + } catch (error) { + console.error('[BACKUP] Erro ao fazer backup do PostgreSQL:', error); + backupInfo.status = 'error'; + backupInfo.message += `Erro PostgreSQL: ${(error as Error).message}. `; + } + + // 2. Fazer backup do MinIO (copiar os dados) + try { + console.log('[BACKUP] Iniciando backup do MinIO...'); + const minioBackupPath = path.join(backupPath, 'minio-data'); + + // Se estiver usando Docker, copiar do volume + // Se estiver local, copiar do diretório minio_data + const minioDataPath = path.join(process.cwd(), '..', 'minio_data'); + + if (fs.existsSync(minioDataPath)) { + // Copiar recursivamente + copyDirSync(minioDataPath, minioBackupPath); + console.log('[BACKUP] MinIO backup concluído'); + } else { + console.warn('[BACKUP] Diretório MinIO não encontrado em:', minioDataPath); + backupInfo.message += `Aviso: MinIO local não encontrado. `; + } + } catch (error) { + console.error('[BACKUP] Erro ao fazer backup do MinIO:', error); + backupInfo.message += `Erro MinIO: ${(error as Error).message}. `; + } + + // 3. Criar arquivo metadata.json + const metadataPath = path.join(backupPath, 'metadata.json'); + fs.writeFileSync(metadataPath, JSON.stringify({ + timestamp: backupInfo.timestamp, + database: POSTGRES_DB, + hostname: POSTGRES_HOST, + minioEndpoint: MINIO_ENDPOINT, + version: '1.0' + }, null, 2)); + + // 4. Calcular tamanho do backup + const size = calculateDirSize(backupPath); + backupInfo.size = size; + + // 5. Compactar backup (opcional - melhor para armazenamento) + const compressBackup = true; + if (compressBackup) { + try { + console.log('[BACKUP] Compactando backup...'); + const tarCommand = `tar -czf "${path.join(BACKUP_DIR, backupId)}.tar.gz" -C "${BACKUP_DIR}" "${backupId}"`; + execSync(tarCommand, { stdio: 'pipe' }); + + // Remover pasta original após compactar + fs.rmSync(backupPath, { recursive: true, force: true }); + + backupInfo.filename = `${backupId}.tar.gz`; + console.log('[BACKUP] Compactação concluída'); + } catch (error) { + console.warn('[BACKUP] Erro ao compactar:', error); + backupInfo.message += `Aviso: Não foi possível compactar. `; + } + } + + if (backupInfo.status === 'error' && backupInfo.message) { + return NextResponse.json( + { + success: false, + message: 'Backup concluído com erros', + backup: backupInfo, + error: backupInfo.message + }, + { status: 207 } // Multi-status + ); + } + + return NextResponse.json( + { + success: true, + message: 'Backup realizado com sucesso', + backup: backupInfo + }, + { status: 201 } + ); + } catch (error) { + console.error('[BACKUP] Erro geral:', error); + return NextResponse.json( + { + success: false, + message: 'Erro ao criar backup', + error: (error as Error).message + }, + { status: 500 } + ); + } +} + +/** + * GET /api/backup + * Lista os backups disponíveis + */ +export async function GET(request: NextRequest) { + try { + const authHeader = request.headers.get('authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json( + { success: false, error: 'Não autorizado' }, + { status: 401 } + ); + } + + const files = fs.readdirSync(BACKUP_DIR); + const backups: BackupInfo[] = []; + + for (const file of files) { + const filePath = path.join(BACKUP_DIR, file); + const stat = fs.statSync(filePath); + + if (stat.isFile()) { + const timestamp = file.replace('backup-', '').replace('.tar.gz', ''); + backups.push({ + id: file.replace('.tar.gz', ''), + timestamp: new Date(timestamp.replace(/-/g, ':')).toISOString(), + date: new Date(timestamp.replace(/-/g, ':')).toLocaleDateString('pt-BR'), + size: stat.size, + filename: file, + status: 'success', + message: 'Backup disponível' + }); + } + } + + // Ordenar por data (mais recente primeiro) + backups.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + + return NextResponse.json({ + success: true, + backups, + count: backups.length + }); + } catch (error) { + console.error('[BACKUP] Erro ao listar backups:', error); + return NextResponse.json( + { + success: false, + error: (error as Error).message + }, + { status: 500 } + ); + } +} + +/** + * DELETE /api/backup?id=backup-id + * Remove um backup específico + */ +export async function DELETE(request: NextRequest) { + try { + const authHeader = request.headers.get('authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json( + { success: false, error: 'Não autorizado' }, + { status: 401 } + ); + } + + const { searchParams } = new URL(request.url); + const backupId = searchParams.get('id'); + + if (!backupId) { + return NextResponse.json( + { success: false, error: 'ID do backup não fornecido' }, + { status: 400 } + ); + } + + const backupPath = path.join(BACKUP_DIR, `${backupId}.tar.gz`); + + if (!fs.existsSync(backupPath)) { + return NextResponse.json( + { success: false, error: 'Backup não encontrado' }, + { status: 404 } + ); + } + + fs.unlinkSync(backupPath); + + return NextResponse.json({ + success: true, + message: 'Backup removido com sucesso' + }); + } catch (error) { + console.error('[BACKUP] Erro ao deletar backup:', error); + return NextResponse.json( + { + success: false, + error: (error as Error).message + }, + { status: 500 } + ); + } +} + +/** + * Função auxiliar: copiar diretório recursivamente + */ +function copyDirSync(src: string, dest: string) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest, { recursive: true }); + } + + const files = fs.readdirSync(src); + + for (const file of files) { + const srcPath = path.join(src, file); + const destPath = path.join(dest, file); + const stat = fs.statSync(srcPath); + + if (stat.isDirectory()) { + copyDirSync(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +/** + * Função auxiliar: calcular tamanho do diretório + */ +function calculateDirSize(dirPath: string): number { + let size = 0; + + try { + const files = fs.readdirSync(dirPath); + + for (const file of files) { + const filePath = path.join(dirPath, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + size += calculateDirSize(filePath); + } else { + size += stat.size; + } + } + } catch (error) { + console.error('Erro ao calcular tamanho:', error); + } + + return size; +} diff --git a/frontend/src/components/admin/BackupManager.tsx b/frontend/src/components/admin/BackupManager.tsx new file mode 100644 index 0000000..8ffd4a0 --- /dev/null +++ b/frontend/src/components/admin/BackupManager.tsx @@ -0,0 +1,270 @@ +"use client"; + +import { useState, useEffect } from 'react'; +import { useToast } from '@/contexts/ToastContext'; + +interface BackupInfo { + id: string; + timestamp: string; + date: string; + size: number; + filename: string; + status: 'success' | 'error'; + message: string; +} + +interface BackupsListResponse { + success: boolean; + backups: BackupInfo[]; + count: number; +} + +export function BackupManager() { + const [loading, setLoading] = useState(false); + const [backups, setBackups] = useState([]); + const [listLoading, setListLoading] = useState(true); + const { success, error: showError } = useToast(); + + // Buscar lista de backups ao carregar + useEffect(() => { + fetchBackups(); + }, []); + + const fetchBackups = async () => { + try { + setListLoading(true); + const token = localStorage.getItem('auth_token') || ''; + + const response = await fetch('/api/backup', { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (response.ok) { + const data: BackupsListResponse = await response.json(); + setBackups(data.backups || []); + } else { + showError('Erro ao carregar backups'); + } + } catch (err) { + showError('Erro ao carregar backups: ' + (err as Error).message); + } finally { + setListLoading(false); + } + }; + + const createBackup = async () => { + try { + setLoading(true); + const token = localStorage.getItem('auth_token') || ''; + + const response = await fetch('/api/backup', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + + if (response.ok || response.status === 201) { + success('Backup criado com sucesso!'); + // Atualizar lista de backups + await new Promise(resolve => setTimeout(resolve, 1000)); + await fetchBackups(); + } else { + showError(data.error || 'Erro ao criar backup'); + } + } catch (err) { + showError('Erro ao criar backup: ' + (err as Error).message); + } finally { + setLoading(false); + } + }; + + const deleteBackup = async (backupId: string) => { + if (!confirm('Tem certeza que deseja remover este backup?')) { + return; + } + + try { + const token = localStorage.getItem('auth_token') || ''; + + const response = await fetch(`/api/backup?id=${backupId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + const data = await response.json(); + + if (response.ok) { + success('Backup removido com sucesso!'); + await fetchBackups(); + } else { + showError(data.error || 'Erro ao remover backup'); + } + } catch (err) { + showError('Erro ao remover backup: ' + (err as Error).message); + } + }; + + const downloadBackup = async (filename: string) => { + try { + const token = localStorage.getItem('auth_token') || ''; + + const response = await fetch(`/api/backup/download?file=${filename}`, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (response.ok) { + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } else { + showError('Erro ao baixar backup'); + } + } catch (err) { + showError('Erro ao baixar backup: ' + (err as Error).message); + } + }; + + const formatSize = (bytes: number) => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; + }; + + const formatDate = (isoString: string) => { + return new Date(isoString).toLocaleString('pt-BR'); + }; + + return ( +
+ {/* Botão de Criar Backup */} +
+
+
+

+ + Criar Novo Backup +

+

+ Realize um backup completo do banco de dados e arquivos MinIO +

+
+ +
+
+ + {/* Lista de Backups */} +
+

+ + Backups Salvos ({backups.length}) +

+ + {listLoading ? ( +
+ +

Carregando backups...

+
+ ) : backups.length === 0 ? ( +
+ +

Nenhum backup realizado ainda

+
+ ) : ( +
+ {backups.map((backup) => ( +
+
+
+
+

+ {backup.filename} +

+
+

+ + {formatDate(backup.timestamp)} + + + {formatSize(backup.size)} +

+ {backup.message && ( +

+ {backup.message} +

+ )} +
+ +
+ + +
+
+ ))} +
+ )} +
+ + {/* Informações */} +
+

+ + Os backups incluem banco de dados PostgreSQL e todos os arquivos do MinIO. + Armazene-os em local seguro para recuperação em caso de necessidade. +

+
+
+ ); +} diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index c4e8a67..ec2b48a 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -194,6 +194,70 @@ "accept": "Accept", "decline": "Decline" }, + "privacy": { + "title": "Privacy Policy", + "intro": "OCCTO Engineering values the privacy of its users and clients. This Privacy Policy describes how we collect, use and protect your personal information when using our website and services.", + "section1": { + "title": "1. Information Collection", + "content": "We collect information that you provide directly to us, such as when you fill out our contact form, request a quote or subscribe to our newsletter. The information may include name, email, phone and details about your company or project." + }, + "section2": { + "title": "2. Use of Information", + "intro": "We use the collected information to:", + "items": [ + "Respond to your inquiries and quote requests;", + "Provide information about our engineering services and technical reports;", + "Improve the user experience on our website;", + "Comply with legal and regulatory obligations." + ] + }, + "section3": { + "title": "3. Data Protection", + "content": "We adopt appropriate technical and organizational security measures to protect your personal information against unauthorized access, alteration, disclosure or destruction." + }, + "section4": { + "title": "4. Sharing Information", + "content": "We do not sell, trade or transfer your personal information to third parties, except when necessary for the provision of our services (eg: technical partners involved in a specific project) or when required by law." + }, + "section5": { + "title": "5. Cookies", + "content": "Our website may use cookies to improve navigation and understand how visitors interact with our content. You can disable cookies in your browser settings if you prefer." + }, + "section6": { + "title": "6. Contact", + "content": "If you have questions about this Privacy Policy, please contact us via email: contato@octto.com.br." + }, + "lastUpdate": "Last updated: November 2025." + }, + "terms": { + "title": "Terms of Use", + "intro": "Welcome to OCCTO Engineering's website. By accessing and using this website, you agree to comply with and be bound by the following Terms of Use. If you disagree with any part of these terms, please do not use our website.", + "section1": { + "title": "1. Website Use", + "content": "The content of this website is for general informational purposes only about our mechanical engineering services, reports and projects. We reserve the right to modify or discontinue any aspect of the website at any time." + }, + "section2": { + "title": "2. Intellectual Property", + "content": "All content on this website, including texts, graphics, logos, icons, images and software, is owned by OCCTO Engineering or its content providers and is protected by Brazilian and international copyright laws." + }, + "section3": { + "title": "3. Limitation of Liability", + "content": "OCCTO Engineering is not responsible for any direct, indirect, incidental or consequential damages resulting from the use or inability to use this website or any information contained therein. The technical information provided on the website does not substitute professional consultation and the issuance of specific technical reports for each case." + }, + "section4": { + "title": "4. Links to Third Parties", + "content": "Our website may contain links to third-party websites. These links are provided for your convenience only. OCCTO Engineering has no control over the content of these sites and assumes no responsibility for them." + }, + "section5": { + "title": "5. Changes to Terms", + "content": "We may revise these Terms of Use at any time. By using this website, you agree to be bound by the current version of these Terms of Use." + }, + "section6": { + "title": "6. Applicable Law", + "content": "These terms are governed by and interpreted in accordance with the laws of the Federative Republic of Brazil. Any dispute related to these terms will be submitted to the exclusive jurisdiction of the competent courts." + }, + "lastUpdate": "Last updated: November 2025." + }, "common": { "loading": "Loading...", "error": "Error", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 2d65076..d1599ec 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -194,6 +194,70 @@ "accept": "Aceptar", "decline": "Rechazar" }, + "privacy": { + "title": "Política de Privacidad", + "intro": "OCCTO Ingeniería valora la privacidad de sus usuarios y clientes. Esta Política de Privacidad describe cómo recopilamos, utilizamos y protegemos su información personal al utilizar nuestro sitio web y servicios.", + "section1": { + "title": "1. Recopilación de Información", + "content": "Recopilamos información que nos proporciona directamente, como cuando completa nuestro formulario de contacto, solicita un presupuesto o se suscribe a nuestro boletín informativo. La información puede incluir nombre, correo electrónico, teléfono y detalles sobre su empresa o proyecto." + }, + "section2": { + "title": "2. Uso de la Información", + "intro": "Utilizamos la información recopilada para:", + "items": [ + "Responder a sus consultas y solicitudes de presupuesto;", + "Proporcionar información sobre nuestros servicios de ingeniería e informes técnicos;", + "Mejorar la experiencia del usuario en nuestro sitio web;", + "Cumplir con obligaciones legales y normativas." + ] + }, + "section3": { + "title": "3. Protección de Datos", + "content": "Adoptamos medidas de seguridad técnicas y organizacionales apropiadas para proteger su información personal contra acceso no autorizado, alteración, divulgación o destrucción." + }, + "section4": { + "title": "4. Compartir Información", + "content": "No vendemos, canjeamos ni transferimos su información personal a terceros, excepto cuando sea necesario para la prestación de nuestros servicios (ej: socios técnicos involucrados en un proyecto específico) o cuando lo exija la ley." + }, + "section5": { + "title": "5. Cookies", + "content": "Nuestro sitio web puede utilizar cookies para mejorar la navegación y entender cómo los visitantes interactúan con nuestro contenido. Puede desactivar las cookies en la configuración de su navegador si lo prefiere." + }, + "section6": { + "title": "6. Contacto", + "content": "Si tiene preguntas sobre esta Política de Privacidad, comuníquese con nosotros a través de correo electrónico: contato@octto.com.br." + }, + "lastUpdate": "Última actualización: Noviembre de 2025." + }, + "terms": { + "title": "Términos de Uso", + "intro": "Bienvenido al sitio web de OCCTO Ingeniería. Al acceder y utilizar este sitio web, usted acepta cumplir y estar vinculado a los siguientes Términos de Uso. Si no está de acuerdo con ninguna parte de estos términos, por favor no utilice nuestro sitio web.", + "section1": { + "title": "1. Uso del Sitio", + "content": "El contenido de este sitio web es solo para propósitos informativos generales sobre nuestros servicios de ingeniería mecánica, informes y proyectos. Nos reservamos el derecho de alterar o descontinuar cualquier aspecto del sitio en cualquier momento." + }, + "section2": { + "title": "2. Propiedad Intelectual", + "content": "Todo el contenido presente en este sitio web, incluidos textos, gráficos, logotipos, iconos, imágenes y software, es propiedad de OCCTO Ingeniería o de sus proveedores de contenido y está protegido por las leyes de derechos de autor de Brasil e internacionales." + }, + "section3": { + "title": "3. Limitación de Responsabilidad", + "content": "OCCTO Ingeniería no se responsabiliza por ningún daño directo, indirecto, incidental o consecuente que resulte del uso o la incapacidad de usar este sitio web o cualquier información contenida en el mismo. La información técnica proporcionada en el sitio web no sustituye la consulta profesional y la emisión de informes técnicos específicos para cada caso." + }, + "section4": { + "title": "4. Enlaces a Terceros", + "content": "Nuestro sitio web puede contener enlaces a sitios web de terceros. Estos enlaces se proporcionan solo para su conveniencia. OCCTO Ingeniería no tiene control sobre el contenido de estos sitios y no asume responsabilidad por ellos." + }, + "section5": { + "title": "5. Cambios en los Términos", + "content": "Podemos revisar estos Términos de Uso en cualquier momento. Al utilizar este sitio web, usted acepta estar vinculado por la versión actual de estos Términos de Uso." + }, + "section6": { + "title": "6. Ley Aplicable", + "content": "Estos términos se rigen e interpretan de acuerdo con las leyes de la República Federativa de Brasil. Cualquier disputa relacionada con estos términos será sometida a la jurisdicción exclusiva de los tribunales competentes." + }, + "lastUpdate": "Última actualización: Noviembre de 2025." + }, "common": { "loading": "Cargando...", "error": "Error", diff --git a/frontend/src/locales/pt.json b/frontend/src/locales/pt.json index 3367c13..81bd6fd 100644 --- a/frontend/src/locales/pt.json +++ b/frontend/src/locales/pt.json @@ -194,6 +194,70 @@ "accept": "Aceitar", "decline": "Recusar" }, + "privacy": { + "title": "Política de Privacidade", + "intro": "A Octto Engenharia valoriza a privacidade de seus usuários e clientes. Esta Política de Privacidade descreve como coletamos, usamos e protegemos suas informações pessoais ao utilizar nosso site e serviços.", + "section1": { + "title": "1. Coleta de Informações", + "content": "Coletamos informações que você nos fornece diretamente, como quando preenche nosso formulário de contato, solicita um orçamento ou se inscreve em nossa newsletter. As informações podem incluir nome, e-mail, telefone e detalhes sobre sua empresa ou projeto." + }, + "section2": { + "title": "2. Uso das Informações", + "intro": "Utilizamos as informações coletadas para:", + "items": [ + "Responder a suas consultas e solicitações de orçamento;", + "Fornecer informações sobre nossos serviços de engenharia e laudos técnicos;", + "Melhorar a experiência do usuário em nosso site;", + "Cumprir obrigações legais e regulatórias." + ] + }, + "section3": { + "title": "3. Proteção de Dados", + "content": "Adotamos medidas de segurança técnicas e organizacionais adequadas para proteger seus dados pessoais contra acesso não autorizado, alteração, divulgação ou destruição." + }, + "section4": { + "title": "4. Compartilhamento de Informações", + "content": "Não vendemos, trocamos ou transferimos suas informações pessoais para terceiros, exceto quando necessário para a prestação de nossos serviços (ex: parceiros técnicos envolvidos em um projeto específico) ou quando exigido por lei." + }, + "section5": { + "title": "5. Cookies", + "content": "Nosso site pode utilizar cookies para melhorar a navegação e entender como os visitantes interagem com nosso conteúdo. Você pode desativar os cookies nas configurações do seu navegador, se preferir." + }, + "section6": { + "title": "6. Contato", + "content": "Se você tiver dúvidas sobre esta Política de Privacidade, entre em contato conosco através do e-mail: contato@octto.com.br." + }, + "lastUpdate": "Última atualização: Novembro de 2025." + }, + "terms": { + "title": "Termos de Uso", + "intro": "Bem-vindo ao site da Octto Engenharia. Ao acessar e utilizar este site, você concorda em cumprir e estar vinculado aos seguintes Termos de Uso. Se você não concordar com qualquer parte destes termos, por favor, não utilize nosso site.", + "section1": { + "title": "1. Uso do Site", + "content": "O conteúdo deste site é apenas para fins informativos gerais sobre nossos serviços de engenharia mecânica, laudos e projetos. Reservamo-nos o direito de alterar ou descontinuar qualquer aspecto do site a qualquer momento." + }, + "section2": { + "title": "2. Propriedade Intelectual", + "content": "Todo o conteúdo presente neste site, incluindo textos, gráficos, logotipos, ícones, imagens e software, é propriedade da Octto Engenharia ou de seus fornecedores de conteúdo e é protegido pelas leis de direitos autorais do Brasil e internacionais." + }, + "section3": { + "title": "3. Limitação de Responsabilidade", + "content": "A Octto Engenharia não se responsabiliza por quaisquer danos diretos, indiretos, incidentais ou consequenciais resultantes do uso ou da incapacidade de uso deste site ou de qualquer informação nele contida. As informações técnicas fornecidas no site não substituem a consulta profissional e a emissão de laudos técnicos específicos para cada caso." + }, + "section4": { + "title": "4. Links para Terceiros", + "content": "Nosso site pode conter links para sites de terceiros. Estes links são fornecidos apenas para sua conveniência. A Octto Engenharia não tem controle sobre o conteúdo desses sites e não assume responsabilidade por eles." + }, + "section5": { + "title": "5. Alterações nos Termos", + "content": "Podemos revisar estes Termos de Uso a qualquer momento. Ao utilizar este site, você concorda em ficar vinculado à versão atual desses Termos de Uso." + }, + "section6": { + "title": "6. Legislação Aplicável", + "content": "Estes termos são regidos e interpretados de acordo com as leis da República Federativa do Brasil. Qualquer disputa relacionada a estes termos será submetida à jurisdição exclusiva dos tribunais competentes." + }, + "lastUpdate": "Última atualização: Novembro de 2025." + }, "common": { "loading": "Carregando...", "error": "Erro",