Compare commits

...

10 Commits

47 changed files with 185 additions and 4015 deletions

View File

@@ -1,110 +0,0 @@
# Configuração de Secrets para GitHub Actions
Para que a automação funcione corretamente, você precisa configurar os seguintes secrets no seu repositório GitHub:
## Como Configurar Secrets
1. Vá para o seu repositório no GitHub
2. Clique em **Settings** (Configurações)
3. No menu lateral, clique em **Secrets and variables** > **Actions**
4. Clique em **New repository secret**
5. Adicione cada secret listado abaixo
## Secrets Necessários
### 🔑 REMOTE_REPO_TOKEN
**Descrição:** Token de acesso pessoal para o repositório remoto `https://github.com/Reifonas/TS_RDO.git`
**Como obter:**
1. Vá para GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)
2. Clique em "Generate new token (classic)"
3. Selecione as permissões:
- `repo` (acesso completo a repositórios)
- `workflow` (atualizar workflows)
- `write:packages` (se usar packages)
4. Copie o token gerado
5. Cole no secret `REMOTE_REPO_TOKEN`
### 🌐 NETLIFY_AUTH_TOKEN (Opcional)
**Descrição:** Token de autenticação do Netlify para deploy automático
**Como obter:**
1. Faça login no Netlify
2. Vá para User settings > Applications > Personal access tokens
3. Clique em "New access token"
4. Dê um nome e clique em "Generate token"
5. Copie o token e cole no secret `NETLIFY_AUTH_TOKEN`
### 🆔 NETLIFY_SITE_ID (Opcional)
**Descrição:** ID do site no Netlify
**Como obter:**
1. No dashboard do Netlify, clique no seu site
2. Vá para Site settings > General > Site details
3. Copie o "Site ID"
4. Cole no secret `NETLIFY_SITE_ID`
## Verificação da Configuração
Após configurar os secrets, você pode testar a automação:
1. **Teste Manual:**
- Vá para Actions no seu repositório
- Clique em "Auto Sync and Deploy"
- Clique em "Run workflow"
- Marque "Force deploy" se quiser forçar
2. **Teste Automático:**
- Faça qualquer alteração no código
- Commit e push para a branch main
- A action será executada automaticamente
## Estrutura dos Secrets
```
Repository Secrets:
├── REMOTE_REPO_TOKEN # Token para repositório remoto (OBRIGATÓRIO)
├── NETLIFY_AUTH_TOKEN # Token Netlify (opcional)
├── NETLIFY_SITE_ID # ID do site Netlify (opcional)
└── GITHUB_TOKEN # Automático (não precisa configurar)
```
## Troubleshooting
### ❌ Erro: "Authentication failed"
- Verifique se o `REMOTE_REPO_TOKEN` está correto
- Confirme se o token tem as permissões necessárias
- Verifique se o token não expirou
### ❌ Erro: "Repository not found"
- Confirme se o repositório `Reifonas/TS_RDO` existe
- Verifique se o token tem acesso ao repositório
### ❌ Erro de Deploy Netlify
- Verifique se `NETLIFY_AUTH_TOKEN` e `NETLIFY_SITE_ID` estão corretos
- Confirme se o site existe no Netlify
## Logs e Monitoramento
Para acompanhar a execução:
1. Vá para **Actions** no seu repositório
2. Clique na execução desejada
3. Expanda os jobs para ver os logs detalhados
## Segurança
⚠️ **IMPORTANTE:**
- Nunca compartilhe seus tokens
- Use tokens com permissões mínimas necessárias
- Revogue tokens antigos quando não precisar mais
- Monitore o uso dos tokens regularmente
## Frequência de Execução
A action executa:
- ✅ A cada push na branch main ou develop
- ✅ A cada pull request para main
- ✅ A cada 30 minutos (agendado)
- ✅ Manualmente quando solicitado
Para alterar a frequência, edite o arquivo `.github/workflows/auto-sync-deploy.yml`

View File

@@ -1,263 +0,0 @@
name: Auto Sync and Deploy
# Triggers para execução da action
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
# Executa a cada 30 minutos
- cron: '*/30 * * * *'
workflow_dispatch:
inputs:
force_deploy:
description: 'Force deploy even without changes'
required: false
default: 'false'
type: boolean
target_branch:
description: 'Target branch for deployment'
required: false
default: 'main'
type: string
env:
NODE_VERSION: '18'
PNPM_VERSION: '8'
TARGET_REPO: 'Reifonas/TS_RDO'
jobs:
# Job para verificar mudanças
check-changes:
runs-on: ubuntu-latest
outputs:
has-changes: ${{ steps.changes.outputs.has-changes }}
changed-files: ${{ steps.changes.outputs.changed-files }}
steps:
- name: Checkout código
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Verificar mudanças
id: changes
run: |
if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ inputs.force_deploy }}" = "true" ]; then
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "changed-files=scheduled-run" >> $GITHUB_OUTPUT
else
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD || echo "")
if [ -n "$CHANGED_FILES" ]; then
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "changed-files<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "has-changes=false" >> $GITHUB_OUTPUT
echo "changed-files=" >> $GITHUB_OUTPUT
fi
fi
# Job para build e testes
build-and-test:
needs: check-changes
if: needs.check-changes.outputs.has-changes == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Instalar dependências
run: pnpm install --frozen-lockfile
- name: Executar linting
run: pnpm run lint
continue-on-error: true
- name: Executar testes
run: pnpm run test
continue-on-error: true
- name: Build do projeto
run: pnpm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-files
path: dist/
retention-days: 1
# Job para sincronização com repositório remoto
sync-to-remote:
needs: [check-changes, build-and-test]
if: needs.check-changes.outputs.has-changes == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout código atual
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Configurar Git
run: |
git config --global user.name "Auto Sync Bot"
git config --global user.email "auto-sync@rdo-app.com"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-files
path: dist/
- name: Preparar arquivos para sincronização
run: |
# Criar diretório temporário
mkdir -p /tmp/sync-repo
# Copiar arquivos essenciais (excluir node_modules, logs, etc.)
rsync -av --exclude='node_modules' \
--exclude='.git' \
--exclude='logs' \
--exclude='.env.local' \
--exclude='*.log' \
--exclude='.trae' \
--exclude='android' \
--exclude='ios' \
./ /tmp/sync-repo/
- name: Clonar repositório remoto
env:
REMOTE_TOKEN: ${{ secrets.REMOTE_REPO_TOKEN }}
run: |
cd /tmp
git clone https://${REMOTE_TOKEN}@github.com/${{ env.TARGET_REPO }}.git remote-repo
cd remote-repo
git checkout ${{ inputs.target_branch || 'main' }} || git checkout -b ${{ inputs.target_branch || 'main' }}
- name: Sincronizar arquivos
run: |
cd /tmp/remote-repo
# Remover arquivos antigos (exceto .git)
find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
# Copiar novos arquivos
cp -r /tmp/sync-repo/* .
cp -r /tmp/sync-repo/.* . 2>/dev/null || true
# Adicionar todos os arquivos
git add .
- name: Commit e Push para repositório remoto
env:
REMOTE_TOKEN: ${{ secrets.REMOTE_REPO_TOKEN }}
run: |
cd /tmp/remote-repo
# Verificar se há mudanças
if git diff --staged --quiet; then
echo "Nenhuma mudança para sincronizar"
exit 0
fi
# Criar mensagem de commit
COMMIT_MSG="Auto-sync from RDO-C: $(date '+%Y-%m-%d %H:%M:%S')"
if [ "${{ needs.check-changes.outputs.changed-files }}" != "scheduled-run" ]; then
COMMIT_MSG="$COMMIT_MSG\n\nChanged files:\n${{ needs.check-changes.outputs.changed-files }}"
fi
# Commit e push
git commit -m "$COMMIT_MSG"
git push origin ${{ inputs.target_branch || 'main' }}
# Job para deploy (se necessário)
deploy:
needs: [check-changes, build-and-test, sync-to-remote]
if: needs.check-changes.outputs.has-changes == 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-files
path: dist/
- name: Deploy para Netlify
uses: nwtgck/actions-netlify@v3.0
with:
publish-dir: './dist'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Auto-deploy from commit ${{ github.sha }}"
enable-pull-request-comment: false
enable-commit-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 10
# Job para notificações
notify:
needs: [check-changes, build-and-test, sync-to-remote, deploy]
if: always() && needs.check-changes.outputs.has-changes == 'true'
runs-on: ubuntu-latest
steps:
- name: Notificar sucesso
if: needs.sync-to-remote.result == 'success'
run: |
echo "✅ Sincronização concluída com sucesso!"
echo "Repositório: ${{ env.TARGET_REPO }}"
echo "Branch: ${{ inputs.target_branch || 'main' }}"
echo "Commit: ${{ github.sha }}"
- name: Notificar falha
if: needs.sync-to-remote.result == 'failure' || needs.build-and-test.result == 'failure'
run: |
echo "❌ Falha na sincronização!"
echo "Verifique os logs para mais detalhes."
exit 1
# Job para limpeza
cleanup:
needs: [check-changes, build-and-test, sync-to-remote, deploy, notify]
if: always()
runs-on: ubuntu-latest
steps:
- name: Limpar artifacts
uses: actions/github-script@v7
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
for (const artifact of artifacts.data.artifacts) {
if (artifact.name === 'build-files') {
await github.rest.actions.deleteArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
});
console.log(`Artifact ${artifact.name} removido`);
}
}

View File

@@ -1,64 +0,0 @@
name: Deploy to GitHub Pages
on:
# Executa no push para a branch main
push:
branches: [ main ]
# Permite execução manual
workflow_dispatch:
# Define permissões necessárias para o GITHUB_TOKEN
permissions:
contents: read
pages: write
id-token: write
# Permite apenas um deploy por vez
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Job de build
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'
# Job de deploy
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,352 +0,0 @@
# Análise Completa e Plano de Otimização - Aplicativo RDO
## 1. Resumo Executivo
Este documento apresenta uma análise detalhada da estrutura atual do aplicativo RDO (Relatório Diário de Obra) e propõe um plano abrangente de otimização, refatoração e modernização do código. O objetivo é melhorar a performance, legibilidade, manutenibilidade e seguir as melhores práticas atuais de desenvolvimento.
## 2. Análise da Arquitetura Atual
### 2.1 Pontos Fortes Identificados
- ✅ Uso de TypeScript para tipagem estática
- ✅ Arquitetura baseada em React 18 com hooks modernos
- ✅ Integração com Supabase para backend-as-a-service
- ✅ Implementação de React Query para gerenciamento de estado servidor
- ✅ Uso de Zustand para estado global
- ✅ Configuração de PWA com Capacitor
- ✅ Implementação de modo offline com Dexie
- ✅ Estrutura de pastas organizada
### 2.2 Problemas Críticos Identificados
#### 2.2.1 Duplicação de Lógica de Estado
-**Problema**: Existem dois sistemas paralelos para gerenciamento de obras:
- `src/hooks/useObras.ts` (useState + useEffect)
- `src/hooks/queries/useObras.ts` (React Query)
- `src/stores/useObraStore.ts` (Zustand)
-**Impacto**: Inconsistência de dados, complexidade desnecessária, bugs potenciais
#### 2.2.2 Configuração TypeScript Permissiva
-**Problema**: `tsconfig.json` com `strict: false` e outras verificações desabilitadas
-**Impacto**: Perda de benefícios da tipagem estática, bugs em runtime
#### 2.2.3 Estrutura de Rotas Repetitiva
-**Problema**: Código repetitivo no `App.tsx` com múltiplas rotas similares
-**Impacto**: Dificulta manutenção e adiciona verbosidade
#### 2.2.4 Falta de Padronização de Componentes
-**Problema**: Componentes sem padrão consistente de props e estrutura
-**Impacto**: Dificuldade de manutenção e reutilização
## 3. Plano de Otimização Detalhado
### 3.1 Fase 1: Consolidação da Arquitetura de Estado (Prioridade Alta)
#### 3.1.1 Migração para Arquitetura Unificada
**Objetivo**: Eliminar duplicações e criar uma única fonte de verdade
**Ações**:
1. **Manter apenas React Query + Zustand**:
- Remover hooks baseados em useState (`useObras.ts`, `useRdos.ts`, etc.)
- Usar React Query para estado servidor (dados do Supabase)
- Usar Zustand apenas para estado cliente (UI, configurações)
2. **Reestruturar hooks de queries**:
```typescript
// Estrutura otimizada
src/hooks/
├── queries/
│ ├── useObrasQuery.ts // React Query apenas
│ ├── useRdosQuery.ts // React Query apenas
│ └── useUsersQuery.ts // React Query apenas
├── stores/
│ ├── useUIStore.ts // Estado da UI
│ ├── useConfigStore.ts // Configurações
│ └── useOfflineStore.ts // Estado offline
└── index.ts
```
3. **Implementar padrão de custom hooks compostos**:
```typescript
// Exemplo: useObra.ts
export const useObra = (id: string) => {
const query = useObraQuery(id);
const mutation = useUpdateObraMutation();
const uiState = useUIStore();
return {
...query,
update: mutation.mutate,
isUpdating: mutation.isPending,
// Lógica composta
};
};
```
#### 3.1.2 Otimização do React Query
**Configurações aprimoradas**:
```typescript
// queryClient.ts otimizado
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutos
gcTime: 10 * 60 * 1000, // 10 minutos
retry: (failureCount, error) => {
if (error?.status === 401) return false;
return failureCount < 3;
},
refetchOnWindowFocus: false, // Otimização mobile
},
},
});
```
### 3.2 Fase 2: Modernização do TypeScript (Prioridade Alta)
#### 3.2.1 Configuração Strict
```json
// tsconfig.json otimizado
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}
```
#### 3.2.2 Melhoria dos Tipos
1. **Criar tipos específicos do domínio**:
```typescript
// types/domain.ts
export type ObraStatus = 'ativa' | 'pausada' | 'concluida' | 'cancelada';
export type UserRole = 'admin' | 'engenheiro' | 'mestre_obra' | 'usuario';
export interface ObraWithRelations extends Obra {
responsavel?: Usuario;
rdos?: RDO[];
}
```
2. **Implementar branded types para IDs**:
```typescript
export type ObraId = string & { readonly brand: unique symbol };
export type UserId = string & { readonly brand: unique symbol };
```
### 3.3 Fase 3: Refatoração de Componentes (Prioridade Média)
#### 3.3.1 Sistema de Design Consistente
1. **Criar componentes base reutilizáveis**:
```typescript
// components/ui/
├── Button/
│ ├── Button.tsx
│ ├── Button.types.ts
│ └── Button.stories.tsx
├── Input/
├── Modal/
└── Card/
```
2. **Implementar compound components**:
```typescript
// Exemplo: Modal compound component
export const Modal = {
Root: ModalRoot,
Header: ModalHeader,
Body: ModalBody,
Footer: ModalFooter,
};
```
#### 3.3.2 Otimização de Performance
1. **Implementar React.memo estratégico**:
```typescript
export const ObraCard = React.memo(({ obra }: { obra: Obra }) => {
// Componente otimizado
});
```
2. **Usar React.lazy para code splitting**:
```typescript
const ObraDetails = React.lazy(() => import('./pages/ObraDetails'));
```
3. **Implementar virtualization para listas grandes**:
```typescript
import { FixedSizeList as List } from 'react-window';
```
### 3.4 Fase 4: Otimização de Rotas (Prioridade Média)
#### 3.4.1 Configuração Declarativa de Rotas
```typescript
// routes/config.ts
export const routes = [
{
path: '/',
element: Dashboard,
protected: true,
layout: MainLayout,
},
{
path: '/obra/:id',
element: ObraDetails,
protected: true,
layout: null, // Tela cheia
},
] as const;
// App.tsx simplificado
export default function App() {
return (
<Router>
<QueryProvider>
<AuthProvider>
<RouteRenderer routes={routes} />
</AuthProvider>
</QueryProvider>
</Router>
);
}
```
### 3.5 Fase 5: Otimizações de Performance (Prioridade Média)
#### 3.5.1 Bundle Optimization
1. **Configurar Vite para otimização**:
```typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
supabase: ['@supabase/supabase-js'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-select'],
},
},
},
},
});
```
2. **Implementar preloading estratégico**:
```typescript
// Preload de rotas críticas
const prefetchObraDetails = (obraId: string) => {
queryClient.prefetchQuery({
queryKey: ['obra', obraId],
queryFn: () => getObra(obraId),
});
};
```
#### 3.5.2 Otimização de Imagens e Assets
1. **Implementar lazy loading de imagens**
2. **Usar WebP com fallback**
3. **Configurar service worker para cache de assets**
### 3.6 Fase 6: Melhorias de DX (Developer Experience)
#### 3.6.1 Ferramentas de Desenvolvimento
1. **Configurar ESLint mais rigoroso**:
```javascript
// eslint.config.js
export default [
...tseslint.configs.strictTypeChecked,
{
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'react-hooks/exhaustive-deps': 'error',
},
},
];
```
2. **Implementar Prettier com configuração consistente**
3. **Configurar Husky para pre-commit hooks**
#### 3.6.2 Testing Strategy
1. **Implementar testes unitários com Vitest**
2. **Testes de integração com Testing Library**
3. **E2E tests com Playwright**
## 4. Cronograma de Implementação
### Sprint 1 (2 semanas) - Fundação
- [ ] Configurar TypeScript strict mode
- [ ] Consolidar arquitetura de estado (Fase 1)
- [ ] Remover hooks duplicados
- [ ] Configurar ferramentas de desenvolvimento
### Sprint 2 (2 semanas) - Componentes
- [ ] Criar sistema de design base
- [ ] Refatorar componentes principais
- [ ] Implementar compound components
- [ ] Otimizar performance com React.memo
### Sprint 3 (1 semana) - Rotas e Performance
- [ ] Refatorar sistema de rotas
- [ ] Implementar code splitting
- [ ] Otimizar bundle com Vite
- [ ] Configurar preloading
### Sprint 4 (1 semana) - Finalização
- [ ] Testes e validação
- [ ] Documentação
- [ ] Deploy e monitoramento
## 5. Métricas de Sucesso
### 5.1 Performance
- **Bundle size**: Redução de 30%
- **First Contentful Paint**: < 1.5s
- **Largest Contentful Paint**: < 2.5s
- **Time to Interactive**: < 3s
### 5.2 Qualidade de Código
- **TypeScript coverage**: 100%
- **ESLint errors**: 0
- **Test coverage**: > 80%
- **Duplicação de código**: < 5%
### 5.3 Developer Experience
- **Build time**: Redução de 40%
- **Hot reload**: < 200ms
- **Type checking**: < 5s
## 6. Riscos e Mitigações
### 6.1 Riscos Identificados
1. **Breaking changes durante refatoração**
- *Mitigação*: Implementar testes abrangentes antes das mudanças
2. **Regressões de funcionalidade**
- *Mitigação*: Refatoração incremental com validação contínua
3. **Resistência da equipe às mudanças**
- *Mitigação*: Documentação clara e treinamento
### 6.2 Plano de Rollback
- Manter branches de feature para cada fase
- Implementar feature flags para mudanças críticas
- Monitoramento contínuo em produção
## 7. Conclusão
Este plano de otimização transformará o aplicativo RDO em uma aplicação moderna, performática e maintível. A implementação incremental garante baixo risco enquanto maximiza os benefícios de cada melhoria.
A consolidação da arquitetura de estado e a modernização do TypeScript são as prioridades mais altas, pois impactam diretamente na estabilidade e manutenibilidade do código.
Com a implementação completa deste plano, esperamos:
- **50% de redução** no tempo de desenvolvimento de novas features
- **30% de melhoria** na performance da aplicação
- **90% de redução** em bugs relacionados a tipos
- **Experiência de desenvolvimento** significativamente melhorada

File diff suppressed because it is too large Load Diff

View File

@@ -1,815 +0,0 @@
# Documento de Arquitetura Técnica - RDO Mobile App
## 1. Design da Arquitetura
```mermaid
graph TD
A[Usuário Mobile] --> B[React Native/PWA Frontend]
B --> C[Supabase SDK]
C --> D[Supabase Backend]
subgraph "Frontend Layer"
B
E[Zustand State Management]
F[React Hook Form]
G[Framer Motion]
H[Tailwind CSS]
end
subgraph "Backend as a Service (Supabase)"
D
I[PostgreSQL Database]
J[Authentication]
K[Storage (Fotos/Docs)]
L[Real-time Subscriptions]
end
subgraph "Funcionalidades Offline"
M[IndexedDB Cache]
N[Service Worker]
O[Background Sync]
end
B --> M
N --> B
O --> C
```
## 2. Descrição das Tecnologias
* **Frontend**: React\@18 + TypeScript + Vite + Tailwind CSS
* **Mobile**: PWA (Progressive Web App) com Capacitor para recursos nativos
* **Estado**: Zustand para gerenciamento de estado global
* **Formulários**: React Hook Form + Zod para validação
* **Animações**: Framer Motion para microinterações
* **UI Components**: Headless UI + Radix UI primitives
* **Ícones**: Phosphor Icons
* **Backend**: Supabase (PostgreSQL + Auth + Storage + Real-time)
* **Cache**: TanStack Query para cache de dados
* **Offline**: Workbox para service workers
## 3. Definições de Rotas
| Rota | Propósito |
| ----------------------- | --------------------------------------------- |
| / | Dashboard principal com visão geral das obras |
| /obra/:id | Detalhes específicos de uma obra |
| /obra/:id/rdo/novo | Formulário para criar novo RDO |
| /obra/:id/rdo/:rdoId | Visualizar/editar RDO específico |
| /obra/:id/tarefas | Lista de tarefas da obra |
| /cadastros | Menu principal de cadastros |
| /cadastros/obras | Formulário de cadastro de obras |
| /cadastros/usuarios | Gerenciamento de usuários |
| /cadastros/equipamentos | Cadastro de equipamentos |
| <br /> | <br /> |
| /cadastros/atividades | Tipos de atividades padrão |
| /relatorios | Dashboard de relatórios e exportações |
| /perfil | Configurações do usuário |
| /configuracoes | Configurações do aplicativo |
## 4. Definições de API
### 4.1 APIs Principais
**Autenticação de Usuário**
```
POST /auth/v1/token
```
Request:
| Nome do Parâmetro | Tipo | Obrigatório | Descrição |
| ----------------- | ------ | ----------- | ---------------- |
| email | string | true | Email do usuário |
| password | string | true | Senha do usuário |
Response:
| Nome do Parâmetro | Tipo | Descrição |
| ----------------- | ------ | ---------------------------- |
| access\_token | string | Token JWT para autenticação |
| user | object | Dados do usuário autenticado |
**Criar RDO**
```
POST /rest/v1/rdos
```
Request:
| Nome do Parâmetro | Tipo | Obrigatório | Descrição |
| --------------------- | ------ | ----------- | ------------------------------ |
| obra\_id | uuid | true | ID da obra |
| data\_relatorio | date | true | Data do relatório |
| condicoes\_climaticas | string | true | Condições do tempo |
| atividades | array | true | Lista de atividades executadas |
| mao\_de\_obra | array | false | Funcionários presentes |
| equipamentos | array | false | Equipamentos utilizados |
| ocorrencias | array | false | Ocorrências registradas |
Response:
| Nome do Parâmetro | Tipo | Descrição |
| ----------------- | --------- | ------------------- |
| id | uuid | ID do RDO criado |
| status | string | Status do relatório |
| created\_at | timestamp | Data de criação |
Exemplo:
```json
{
"obra_id": "123e4567-e89b-12d3-a456-426614174000",
"data_relatorio": "2024-01-15",
"condicoes_climaticas": "Ensolarado",
"atividades": [
{
"tipo": "Concretagem",
"descricao": "Concretagem da laje do 2º pavimento",
"percentual_concluido": 75
}
]
}
```
**Listar Obras**
```
GET /rest/v1/obras
```
Response:
| Nome do Parâmetro | Tipo | Descrição |
| ----------------- | ------ | ---------------------------------------- |
| id | uuid | ID da obra |
| nome | string | Nome da obra |
| endereco | string | Endereço da obra |
| status | string | Status atual (ativa, pausada, concluída) |
| progresso | number | Percentual de conclusão |
## 5. Arquitetura do Servidor
```mermaid
graph TD
A[Cliente Mobile] --> B[Supabase Edge Functions]
B --> C[Supabase Auth]
B --> D[PostgreSQL Database]
B --> E[Supabase Storage]
subgraph "Supabase Backend"
C
D
E
F[Real-time Engine]
G[Row Level Security]
end
subgraph "Edge Functions"
H[Geração de Relatórios PDF]
I[Processamento de Imagens]
J[Notificações Push]
end
B --> H
B --> I
B --> J
```
## 6. Modelo de Dados
### 6.1 Definição do Modelo de Dados
```mermaid
erDiagram
USUARIOS ||--o{ OBRAS : gerencia
USUARIOS ||--o{ RDOS : cria
OBRAS ||--o{ RDOS : possui
OBRAS ||--o{ TAREFAS : contem
RDOS ||--o{ RDO_ATIVIDADES : possui
RDOS ||--o{ RDO_MAO_OBRA : registra
RDOS ||--o{ RDO_EQUIPAMENTOS : utiliza
RDOS ||--o{ RDO_OCORRENCIAS : reporta
RDOS ||--o{ RDO_ANEXOS : contem
RDOS ||--o{ RDO_INSPECOES_SOLDA : possui
RDOS ||--o{ RDO_VERIFICACOES_TORQUE : possui
TAREFAS ||--o{ TASK_LOGS : possui
USUARIOS ||--o{ TASK_LOGS : executa
USUARIOS {
uuid id PK
string email UK
string nome
string telefone
string cargo
string role
boolean ativo
timestamp created_at
timestamp updated_at
}
OBRAS {
uuid id PK
string nome
string descricao
string endereco
string cep
string cidade
string estado
uuid responsavel_id FK
date data_inicio
date data_prevista_fim
decimal progresso_geral
string status
jsonb configuracoes
timestamp created_at
timestamp updated_at
}
RDOS {
uuid id PK
uuid obra_id FK
uuid criado_por FK
date data_relatorio
string condicoes_climaticas
text observacoes_gerais
string status
uuid aprovado_por FK
timestamp aprovado_em
timestamp created_at
timestamp updated_at
}
RDO_ATIVIDADES {
uuid id PK
uuid rdo_id FK
string tipo_atividade
text descricao
string localizacao
decimal percentual_concluido
integer ordem
timestamp created_at
}
RDO_MAO_OBRA {
uuid id PK
uuid rdo_id FK
string funcao
integer quantidade
decimal horas_trabalhadas
text observacoes
timestamp created_at
}
RDO_EQUIPAMENTOS {
uuid id PK
uuid rdo_id FK
string nome_equipamento
string tipo
decimal horas_utilizadas
decimal combustivel_gasto
text observacoes
timestamp created_at
}
RDO_OCORRENCIAS {
uuid id PK
uuid rdo_id FK
string tipo_ocorrencia
text descricao
string gravidade
text acao_tomada
timestamp created_at
}
RDO_ANEXOS {
uuid id PK
uuid rdo_id FK
string nome_arquivo
string tipo_arquivo
string url_storage
integer tamanho_bytes
text descricao
timestamp created_at
}
RDO_INSPECOES_SOLDA {
uuid id PK
uuid rdo_id FK
string identificacao_junta
string status_inspecao
string metodo_inspecao
text observacoes
uuid inspecionado_por FK
timestamp created_at
}
RDO_VERIFICACOES_TORQUE {
uuid id PK
uuid rdo_id FK
string identificacao_parafuso
decimal torque_especificado
decimal torque_aplicado
string status_verificacao
text observacoes
uuid verificado_por FK
timestamp created_at
}
TAREFAS {
uuid id PK
uuid obra_id FK
string titulo
text descricao
string status
string prioridade
uuid responsavel_id FK
date data_inicio
date data_fim
decimal progresso
jsonb metadados
timestamp created_at
timestamp updated_at
}
TASK_LOGS {
uuid id PK
uuid task_id FK
uuid usuario_id FK
string tipo_evento
text descricao
jsonb detalhes
timestamp created_at
}
RDOS ||--o{ OCORRENCIAS : possui
RDOS ||--o{ ANEXOS : contem
OBRAS ||--o{ TAREFAS : possui
EQUIPAMENTOS ||--o{ EQUIPAMENTOS_UTILIZADOS : referencia
USUARIOS {
uuid id PK
string email
string nome
string funcao
string telefone
timestamp created_at
timestamp updated_at
}
OBRAS {
uuid id PK
string nome
string endereco
string descricao
date data_inicio
date data_prevista_fim
string status
decimal progresso_percentual
uuid responsavel_id FK
timestamp created_at
}
RDOS {
uuid id PK
uuid obra_id FK
uuid criado_por FK
date data_relatorio
string condicoes_climaticas
text observacoes_gerais
string status
timestamp created_at
}
ATIVIDADES {
uuid id PK
uuid rdo_id FK
string tipo_atividade
text descricao
string localizacao
decimal percentual_concluido
time hora_inicio
time hora_fim
}
MAO_DE_OBRA {
uuid id PK
uuid rdo_id FK
string nome_funcionario
string funcao
decimal horas_trabalhadas
boolean presente
}
EQUIPAMENTOS {
uuid id PK
string nome
string tipo
string modelo
string status
text observacoes
}
EQUIPAMENTOS_UTILIZADOS {
uuid id PK
uuid rdo_id FK
uuid equipamento_id FK
decimal horas_operacao
decimal combustivel_consumido
text observacoes
}
OCORRENCIAS {
uuid id PK
uuid rdo_id FK
string tipo
text descricao
string gravidade
boolean resolvida
}
ANEXOS {
uuid id PK
uuid rdo_id FK
string tipo
string url_arquivo
string nome_arquivo
text descricao
}
TAREFAS {
uuid id PK
uuid obra_id FK
string titulo
text descricao
date data_prevista
string status
string prioridade
uuid responsavel_id FK
}
```
### 6.2 Linguagem de Definição de Dados (DDL)
**Tabela de Usuários (usuarios)**
```sql
-- Criar tabela
CREATE TABLE usuarios (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
nome VARCHAR(100) NOT NULL,
telefone VARCHAR(20),
cargo VARCHAR(100),
role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('admin', 'supervisor', 'user')),
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Criar índices
CREATE INDEX idx_usuarios_email ON usuarios(email);
CREATE INDEX idx_usuarios_role ON usuarios(role);
CREATE INDEX idx_usuarios_ativo ON usuarios(ativo);
-- Dados iniciais
INSERT INTO usuarios (email, nome, cargo, role) VALUES
('admin@rdo.com', 'Administrador', 'Gerente de Projeto', 'admin'),
('supervisor@rdo.com', 'Supervisor', 'Supervisor de Obra', 'supervisor'),
('user@rdo.com', 'Usuário Padrão', 'Técnico', 'user');
```
**Tabela de Obras (obras)**
```sql
CREATE TABLE obras (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome VARCHAR(255) NOT NULL,
descricao TEXT,
endereco TEXT,
cep VARCHAR(10),
cidade VARCHAR(100),
estado VARCHAR(2),
responsavel_id UUID REFERENCES usuarios(id),
data_inicio DATE,
data_prevista_fim DATE,
progresso_geral DECIMAL(5,2) DEFAULT 0.00,
status VARCHAR(20) DEFAULT 'planejamento' CHECK (status IN ('planejamento', 'em_andamento', 'pausada', 'concluida', 'cancelada')),
configuracoes JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_obras_responsavel ON obras(responsavel_id);
CREATE INDEX idx_obras_status ON obras(status);
CREATE INDEX idx_obras_data_inicio ON obras(data_inicio);
```
**Tabela de RDOs (rdos)**
```sql
CREATE TABLE rdos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
obra_id UUID NOT NULL REFERENCES obras(id),
criado_por UUID NOT NULL REFERENCES usuarios(id),
data_relatorio DATE NOT NULL,
condicoes_climaticas VARCHAR(100),
observacoes_gerais TEXT,
status VARCHAR(20) DEFAULT 'rascunho' CHECK (status IN ('rascunho', 'enviado', 'aprovado', 'rejeitado')),
aprovado_por UUID REFERENCES usuarios(id),
aprovado_em TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdos_obra ON rdos(obra_id);
CREATE INDEX idx_rdos_criado_por ON rdos(criado_por);
CREATE INDEX idx_rdos_data_relatorio ON rdos(data_relatorio);
CREATE INDEX idx_rdos_status ON rdos(status);
```
**Tabela de Atividades do RDO (rdo_atividades)**
```sql
CREATE TABLE rdo_atividades (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
tipo_atividade VARCHAR(100) NOT NULL,
descricao TEXT,
localizacao VARCHAR(255),
percentual_concluido DECIMAL(5,2) DEFAULT 0.00,
ordem INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_atividades_rdo ON rdo_atividades(rdo_id);
CREATE INDEX idx_rdo_atividades_tipo ON rdo_atividades(tipo_atividade);
```
**Tabela de Mão de Obra (rdo_mao_obra)**
```sql
CREATE TABLE rdo_mao_obra (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
funcao VARCHAR(100) NOT NULL,
quantidade INTEGER NOT NULL DEFAULT 1,
horas_trabalhadas DECIMAL(5,2) NOT NULL DEFAULT 0.00,
observacoes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_mao_obra_rdo ON rdo_mao_obra(rdo_id);
CREATE INDEX idx_rdo_mao_obra_funcao ON rdo_mao_obra(funcao);
```
**Tabela de Equipamentos (rdo_equipamentos)**
```sql
CREATE TABLE rdo_equipamentos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
nome_equipamento VARCHAR(255) NOT NULL,
tipo VARCHAR(100),
horas_utilizadas DECIMAL(5,2) DEFAULT 0.00,
combustivel_gasto DECIMAL(8,2) DEFAULT 0.00,
observacoes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_equipamentos_rdo ON rdo_equipamentos(rdo_id);
CREATE INDEX idx_rdo_equipamentos_tipo ON rdo_equipamentos(tipo);
```
**Tabela de Ocorrências (rdo_ocorrencias)**
```sql
CREATE TABLE rdo_ocorrencias (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
tipo_ocorrencia VARCHAR(100) NOT NULL,
descricao TEXT NOT NULL,
gravidade VARCHAR(20) DEFAULT 'baixa' CHECK (gravidade IN ('baixa', 'media', 'alta', 'critica')),
acao_tomada TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_ocorrencias_rdo ON rdo_ocorrencias(rdo_id);
CREATE INDEX idx_rdo_ocorrencias_tipo ON rdo_ocorrencias(tipo_ocorrencia);
CREATE INDEX idx_rdo_ocorrencias_gravidade ON rdo_ocorrencias(gravidade);
```
**Tabela de Anexos (rdo_anexos)**
```sql
CREATE TABLE rdo_anexos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
nome_arquivo VARCHAR(255) NOT NULL,
tipo_arquivo VARCHAR(50),
url_storage TEXT NOT NULL,
tamanho_bytes INTEGER,
descricao TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_anexos_rdo ON rdo_anexos(rdo_id);
CREATE INDEX idx_rdo_anexos_tipo ON rdo_anexos(tipo_arquivo);
```
**Tabela de Inspeções de Solda (rdo_inspecoes_solda)**
```sql
CREATE TABLE rdo_inspecoes_solda (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
identificacao_junta VARCHAR(100) NOT NULL,
status_inspecao VARCHAR(20) DEFAULT 'pendente' CHECK (status_inspecao IN ('pendente', 'aprovada', 'rejeitada', 'retrabalho')),
metodo_inspecao VARCHAR(100),
observacoes TEXT,
inspecionado_por UUID REFERENCES usuarios(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_inspecoes_solda_rdo ON rdo_inspecoes_solda(rdo_id);
CREATE INDEX idx_rdo_inspecoes_solda_status ON rdo_inspecoes_solda(status_inspecao);
```
**Tabela de Verificações de Torque (rdo_verificacoes_torque)**
```sql
CREATE TABLE rdo_verificacoes_torque (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
identificacao_parafuso VARCHAR(100) NOT NULL,
torque_especificado DECIMAL(8,2) NOT NULL,
torque_aplicado DECIMAL(8,2) NOT NULL,
status_verificacao VARCHAR(20) DEFAULT 'conforme' CHECK (status_verificacao IN ('conforme', 'nao_conforme', 'retrabalho')),
observacoes TEXT,
verificado_por UUID REFERENCES usuarios(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_rdo_verificacoes_torque_rdo ON rdo_verificacoes_torque(rdo_id);
CREATE INDEX idx_rdo_verificacoes_torque_status ON rdo_verificacoes_torque(status_verificacao);
```
**Tabela de Tarefas (tarefas)**
```sql
CREATE TABLE tarefas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
obra_id UUID NOT NULL REFERENCES obras(id),
titulo VARCHAR(255) NOT NULL,
descricao TEXT,
status VARCHAR(20) DEFAULT 'pendente' CHECK (status IN ('pendente', 'em_andamento', 'concluida', 'cancelada')),
prioridade VARCHAR(20) DEFAULT 'media' CHECK (prioridade IN ('baixa', 'media', 'alta', 'urgente')),
responsavel_id UUID REFERENCES usuarios(id),
data_inicio DATE,
data_fim DATE,
progresso DECIMAL(5,2) DEFAULT 0.00,
metadados JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_tarefas_obra ON tarefas(obra_id);
CREATE INDEX idx_tarefas_responsavel ON tarefas(responsavel_id);
CREATE INDEX idx_tarefas_status ON tarefas(status);
CREATE INDEX idx_tarefas_prioridade ON tarefas(prioridade);
```
**Tabela de Logs de Tarefas (task_logs)**
```sql
CREATE TABLE task_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_id UUID NOT NULL REFERENCES tarefas(id) ON DELETE CASCADE,
usuario_id UUID NOT NULL REFERENCES usuarios(id),
tipo_evento VARCHAR(50) NOT NULL,
descricao TEXT,
detalhes JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_task_logs_task ON task_logs(task_id);
CREATE INDEX idx_task_logs_usuario ON task_logs(usuario_id);
CREATE INDEX idx_task_logs_tipo ON task_logs(tipo_evento);
CREATE INDEX idx_task_logs_created_at ON task_logs(created_at DESC);
```
**Tabela de Obras**
```sql
-- Criar tabela de obras
CREATE TABLE obras (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome VARCHAR(200) NOT NULL,
endereco TEXT NOT NULL,
descricao TEXT,
data_inicio DATE NOT NULL,
data_prevista_fim DATE,
status VARCHAR(20) DEFAULT 'ativa' CHECK (status IN ('ativa', 'pausada', 'concluida')),
progresso_percentual DECIMAL(5,2) DEFAULT 0.00,
responsavel_id UUID REFERENCES usuarios(id),
orcamento_total DECIMAL(15,2),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Criar índices
CREATE INDEX idx_obras_status ON obras(status);
CREATE INDEX idx_obras_responsavel ON obras(responsavel_id);
CREATE INDEX idx_obras_data_inicio ON obras(data_inicio DESC);
-- Políticas RLS
ALTER TABLE obras ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Usuários podem ver obras que participam" ON obras
FOR SELECT USING (
responsavel_id = auth.uid() OR
EXISTS (
SELECT 1 FROM usuarios
WHERE id = auth.uid() AND funcao IN ('Gestor', 'Engenheiro')
)
);
-- Permissões
GRANT SELECT ON obras TO anon;
GRANT ALL PRIVILEGES ON obras TO authenticated;
```
**Tabela de RDOs**
```sql
-- Criar tabela de RDOs
CREATE TABLE rdos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
obra_id UUID NOT NULL REFERENCES obras(id) ON DELETE CASCADE,
criado_por UUID NOT NULL REFERENCES usuarios(id),
data_relatorio DATE NOT NULL,
condicoes_climaticas VARCHAR(50) NOT NULL,
temperatura_min DECIMAL(4,1),
temperatura_max DECIMAL(4,1),
observacoes_gerais TEXT,
status VARCHAR(20) DEFAULT 'rascunho' CHECK (status IN ('rascunho', 'enviado', 'aprovado')),
aprovado_por UUID REFERENCES usuarios(id),
aprovado_em TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Criar índices
CREATE INDEX idx_rdos_obra_id ON rdos(obra_id);
CREATE INDEX idx_rdos_data_relatorio ON rdos(data_relatorio DESC);
CREATE INDEX idx_rdos_status ON rdos(status);
CREATE INDEX idx_rdos_criado_por ON rdos(criado_por);
-- Constraint única para evitar múltiplos RDOs na mesma data/obra
CREATE UNIQUE INDEX idx_rdos_obra_data_unique ON rdos(obra_id, data_relatorio);
-- Políticas RLS
ALTER TABLE rdos ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Usuários podem ver RDOs das suas obras" ON rdos
FOR SELECT USING (
EXISTS (
SELECT 1 FROM obras
WHERE id = obra_id AND (
responsavel_id = auth.uid() OR
EXISTS (
SELECT 1 FROM usuarios
WHERE usuarios.id = auth.uid() AND funcao IN ('Gestor', 'Engenheiro')
)
)
)
);
-- Permissões
GRANT SELECT ON rdos TO anon;
GRANT ALL PRIVILEGES ON rdos TO authenticated;
```
**Dados Iniciais**
```sql
-- Inserir tipos de atividades padrão
INSERT INTO tipos_atividades (nome, categoria) VALUES
('Escavação', 'Terraplanagem'),
('Fundação', 'Estrutura'),
('Concretagem', 'Estrutura'),
('Alvenaria', 'Vedação'),
('Instalação Elétrica', 'Instalações'),
('Instalação Hidráulica', 'Instalações'),
('Revestimento', 'Acabamento'),
('Pintura', 'Acabamento');
-- Inserir condições climáticas padrão
INSERT INTO condicoes_climaticas (descricao) VALUES
('Ensolarado'),
('Parcialmente Nublado'),
('Nublado'),
('Chuvisco'),
('Chuva Leve'),
('Chuva Forte'),
('Tempestade');
```

View File

@@ -1,129 +0,0 @@
# Documento de Requisitos do Produto - App RDO Mobile
## 1. Visão Geral do Produto
O **RDO Mobile** é um aplicativo mobile-first para registro e acompanhamento diário de atividades em obras de construção civil. O app facilita o processo de criação de Relatórios Diários de Obra (RDO), permitindo que engenheiros, mestres de obra e gestores registrem atividades, mão de obra, equipamentos, condições climáticas e ocorrências de forma prática e organizada.
O produto visa substituir os relatórios em papel ou Excel por uma solução digital moderna, melhorando a comunicação entre canteiro de obra, escritório e clientes, proporcionando acompanhamento em tempo real do progresso das obras.
## 2. Funcionalidades Principais
### 2.1 Papéis de Usuário
| Papel | Método de Registro | Permissões Principais |
|-------|-------------------|----------------------|
| Mestre de Obra | Cadastro por convite | Criar e editar RDOs, registrar atividades, gerenciar mão de obra |
| Engenheiro | Cadastro por convite | Todas as permissões + aprovar RDOs, gerar relatórios |
| Gestor | Cadastro administrativo | Visualizar todas as obras, relatórios consolidados, gerenciar usuários |
| Cliente | Acesso por convite | Visualizar progresso da obra, relatórios aprovados |
### 2.2 Módulos Funcionais
Nosso aplicativo RDO Mobile consiste nas seguintes páginas principais:
1. **Dashboard Principal**: visão geral das obras, indicadores de progresso, últimas atividades
2. **Detalhes da Obra**: informações específicas da obra, histórico de RDOs, galeria de fotos
3. **Criar RDO**: formulário completo para registro diário de atividades
4. **Lista de Tarefas**: gerenciamento de atividades planejadas e executadas
5. **Cadastros**: formulários para obras, usuários, equipamentos e tipos de atividades
6. **Relatórios**: visualização e exportação de relatórios consolidados
### 2.3 Detalhes das Páginas
| Nome da Página | Nome do Módulo | Descrição da Funcionalidade |
|----------------|----------------|-----------------------------|
| Dashboard Principal | Visão Geral | Exibir cards das obras ativas, indicadores de progresso, últimos RDOs criados, notificações importantes |
| Dashboard Principal | Navegação Rápida | Acesso rápido para criar novo RDO, visualizar obras, acessar cadastros |
| Detalhes da Obra | Informações da Obra | Mostrar dados da obra, cronograma, responsáveis, localização |
| Detalhes da Obra | Histórico RDO | Listar RDOs anteriores com filtros por data, status, responsável |
| Detalhes da Obra | Galeria de Fotos | Visualizar fotos organizadas por data, com zoom e compartilhamento |
| Criar RDO | Informações Básicas | Campos para data, obra, responsável, condições climáticas |
| Criar RDO | Atividades Executadas | Adicionar atividades com descrição, localização, percentual concluído |
| Criar RDO | Mão de Obra | Registrar funcionários presentes, horas trabalhadas, função |
| Criar RDO | Equipamentos | Listar equipamentos utilizados, horas de operação, combustível |
| Criar RDO | Ocorrências | Registrar problemas, acidentes, atrasos com descrição detalhada |
| Criar RDO | Anexos | Adicionar fotos, documentos, assinaturas digitais |
| Lista de Tarefas | Tarefas Planejadas | Visualizar atividades programadas para o dia/semana |
| Lista de Tarefas | Controle de Execução | Marcar tarefas como iniciadas, em andamento, concluídas |
| Lista de Tarefas | Progresso Visual | Mostrar percentual de conclusão com barras de progresso |
| Cadastros | Cadastro de Obras | Formulário com dados da obra, endereço, responsáveis, cronograma |
| Cadastros | Cadastro de Usuários | Registrar funcionários com foto, função, permissões |
| Cadastros | Cadastro de Equipamentos | Listar equipamentos com especificações, manutenção, disponibilidade |
| Cadastros | Tipos de Atividades | Definir categorias de atividades padrão para seleção rápida |
| Relatórios | Relatórios Consolidados | Gerar relatórios por período, obra, tipo de atividade |
| Relatórios | Exportação | Exportar relatórios em PDF, Excel, compartilhar por email/WhatsApp |
## 3. Fluxo Principal
**Fluxo do Mestre de Obra:**
1. Acessa o dashboard e visualiza as obras sob sua responsabilidade
2. Seleciona uma obra específica para trabalhar
3. Cria um novo RDO diário preenchendo todas as seções obrigatórias
4. Adiciona fotos e registra ocorrências se necessário
5. Submete o RDO para aprovação do engenheiro
6. Acompanha o status das tarefas planejadas
**Fluxo do Engenheiro:**
1. Revisa RDOs pendentes de aprovação
2. Analisa o progresso geral das obras no dashboard
3. Gera relatórios consolidados para apresentação
4. Gerencia cadastros de equipamentos e atividades
```mermaid
graph TD
A[Dashboard Principal] --> B[Selecionar Obra]
B --> C[Detalhes da Obra]
C --> D[Criar Novo RDO]
C --> E[Lista de Tarefas]
C --> F[Histórico RDOs]
D --> G[Preencher Atividades]
G --> H[Registrar Mão de Obra]
H --> I[Adicionar Equipamentos]
I --> J[Registrar Ocorrências]
J --> K[Anexar Fotos]
K --> L[Submeter RDO]
A --> M[Cadastros]
M --> N[Obras]
M --> O[Usuários]
M --> P[Equipamentos]
A --> Q[Relatórios]
```
## 4. Design da Interface do Usuário
### 4.1 Estilo de Design
- **Cores Primárias**: Azul #2563EB (confiança, profissionalismo), Laranja #F97316 (energia, ação)
- **Cores Secundárias**: Cinza #64748B (neutro), Verde #10B981 (sucesso), Vermelho #EF4444 (alertas)
- **Estilo dos Botões**: Neumorphism com bordas arredondadas (12px), sombras sutis, efeitos de pressão
- **Tipografia**: Inter (títulos 18-24px), Open Sans (corpo 14-16px), peso regular e semi-bold
- **Layout**: Cards com Glassmorphism, navegação bottom tab, espaçamentos de 16px/24px
- **Ícones**: Phosphor Icons com estilo outline, tamanho 24px para ações principais
### 4.2 Visão Geral do Design das Páginas
| Nome da Página | Nome do Módulo | Elementos da UI |
|----------------|----------------|----------------|
| Dashboard Principal | Cards de Obras | Cards com glassmorphism, gradiente sutil azul-roxo, sombra 0 4px 20px rgba(0,0,0,0.1), bordas arredondadas 16px |
| Dashboard Principal | Indicadores | Gráficos circulares com animação, cores verde/amarelo/vermelho para status, fonte Inter 14px |
| Dashboard Principal | Navegação | Bottom navigation com 5 tabs, ícones Phosphor, background blur, altura 80px |
| Criar RDO | Formulário | Floating labels com animação, campos com border-radius 12px, validação em tempo real com cores |
| Criar RDO | Seções Expansíveis | Accordion com ícones de seta, transição suave 300ms, background rgba(255,255,255,0.1) |
| Lista de Tarefas | Cards de Tarefa | Swipe actions (verde para concluir, vermelho para excluir), checkbox animado, progress bar |
| Lista de Tarefas | Filtros | Chips com seleção múltipla, cores Material Design 3, espaçamento 8px |
| Cadastros | Formulários | Stepper horizontal, campos agrupados em cards, botões flutuantes para ações |
| Relatórios | Gráficos | Charts.js com tema dark/light, cores consistentes, animações de entrada |
### 4.3 Responsividade
**Mobile-first** com adaptação para tablets. Layouts flexíveis usando CSS Grid e Flexbox, tamanhos em rem (base 16px), espaçamentos escaláveis (16px/24px/32px). Safe areas para dispositivos com notch/dynamic island. Suporte a orientação portrait e landscape para tablets.
**Gestos de Navegação:**
- Swipe para direita: voltar à tela anterior
- Swipe para baixo: pull-to-refresh nas listas
- Swipe horizontal em cards: ações rápidas (editar/excluir)
- Long press: menu contextual
- Pinch to zoom: galeria de fotos
**Dark Mode Automático:**
Detecção da preferência do sistema com toggle manual. Paleta adaptada: backgrounds #1F2937, cards #374151, textos #F9FAFB, acentos mantidos com ajuste de saturação.

View File

@@ -1 +0,0 @@
{"projectId":"prj_yIejbjdWxs5ZZ5YWOEHm8UAqDS8k","orgId":"team_KY59uhtbyLyWDtIuz7Ew3uEk","projectName":"trae_42fqthkv","neverMindDeployCard":true}

View File

@@ -1,7 +1,51 @@
# Dependencies
node_modules node_modules
.pnp
.pnp.js
# Testing
coverage
# Production
build build
dist dist
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# IDE
.vscode
.idea
*.swp
*.swo
*~
# Capacitor
ios/
android/
# Scripts
scripts/
# Documentation
documentation/
.trae/
# Database scripts
database_scripts/
*.sql
# Git
.git .git
.trae .gitignore
.log .gitattributes
.figma

View File

@@ -1,162 +0,0 @@
# ✅ CHECKLIST NETLIFY - CONFIGURAÇÃO COMPLETA
## STATUS ATUAL
- ✅ SQL executado com sucesso (2 usuários no banco)
- ✅ Políticas RLS configuradas
- ⚠️ Login ainda não funciona no Netlify
---
## 🔧 CONFIGURAÇÕES NECESSÁRIAS
### 1⃣ VARIÁVEIS DE AMBIENTE NO NETLIFY
**CRÍTICO:** O Netlify NÃO lê o arquivo `.env` local!
#### Como configurar:
1. Acesse: https://app.netlify.com
2. Clique no seu site
3. Vá em: **Site settings****Environment variables**
4. Clique em **Add a variable**
5. Adicione estas 2 variáveis:
```
Key: VITE_SUPABASE_URL
Value: https://xzudfhifaancyxxfdejx.supabase.co
Key: VITE_SUPABASE_ANON_KEY
Value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh6dWRmaGlmYWFuY3l4eGZkZWp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjE0MTAsImV4cCI6MjA4NjkzNzQxMH0.c5CHWhfXcMrm27LfxEt6OZtttXXvVJOeWu-IbnNLfWY
```
6. **IMPORTANTE:** Após adicionar, clique em **Trigger deploy** para fazer um novo deploy
---
### 2⃣ REDIRECT URLs NO SUPABASE
1. Acesse: https://supabase.com/dashboard/project/xzudfhifaancyxxfdejx/auth/url-configuration
2. Na seção **Redirect URLs**, adicione (substitua `SEU-SITE` pelo domínio real):
```
https://SEU-SITE.netlify.app/auth/callback
https://SEU-SITE.netlify.app/*
```
**Exemplo:**
```
https://rdo-tracksteel.netlify.app/auth/callback
https://rdo-tracksteel.netlify.app/*
```
3. Na seção **Site URL**, configure:
```
https://SEU-SITE.netlify.app
```
4. Clique em **Save**
---
### 3⃣ GOOGLE CLOUD CONSOLE (OAuth)
1. Acesse: https://console.cloud.google.com/apis/credentials
2. Clique no seu **OAuth 2.0 Client ID**
3. Em **Authorized redirect URIs**, adicione:
```
https://xzudfhifaancyxxfdejx.supabase.co/auth/v1/callback
https://SEU-SITE.netlify.app/auth/callback
```
4. Clique em **Save**
---
## 🧪 COMO TESTAR
### Teste 1: Verificar Variáveis de Ambiente
Após fazer o deploy no Netlify:
1. Abra o site no Netlify
2. Pressione **F12** (Console do navegador)
3. Digite:
```javascript
console.log('URL:', import.meta.env.VITE_SUPABASE_URL)
console.log('Key:', import.meta.env.VITE_SUPABASE_ANON_KEY?.substring(0, 20) + '...')
```
**Resultado esperado:**
```
URL: https://xzudfhifaancyxxfdejx.supabase.co
Key: eyJhbGciOiJIUzI1NiIs...
```
**Se retornar `undefined`** = Variáveis não configuradas no Netlify!
---
### Teste 2: Verificar Login
1. Clique em "Login com Google"
2. Autorize o app
3. Deve redirecionar para `/auth/callback`
4. Deve fazer login com sucesso
---
## 🔍 DIAGNÓSTICO DE ERROS
### Erro: "Invalid redirect URL"
- ❌ Faltou adicionar URL no Supabase (passo 2)
- ❌ Faltou adicionar URI no Google Cloud (passo 3)
### Erro: "Environment variables not defined"
- ❌ Faltou configurar variáveis no Netlify (passo 1)
- ❌ Faltou fazer novo deploy após configurar
### Erro: 401 Unauthorized
- ✅ Já resolvido com o SQL que você executou!
### Login funciona mas volta para tela de login
- ❌ Variáveis de ambiente não configuradas
- ❌ Redirect URLs não configuradas
---
## 📋 ORDEM DE EXECUÇÃO
1.**SQL executado** (você já fez!)
2.**Configurar variáveis no Netlify** (passo 1)
3.**Fazer novo deploy no Netlify**
4.**Configurar Redirect URLs no Supabase** (passo 2)
5.**Configurar OAuth no Google Cloud** (passo 3)
6.**Testar login**
---
## 🎯 QUAL É O SEU DOMÍNIO NETLIFY?
Para eu te dar os comandos exatos, me informe:
**Qual é a URL do seu site no Netlify?**
Exemplo: `https://rdo-tracksteel.netlify.app`
Com essa informação, posso te dar os valores exatos para copiar e colar!
---
## 💡 DICA RÁPIDA
Se você ainda não sabe a URL do Netlify:
1. Acesse: https://app.netlify.com
2. Clique no seu site
3. A URL está no topo da página (ex: `https://nome-do-site.netlify.app`)

View File

@@ -1,172 +0,0 @@
# 🎯 CONFIGURAÇÃO EXATA - rdo.tracksteel.com.br
## ✅ VALORES PRONTOS PARA COPIAR E COLAR
---
## 1⃣ NETLIFY - VARIÁVEIS DE AMBIENTE
### Acesse:
https://app.netlify.com → Seu Site → Site settings → Environment variables
### Adicione estas 2 variáveis:
**Variável 1:**
```
Key: VITE_SUPABASE_URL
Value: https://xzudfhifaancyxxfdejx.supabase.co
```
**Variável 2:**
```
Key: VITE_SUPABASE_ANON_KEY
Value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh6dWRmaGlmYWFuY3l4eGZkZWp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjE0MTAsImV4cCI6MjA4NjkzNzQxMH0.c5CHWhfXcMrm27LfxEt6OZtttXXvVJOeWu-IbnNLfWY
```
### ⚠️ IMPORTANTE:
Após adicionar as variáveis, clique em **"Trigger deploy"** para fazer um novo deploy!
---
## 2⃣ SUPABASE - REDIRECT URLs
### Acesse:
https://supabase.com/dashboard/project/xzudfhifaancyxxfdejx/auth/url-configuration
### Na seção "Redirect URLs", adicione estas URLs (uma por linha):
```
https://rdo.tracksteel.com.br/auth/callback
https://rdo.tracksteel.com.br/*
http://localhost:5173/auth/callback
http://localhost:3000/auth/callback
```
### Na seção "Site URL", configure:
```
https://rdo.tracksteel.com.br
```
### Clique em **Save**
---
## 3⃣ GOOGLE CLOUD CONSOLE - OAUTH
### Acesse:
https://console.cloud.google.com/apis/credentials
### Clique no seu OAuth 2.0 Client ID
### Na seção "Authorized redirect URIs", adicione estas URIs:
```
https://xzudfhifaancyxxfdejx.supabase.co/auth/v1/callback
https://rdo.tracksteel.com.br/auth/callback
http://localhost:5173/auth/callback
http://localhost:3000/auth/callback
```
### Clique em **Save**
---
## 4⃣ TESTE APÓS CONFIGURAR
### Teste 1: Verificar Variáveis de Ambiente
1. Abra: https://rdo.tracksteel.com.br
2. Pressione **F12** (Console do navegador)
3. Cole este código:
```javascript
console.log('URL:', import.meta.env.VITE_SUPABASE_URL)
console.log('Key:', import.meta.env.VITE_SUPABASE_ANON_KEY?.substring(0, 30) + '...')
```
**Resultado esperado:**
```
URL: https://xzudfhifaancyxxfdejx.supabase.co
Key: eyJhbGciOiJIUzI1NiIsInR5cCI6Ik...
```
**Se retornar `undefined`:**
- ❌ Variáveis não foram configuradas no Netlify
- ❌ Ou você não fez o novo deploy após configurar
---
### Teste 2: Login com Google
1. Acesse: https://rdo.tracksteel.com.br
2. Clique em **"Login com Google"**
3. Autorize o app
4. Deve redirecionar para: `https://rdo.tracksteel.com.br/auth/callback`
5. Deve fazer login com sucesso e ir para a página inicial
---
## 📋 CHECKLIST DE EXECUÇÃO
- [ ] 1. Configurar variáveis no Netlify
- [ ] 2. Fazer novo deploy no Netlify (Trigger deploy)
- [ ] 3. Aguardar deploy finalizar (2-3 minutos)
- [ ] 4. Configurar Redirect URLs no Supabase
- [ ] 5. Configurar OAuth no Google Cloud
- [ ] 6. Testar variáveis de ambiente (Teste 1)
- [ ] 7. Testar login com Google (Teste 2)
---
## 🔍 DIAGNÓSTICO DE PROBLEMAS
### Problema: Variáveis retornam `undefined`
**Solução:**
1. Verifique se adicionou as variáveis no Netlify
2. Verifique se fez o novo deploy
3. Aguarde o deploy finalizar completamente
4. Limpe o cache do navegador (Ctrl+Shift+Delete)
### Problema: "Invalid redirect URL"
**Solução:**
1. Verifique se adicionou as URLs no Supabase (passo 2)
2. Verifique se adicionou as URIs no Google Cloud (passo 3)
3. Aguarde 1-2 minutos para as configurações propagarem
### Problema: Erro 401 após login
**Solução:**
- ✅ Já resolvido! Você executou o SQL corretamente
### Problema: Login funciona mas volta para tela de login
**Solução:**
1. Verifique se as variáveis de ambiente estão configuradas
2. Verifique se o novo deploy foi feito
3. Limpe o cache do navegador
---
## 🎯 RESUMO RÁPIDO
**3 lugares para configurar:**
1. **Netlify** → Variáveis de ambiente + Novo deploy
2. **Supabase** → Redirect URLs
3. **Google Cloud** → Redirect URIs
**Tempo estimado:** 10-15 minutos
---
## 💡 DICA IMPORTANTE
Após configurar tudo, aguarde 2-3 minutos para:
- Deploy do Netlify finalizar
- Configurações do Supabase propagarem
- Configurações do Google Cloud propagarem
Depois teste o login!
---
**Qualquer dúvida, me avise em qual passo você está!**

View File

@@ -1,211 +0,0 @@
# 🔧 CORREÇÃO: Autenticação no Netlify
## 🎯 PROBLEMA IDENTIFICADO
O app funciona localmente (localhost:5173 e :3000) mas falha ao autenticar no Netlify devido a:
1. ❌ Variáveis de ambiente não configuradas no Netlify
2. ❌ URLs de callback OAuth não configuradas no Supabase
3. ❌ Políticas RLS bloqueando acesso após OAuth
---
## ✅ SOLUÇÃO COMPLETA
### 1⃣ CONFIGURAR VARIÁVEIS DE AMBIENTE NO NETLIFY
Acesse: https://app.netlify.com → Seu Site → Site settings → Environment variables
Adicione estas variáveis:
```
VITE_SUPABASE_URL=https://xzudfhifaancyxxfdejx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh6dWRmaGlmYWFuY3l4eGZkZWp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjE0MTAsImV4cCI6MjA4NjkzNzQxMH0.c5CHWhfXcMrm27LfxEt6OZtttXXvVJOeWu-IbnNLfWY
```
**IMPORTANTE:** Após adicionar, faça um novo deploy!
---
### 2⃣ CONFIGURAR REDIRECT URLs NO SUPABASE
Acesse: https://supabase.com/dashboard/project/xzudfhifaancyxxfdejx/auth/url-configuration
Na seção **Redirect URLs**, adicione:
```
https://SEU-SITE.netlify.app/auth/callback
https://SEU-SITE.netlify.app/*
```
**Exemplo:**
```
https://rdo-tracksteel.netlify.app/auth/callback
https://rdo-tracksteel.netlify.app/*
```
Na seção **Site URL**, configure:
```
https://SEU-SITE.netlify.app
```
---
### 3⃣ CORRIGIR POLÍTICAS RLS PARA OAUTH
O problema principal: quando um usuário faz login via Google OAuth, o Supabase cria o registro em `auth.users`, mas a política RLS impede a criação automática do perfil em `public.usuarios`.
Execute este SQL no Supabase (SQL Editor):
```sql
-- ============================================================================
-- CORREÇÃO RLS PARA OAUTH - PERMITIR CRIAÇÃO AUTOMÁTICA DE PERFIL
-- ============================================================================
-- 1. Permitir INSERT na tabela usuarios para usuários autenticados
DROP POLICY IF EXISTS "Users can create own profile" ON public.usuarios;
CREATE POLICY "Users can create own profile" ON public.usuarios
FOR INSERT
WITH CHECK (auth.uid() = id);
-- 2. Garantir que o trigger handle_new_user funciona
-- Verificar se a função existe
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
-- Inserir novo usuário na tabela public.usuarios
INSERT INTO public.usuarios (
id,
email,
nome,
role,
ativo
) VALUES (
NEW.id,
NEW.email,
COALESCE(NEW.raw_user_meta_data->>'nome', NEW.raw_user_meta_data->>'name', split_part(NEW.email, '@', 1)),
'usuario',
true
)
ON CONFLICT (id) DO UPDATE SET
email = EXCLUDED.email,
nome = COALESCE(EXCLUDED.nome, public.usuarios.nome),
updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 3. Garantir que o trigger está ativo
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.handle_new_user();
-- 4. Permitir leitura de organizações para usuários sem organização (signup)
DROP POLICY IF EXISTS "Users can view orgs during signup" ON public.organizacoes;
CREATE POLICY "Users can view orgs during signup" ON public.organizacoes
FOR SELECT
USING (auth.uid() IS NOT NULL);
-- 5. Permitir criação de organização para novos usuários
DROP POLICY IF EXISTS "Users can create org" ON public.organizacoes;
CREATE POLICY "Users can create org" ON public.organizacoes
FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
-- ============================================================================
-- FIM DA CORREÇÃO
-- ============================================================================
```
---
### 4⃣ VERIFICAR CONFIGURAÇÃO DO GOOGLE OAUTH
No Google Cloud Console (https://console.cloud.google.com):
1. Acesse: APIs & Services → Credentials
2. Clique no seu OAuth 2.0 Client ID
3. Em **Authorized redirect URIs**, adicione:
```
https://xzudfhifaancyxxfdejx.supabase.co/auth/v1/callback
https://SEU-SITE.netlify.app/auth/callback
```
---
## 🧪 TESTAR A CORREÇÃO
### Teste 1: Variáveis de Ambiente
```bash
# No console do navegador (F12) no site Netlify:
console.log(import.meta.env.VITE_SUPABASE_URL)
```
Deve retornar: `https://xzudfhifaancyxxfdejx.supabase.co`
### Teste 2: Login OAuth
1. Acesse seu site no Netlify
2. Clique em "Login com Google"
3. Autorize o app
4. Deve redirecionar e autenticar com sucesso
### Teste 3: Verificar Perfil Criado
No Supabase SQL Editor:
```sql
SELECT * FROM public.usuarios WHERE email = 'seu-email@gmail.com';
```
---
## 🔍 DIAGNÓSTICO DE PROBLEMAS
### Erro: "Invalid redirect URL"
- ✅ Verifique se adicionou a URL no Supabase (passo 2)
- ✅ Verifique se adicionou no Google Cloud (passo 4)
### Erro: "User not found" ou "Permission denied"
- ✅ Execute o SQL de correção RLS (passo 3)
- ✅ Verifique se o trigger `handle_new_user` está ativo
### Erro: "Environment variables not defined"
- ✅ Configure variáveis no Netlify (passo 1)
- ✅ Faça um novo deploy após adicionar
### Login funciona mas não carrega dados
- ✅ Problema de RLS - execute o SQL do passo 3
- ✅ Verifique se o usuário foi criado em `public.usuarios`
---
## 📋 CHECKLIST FINAL
- [ ] Variáveis de ambiente configuradas no Netlify
- [ ] Novo deploy realizado após configurar variáveis
- [ ] Redirect URLs adicionadas no Supabase
- [ ] Redirect URIs adicionadas no Google Cloud
- [ ] SQL de correção RLS executado no Supabase
- [ ] Teste de login OAuth realizado com sucesso
- [ ] Perfil do usuário criado em `public.usuarios`
---
## 🎯 RESULTADO ESPERADO
Após seguir todos os passos:
✅ Login via Google funciona no Netlify
✅ Perfil do usuário é criado automaticamente
✅ Usuário consegue acessar o app normalmente
✅ RLS permite acesso aos dados da organização
---
**Data:** 24/02/2026
**Status:** Aguardando aplicação das correções

View File

@@ -0,0 +1,104 @@
# 🧪 TESTE DE VARIÁVEIS NO NAVEGADOR
## ❌ ERRO NO CONSOLE
O comando `import.meta.env` NÃO funciona no console do navegador!
Ele só funciona dentro do código da aplicação durante o build.
---
## ✅ COMO TESTAR CORRETAMENTE
### Opção 1: Verificar no código fonte
1. Abra: https://rdo.tracksteel.com.br
2. Pressione **F12** (DevTools)
3. Vá na aba **Sources** ou **Debugger**
4. Procure por arquivos `.js` no painel esquerdo
5. Abra qualquer arquivo e procure por `xzudfhifaancyxxfdejx`
6. Se encontrar = ✅ Variáveis configuradas!
7. Se NÃO encontrar = ❌ Variáveis não foram injetadas no build
---
### Opção 2: Verificar no Network
1. Abra: https://rdo.tracksteel.com.br
2. Pressione **F12** (DevTools)
3. Vá na aba **Network**
4. Clique em "Login com Google"
5. Procure por requisições para `xzudfhifaancyxxfdejx.supabase.co`
6. Se aparecer = ✅ Variáveis configuradas!
7. Se NÃO aparecer = ❌ Variáveis não foram injetadas
---
### Opção 3: Adicionar console.log temporário
Adicione este código no arquivo `src/lib/supabase.ts` (linha 6):
```typescript
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
// ADICIONAR ESTAS LINHAS TEMPORÁRIAS:
console.log('🔍 SUPABASE URL:', supabaseUrl)
console.log('🔍 SUPABASE KEY:', supabaseAnonKey?.substring(0, 30) + '...')
```
Depois faça commit, push e aguarde o deploy no Netlify.
Quando abrir o site, vai aparecer no console!
---
## 🚨 PROBLEMA IDENTIFICADO
O erro **401 Unauthorized** com `grant_type=pkce` indica que:
1. ✅ As variáveis de ambiente ESTÃO configuradas (senão daria outro erro)
2. ❌ O flowType estava configurado como `pkce` que não funciona bem em produção
3. ✅ JÁ CORRIGI mudando para `implicit`
---
## 📋 PRÓXIMOS PASSOS
1.**Código corrigido** (mudei flowType para implicit)
2.**Push feito** para o GitHub
3.**Aguardar deploy** no Netlify (2-3 minutos)
4. 🧪 **Testar login** novamente
---
## ⏰ AGUARDE O DEPLOY
O Netlify precisa fazer o build novamente com o código corrigido.
**Como verificar se o deploy terminou:**
1. Acesse: https://app.netlify.com
2. Clique no seu site
3. Vá em **Deploys**
4. Aguarde o deploy mais recente ficar **"Published"** (verde)
5. Depois teste o login!
---
## 🎯 TESTE APÓS O DEPLOY
1. Abra: https://rdo.tracksteel.com.br
2. Limpe o cache (Ctrl+Shift+Delete → Limpar tudo)
3. Recarregue a página (Ctrl+F5)
4. Clique em "Login com Google"
5. Deve funcionar agora!
---
## 🔍 SE AINDA DER ERRO
Me envie print do console (F12) mostrando:
- Aba **Console** (mensagens de erro)
- Aba **Network** (requisições falhando)

8
TS_RDO.code-workspace Normal file
View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "TS_RDO"
}
],
"settings": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{j as e}from"./query-vendor-BLVqILA6.js";import{d as w,r as t}from"./router-vendor-D4by-_6Z.js";import{s as i}from"./index-doec96Hx.js";import{a0 as j,c as b,a2 as N}from"./ui-vendor-CyRvbSfR.js";import"./react-vendor-CqRd3GwO.js";import"./supabase-vendor-CnnNSQLo.js";import"./state-vendor-DHadhBU5.js";const _=()=>{const o=w(),[s,c]=t.useState("loading"),[g,n]=t.useState("Processando autenticação..."),l=t.useCallback(async()=>{try{const a=new URLSearchParams(window.location.search),d=a.get("error"),x=a.get("error_description");if(d)throw new Error(x||d);const{data:{session:r},error:m}=await i.auth.getSession();if(m)throw m;if(!r)throw new Error("Nenhuma sessão encontrada");const{data:u,error:h}=await i.from("usuarios").select("id, organizacao_id").eq("id",r.user.id).single();if(!u&&h?.code==="PGRST116"){const{error:p}=await i.from("usuarios").insert({id:r.user.id,email:r.user.email,nome:r.user.user_metadata.full_name||r.user.email?.split("@")[0]})}c("success"),n("Login realizado com sucesso!");const f=u?.organizacao_id;setTimeout(()=>{o(f?"/":"/selecionar-organizacao")},1e3)}catch(a){c("error"),n(a instanceof Error?a.message:"Erro ao processar autenticação"),setTimeout(()=>{o("/login")},3e3)}},[o]);return t.useEffect(()=>{l()},[l]),e.jsx("div",{className:"min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4",children:e.jsx("div",{className:"bg-white rounded-2xl shadow-xl p-8 max-w-md w-full",children:e.jsxs("div",{className:"flex flex-col items-center space-y-4",children:[s==="loading"&&e.jsx(j,{className:"w-16 h-16 text-blue-500 animate-spin"}),s==="success"&&e.jsx(b,{className:"w-16 h-16 text-green-500"}),s==="error"&&e.jsx(N,{className:"w-16 h-16 text-red-500"}),e.jsxs("div",{className:"text-center",children:[e.jsxs("h2",{className:"text-2xl font-bold text-gray-900 mb-2",children:[s==="loading"&&"Processando...",s==="success"&&"Sucesso!",s==="error"&&"Erro"]}),e.jsx("p",{className:"text-gray-600",children:g})]}),s==="loading"&&e.jsx("div",{className:"w-full bg-gray-200 rounded-full h-2 overflow-hidden",children:e.jsx("div",{className:"bg-blue-500 h-2 rounded-full animate-pulse w-2/3"})}),s!=="loading"&&e.jsx("p",{className:"text-sm text-gray-500",children:s==="success"?"Redirecionando para o sistema...":"Redirecionando para o login..."})]})})})};export{_ as AuthCallback};

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

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

View File

@@ -1 +0,0 @@
import{j as M}from"./query-vendor-BLVqILA6.js";import{r as c}from"./router-vendor-D4by-_6Z.js";const E=()=>{const l=c.useRef(null),h=c.useRef(),n=c.useRef([]);return c.useEffect(()=>{const e=l.current;if(!e)return;const r=e.getContext("2d");if(!r)return;const f=()=>{e.width=window.innerWidth,e.height=window.innerHeight},u=()=>{const t=Math.floor(e.width*e.height/15e3);n.current=[];for(let a=0;a<t;a++)n.current.push({x:Math.random()*e.width,y:Math.random()*e.height,vx:(Math.random()-.5)*.5,vy:(Math.random()-.5)*.5})},g=t=>{r.beginPath(),r.arc(t.x,t.y,2,0,Math.PI*2),r.fillStyle="rgba(147, 197, 253, 0.8)",r.fill()},y=(t,a,i,o)=>{const s=1-i/o;r.beginPath(),r.moveTo(t.x,t.y),r.lineTo(a.x,a.y),r.strokeStyle=`rgba(147, 197, 253, ${s*.3})`,r.lineWidth=1,r.stroke()},v=()=>{n.current.forEach(t=>{t.x+=t.vx,t.y+=t.vy,(t.x<0||t.x>e.width)&&(t.vx*=-1),(t.y<0||t.y>e.height)&&(t.vy*=-1),t.x=Math.max(0,Math.min(e.width,t.x)),t.y=Math.max(0,Math.min(e.height,t.y))})},x=()=>{r.clearRect(0,0,e.width,e.height);const t=r.createLinearGradient(0,0,e.width,e.height);t.addColorStop(0,"#0f172a"),t.addColorStop(.5,"#1e1b4b"),t.addColorStop(1,"#581c87"),r.fillStyle=t,r.fillRect(0,0,e.width,e.height),v();const a=120;for(let i=0;i<n.current.length;i++)for(let o=i+1;o<n.current.length;o++){const s=n.current[i],d=n.current[o],w=Math.sqrt(Math.pow(s.x-d.x,2)+Math.pow(s.y-d.y,2));w<a&&y(s,d,w,a)}n.current.forEach(g),h.current=requestAnimationFrame(x)};f(),u(),x();const m=()=>{f(),u()};return window.addEventListener("resize",m),()=>{window.removeEventListener("resize",m),h.current&&cancelAnimationFrame(h.current)}},[]),M.jsx("canvas",{ref:l,className:"fixed inset-0 w-full h-full -z-10",style:{background:"linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #581c87 100%)"}})};export{E as N};

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

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{j as a}from"./query-vendor-BLVqILA6.js";import{r as o}from"./router-vendor-D4by-_6Z.js";import{m as s,I as l,am as i}from"./ui-vendor-CyRvbSfR.js";function n(){const[t,e]=o.useState(()=>{const r=localStorage.getItem("theme");return r||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")});return o.useEffect(()=>{document.documentElement.classList.remove("light","dark"),document.documentElement.classList.add(t),localStorage.setItem("theme",t)},[t]),{theme:t,toggleTheme:()=>{e(r=>r==="light"?"dark":"light")},isDark:t==="dark"}}function h(){const{toggleTheme:t,isDark:e}=n();return a.jsx(s.button,{onClick:t,className:"relative p-2 rounded-xl bg-white/70 dark:bg-gray-800/70 backdrop-blur-md border border-gray-200/50 dark:border-gray-700/50 shadow-lg hover:shadow-xl transition-all duration-300 group",whileHover:{scale:1.05},whileTap:{scale:.95},title:e?"Alternar para modo claro":"Alternar para modo escuro",children:a.jsxs("div",{className:"relative w-6 h-6",children:[a.jsx(s.div,{initial:!1,animate:{scale:e?0:1,rotate:e?180:0,opacity:e?0:1},transition:{duration:.3,ease:"easeInOut"},className:"absolute inset-0 flex items-center justify-center",children:a.jsx(l,{className:"w-5 h-5 text-yellow-500 group-hover:text-yellow-600"})}),a.jsx(s.div,{initial:!1,animate:{scale:e?1:0,rotate:e?0:-180,opacity:e?1:0},transition:{duration:.3,ease:"easeInOut"},className:"absolute inset-0 flex items-center justify-center",children:a.jsx(i,{className:"w-5 h-5 text-blue-400 group-hover:text-blue-300"})})]})})}export{h as T};

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +0,0 @@
import{r as d}from"./router-vendor-D4by-_6Z.js";import{s as n}from"./index-doec96Hx.js";const w=()=>{const[g,s]=d.useState(!1),[m,t]=d.useState(null);return{loading:g,error:m,validarConvite:async i=>{try{s(!0),t(null);const e=i.toUpperCase().trim(),{data:r,error:a}=await n.from("convites").select(`
id,
codigo,
organizacao_id,
role,
max_usos,
usos_atuais,
ativo,
expira_em,
email_convidado,
organizacoes:organizacao_id (nome)
`).eq("codigo",e).eq("ativo",!0).single();if(a||!r)return{success:!1,error:"Código de convite inválido ou expirado."};const o=r;return o.expira_em&&new Date(o.expira_em)<new Date?{success:!1,error:"Este código de convite expirou."}:o.max_usos>0&&o.usos_atuais>=o.max_usos?{success:!1,error:"Este código de convite já atingiu o limite de usos."}:{success:!0,organizacao_id:o.organizacao_id,organizacao_nome:o.organizacoes?.nome||"Organização",role:o.role}}catch(e){const r=e instanceof Error?e.message:"Erro ao validar convite";return t(r),{success:!1,error:r}}finally{s(!1)}},usarConvite:async(i,e)=>{try{s(!0),t(null);const{data:r,error:a}=await n.rpc("usar_convite",{p_codigo:i,p_usuario_id:e});if(a)throw a;const o=r;return o.success||t(o.error||"Erro ao usar convite"),o}catch(r){const a=r instanceof Error?r.message:"Erro ao usar convite";return t(a),{success:!1,error:a}}finally{s(!1)}},gerarConvite:async(i,e={})=>{try{s(!0),t(null);const{data:r,error:a}=await n.rpc("gerar_codigo_convite");if(a)throw a;const o=r;let u=null;if(e.expiraEmDias){const c=new Date;c.setDate(c.getDate()+e.expiraEmDias),u=c.toISOString()}const{data:{user:f}}=await n.auth.getUser(),{error:l}=await n.from("convites").insert({organizacao_id:i,codigo:o,criado_por:f?.id,email_convidado:e.emailConvidado||null,role:e.role||"usuario",max_usos:e.maxUsos??1,expira_em:u});if(l)throw l;return{success:!0,codigo:o}}catch(r){const a=r instanceof Error?r.message:"Erro ao gerar convite";return t(a),{success:!1,error:a}}finally{s(!1)}},listarConvites:async i=>{try{s(!0);const{data:e,error:r}=await n.from("convites").select("*").eq("organizacao_id",i).order("created_at",{ascending:!1});if(r)throw r;return e||[]}catch{return[]}finally{s(!1)}}}};export{w as u};

View File

@@ -1 +0,0 @@
import{d,p as U,s as o}from"./index-doec96Hx.js";import{c as g}from"./state-vendor-DHadhBU5.js";import"./query-vendor-BLVqILA6.js";import"./router-vendor-D4by-_6Z.js";import"./react-vendor-CqRd3GwO.js";import"./ui-vendor-CyRvbSfR.js";import"./supabase-vendor-CnnNSQLo.js";const i={currentUser:null,users:[],loading:!1,error:null},m=g()(d(U((r,u)=>({...i,setCurrentUser:e=>r({currentUser:e},!1,"setCurrentUser"),setUsers:e=>r({users:e},!1,"setUsers"),setLoading:e=>r({loading:e},!1,"setLoading"),setError:e=>r({error:e},!1,"setError"),clearError:()=>r({error:null},!1,"clearError"),reset:()=>r(i,!1,"reset"),fetchCurrentUser:async e=>{try{r({loading:!0,error:null},!1,"fetchCurrentUser:start");const{data:s,error:a}=await o.from("usuarios").select("*").eq("id",e).single();if(a)throw a;r({currentUser:s,loading:!1},!1,"fetchCurrentUser:success")}catch(s){r({error:s.message||"Erro ao buscar usuário",loading:!1},!1,"fetchCurrentUser:error")}},fetchUsers:async()=>{try{r({loading:!0,error:null},!1,"fetchUsers:start");const{data:e,error:s}=await o.from("usuarios").select("*").order("nome");if(s)throw s;r({users:e||[],loading:!1},!1,"fetchUsers:success")}catch(e){r({error:e.message||"Erro ao buscar usuários",loading:!1},!1,"fetchUsers:error")}},updateUser:async(e,s)=>{try{r({loading:!0,error:null},!1,"updateUser:start");const{data:a,error:t}=await o.from("usuarios").update({...s,updated_at:new Date().toISOString()}).eq("id",e).select().single();if(t)throw t;const{users:n,currentUser:l}=u(),f=n.map(c=>c.id===e?a:c);return r({users:f,currentUser:l?.id===e?a:l,loading:!1},!1,"updateUser:success"),!0}catch(a){return r({error:a.message||"Erro ao atualizar usuário",loading:!1},!1,"updateUser:error"),!1}},createUser:async e=>{try{r({loading:!0,error:null},!1,"createUser:start");const{data:s,error:a}=await o.from("usuarios").insert(e).select().single();if(a)throw a;const{users:t}=u();return r({users:[...t,s],loading:!1},!1,"createUser:success"),!0}catch(s){return r({error:s.message||"Erro ao criar usuário",loading:!1},!1,"createUser:error"),!1}},deleteUser:async e=>{try{r({loading:!0,error:null},!1,"deleteUser:start");const{error:s}=await o.from("usuarios").delete().eq("id",e);if(s)throw s;const{users:a,currentUser:t}=u(),n=a.filter(l=>l.id!==e);return r({users:n,currentUser:t?.id===e?null:t,loading:!1},!1,"deleteUser:success"),!0}catch(s){return r({error:s.message||"Erro ao deletar usuário",loading:!1},!1,"deleteUser:error"),!1}}}),{name:"user-store",partialize:r=>({currentUser:r.currentUser,users:r.users})}))),b=()=>m(r=>r.currentUser);export{b as useCurrentUser,m as useUserStore};

12
dist/index.html vendored
View File

@@ -8,14 +8,14 @@
<meta name="theme-color" content="#2563eb" /> <meta name="theme-color" content="#2563eb" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<title>RDO Mobile - Relatório Diário de Obra</title> <title>RDO Mobile - Relatório Diário de Obra</title>
<script type="module" crossorigin src="/assets/js/index-doec96Hx.js"></script> <script type="module" crossorigin src="/assets/js/index-DXLajEHZ.js"></script>
<link rel="modulepreload" crossorigin href="/assets/js/react-vendor-CqRd3GwO.js"> <link rel="modulepreload" crossorigin href="/assets/js/react-vendor-CqRd3GwO.js">
<link rel="modulepreload" crossorigin href="/assets/js/router-vendor-D4by-_6Z.js"> <link rel="modulepreload" crossorigin href="/assets/js/router-vendor-D4by-_6Z.js">
<link rel="modulepreload" crossorigin href="/assets/js/query-vendor-BLVqILA6.js"> <link rel="modulepreload" crossorigin href="/assets/js/query-vendor-Dc_G4OIP.js">
<link rel="modulepreload" crossorigin href="/assets/js/ui-vendor-CyRvbSfR.js"> <link rel="modulepreload" crossorigin href="/assets/js/ui-vendor-DHNIDV-1.js">
<link rel="modulepreload" crossorigin href="/assets/js/supabase-vendor-CnnNSQLo.js"> <link rel="modulepreload" crossorigin href="/assets/js/supabase-vendor-By1yMVW6.js">
<link rel="modulepreload" crossorigin href="/assets/js/state-vendor-DHadhBU5.js"> <link rel="modulepreload" crossorigin href="/assets/js/state-vendor-DK3LaRDK.js">
<link rel="stylesheet" crossorigin href="/assets/css/index-DXaaJsOA.css"> <link rel="stylesheet" crossorigin href="/assets/css/index-CYCdtjzd.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -1,21 +0,0 @@
[build]
command = "npm ci && npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "22"
NODE_ENV = "production"
GENERATE_SOURCEMAP = "false"
# Redirect para SPA
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# Headers básicos de segurança
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"

View File

@@ -1,16 +1,14 @@
{ {
"name": "trae-project", "name": "ts-rdo",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"build:netlify": "vite build --mode production",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"preview": "vite preview", "preview": "vite preview",
"preview:netlify": "vite preview --port 4173 --host",
"start": "node scripts/start.js", "start": "node scripts/start.js",
"check": "tsc -b --noEmit", "check": "tsc -b --noEmit",
"analyze": "vite build --mode analyze", "analyze": "vite build --mode analyze",

View File

@@ -1,22 +0,0 @@
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS",
"buildCommand": "pnpm install && pnpm run build"
},
"deploy": {
"startCommand": "pnpm preview --host 0.0.0.0 --port $PORT",
"healthcheckPath": "/",
"healthcheckTimeout": 100,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
},
"environments": {
"production": {
"variables": {
"NODE_ENV": "production",
"PORT": "${{RAILWAY_PUBLIC_PORT}}"
}
}
}
}

View File

@@ -1,47 +0,0 @@
services:
- type: web
name: rdo-app
env: node
region: oregon # ou frankfurt para Europa
plan: free # plano gratuito
buildCommand: pnpm install && pnpm run build
startCommand: pnpm preview --host 0.0.0.0 --port $PORT
# Configurações do ambiente
envVars:
- key: NODE_ENV
value: production
- key: PORT
fromService:
type: web
name: rdo-app
property: port
# Configurações de build
buildFilter:
paths:
- src/**
- public/**
- index.html
- package.json
- pnpm-lock.yaml
- vite.config.ts
- tsconfig.json
# Headers personalizados
headers:
- path: /*
name: X-Frame-Options
value: DENY
- path: /*
name: X-Content-Type-Options
value: nosniff
- path: /assets/*
name: Cache-Control
value: public, max-age=31536000, immutable
# Redirects para SPA
routes:
- type: redirect
source: /*
destination: /index.html

19
scripts/push_gitea.py Normal file
View File

@@ -0,0 +1,19 @@
import subprocess
import os
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# @@ -> %40%40 (Codificação URL padrão para caracteres especiais)
remote_url = "https://admtracksteel:%40%40Gi05Br;;@git.reifonas.cloud/admtracksteel/RDO.git"
def run_git(args):
result = subprocess.run(["git"] + args, cwd=repo_dir, capture_output=True, text=True)
if result.returncode != 0:
print(f"Erro em git {' '.join(args)}: {result.stderr}")
else:
print(f"Sucesso em git {' '.join(args)}")
return result.returncode
run_git(["remote", "set-url", "origin", remote_url])
run_git(["branch", "-M", "main"])
print("Iniciando Push de RDO para Gitea...")
run_git(["push", "-u", "origin", "main", "--force"])

View File

@@ -1,28 +0,0 @@
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
],
"headers": [
{
"source": "/service-worker.js",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
}
]
},
{
"source": "/(.*\\\\.(js|css|png|jpg|jpeg|gif|ico|svg))$",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}