Files
RDO/.trae/documents/Arquitetura_BD_RDO_Completa.md
2026-02-20 07:25:32 -03:00

33 KiB

Arquitetura Completa de Banco de Dados - RDO Mobile App

1. Visão Geral da Arquitetura de Dados

1.1 Estratégia de Banco de Dados

O sistema RDO Mobile utiliza Supabase como solução Backend-as-a-Service (BaaS), fornecendo:

  • PostgreSQL como banco de dados principal
  • Autenticação integrada com Row Level Security (RLS)
  • Storage para arquivos e imagens
  • Real-time subscriptions para sincronização em tempo real
  • Edge Functions para lógica de negócio complexa

1.2 Arquitetura de Conexão

graph TD
    A[React Frontend] --> B[Supabase Client SDK]
    B --> C[Supabase API Gateway]
    C --> D[PostgreSQL Database]
    C --> E[Supabase Auth]
    C --> F[Supabase Storage]
    C --> G[Real-time Engine]
    
    subgraph "Frontend Layer"
        A
        H[Zustand Store]
        I[TanStack Query Cache]
        J[IndexedDB Offline]
    end
    
    subgraph "Supabase Backend"
        C
        D
        E
        F
        G
        K[Row Level Security]
        L[Edge Functions]
    end
    
    A --> H
    A --> I
    A --> J
    D --> K
    C --> L

2. Modelo de Dados Completo

2.1 Diagrama Entidade-Relacionamento

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
    }

3. Scripts de Criação do Banco de Dados

3.1 Tabelas Principais

-- Habilitar extensões necessárias
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";

-- Tabela de Usuários
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(50),
    role VARCHAR(20) DEFAULT 'usuario' CHECK (role IN ('admin', 'engenheiro', 'mestre_obra', 'usuario')),
    ativo BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Tabela de Obras
CREATE TABLE obras (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    nome VARCHAR(200) 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 'ativa' CHECK (status IN ('ativa', 'pausada', 'concluida', 'cancelada')),
    configuracoes JSONB DEFAULT '{}',
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 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,
    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(),
    UNIQUE(obra_id, data_relatorio)
);

-- Tabela de Atividades do RDO
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 NOT NULL,
    localizacao VARCHAR(200),
    percentual_concluido DECIMAL(5,2) DEFAULT 0.00,
    ordem INTEGER DEFAULT 1,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Tabela de Mão de Obra do RDO
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(4,2) NOT NULL DEFAULT 8.00,
    observacoes TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Tabela de Equipamentos do RDO
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(100) NOT NULL,
    tipo VARCHAR(50),
    horas_utilizadas DECIMAL(4,2) DEFAULT 0.00,
    combustivel_gasto DECIMAL(6,2) DEFAULT 0.00,
    observacoes TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Tabela de Ocorrências do RDO
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()
);

-- Tabela de Anexos do RDO
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()
);

-- Tabela de Inspeções de Solda
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 ('aprovado', 'reprovado', 'pendente')),
    metodo_inspecao VARCHAR(50),
    observacoes TEXT,
    inspecionado_por UUID REFERENCES usuarios(id),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Tabela de Verificações de Torque
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(6,2),
    torque_aplicado DECIMAL(6,2) NOT NULL,
    status_verificacao VARCHAR(20) DEFAULT 'conforme' CHECK (status_verificacao IN ('conforme', 'nao_conforme')),
    observacoes TEXT,
    verificado_por UUID REFERENCES usuarios(id),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Tabela de Tarefas
CREATE TABLE tarefas (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    obra_id UUID NOT NULL REFERENCES obras(id) ON DELETE CASCADE,
    titulo VARCHAR(200) 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()
);

-- Tabela de Logs de Tarefas
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(20) NOT NULL CHECK (tipo_evento IN ('inicio', 'pausa', 'retomada', 'conclusao', 'revisao', 'edicao', 'cancelamento')),
    descricao TEXT,
    detalhes JSONB DEFAULT '{}',
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

3.2 Índices para Performance

-- Índices para otimização de consultas
CREATE INDEX idx_usuarios_email ON usuarios(email);
CREATE INDEX idx_usuarios_role ON usuarios(role);
CREATE INDEX idx_usuarios_ativo ON usuarios(ativo);

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);

CREATE INDEX idx_rdos_obra_data ON rdos(obra_id, data_relatorio);
CREATE INDEX idx_rdos_criado_por ON rdos(criado_por);
CREATE INDEX idx_rdos_status ON rdos(status);
CREATE INDEX idx_rdos_data_relatorio ON rdos(data_relatorio DESC);

CREATE INDEX idx_rdo_atividades_rdo ON rdo_atividades(rdo_id);
CREATE INDEX idx_rdo_mao_obra_rdo ON rdo_mao_obra(rdo_id);
CREATE INDEX idx_rdo_equipamentos_rdo ON rdo_equipamentos(rdo_id);
CREATE INDEX idx_rdo_ocorrencias_rdo ON rdo_ocorrencias(rdo_id);
CREATE INDEX idx_rdo_anexos_rdo ON rdo_anexos(rdo_id);

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_data_fim ON tarefas(data_fim);

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_created_at ON task_logs(created_at DESC);

-- Índices para busca textual
CREATE INDEX idx_obras_nome_trgm ON obras USING gin(nome gin_trgm_ops);
CREATE INDEX idx_rdos_observacoes_trgm ON rdos USING gin(observacoes_gerais gin_trgm_ops);

3.3 Triggers para Auditoria

-- Função para atualizar timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

-- Triggers para updated_at
CREATE TRIGGER update_usuarios_updated_at BEFORE UPDATE ON usuarios FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_obras_updated_at BEFORE UPDATE ON obras FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_rdos_updated_at BEFORE UPDATE ON rdos FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_tarefas_updated_at BEFORE UPDATE ON tarefas FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

4. Row Level Security (RLS)

4.1 Políticas de Segurança

-- Habilitar RLS em todas as tabelas
ALTER TABLE usuarios ENABLE ROW LEVEL SECURITY;
ALTER TABLE obras ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdos ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_atividades ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_mao_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_equipamentos ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_ocorrencias ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_anexos ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_inspecoes_solda ENABLE ROW LEVEL SECURITY;
ALTER TABLE rdo_verificacoes_torque ENABLE ROW LEVEL SECURITY;
ALTER TABLE tarefas ENABLE ROW LEVEL SECURITY;
ALTER TABLE task_logs ENABLE ROW LEVEL SECURITY;

-- Políticas para usuários
CREATE POLICY "Usuários podem ver próprio perfil" ON usuarios FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Usuários podem atualizar próprio perfil" ON usuarios FOR UPDATE USING (auth.uid() = id);
CREATE POLICY "Admins podem gerenciar usuários" ON usuarios FOR ALL USING (auth.jwt() ->> 'role' = 'admin');

-- Políticas para obras
CREATE POLICY "Usuários podem ver obras onde participam" ON obras FOR SELECT USING (
    auth.uid() = responsavel_id OR 
    auth.jwt() ->> 'role' IN ('admin', 'engenheiro')
);

CREATE POLICY "Engenheiros e admins podem criar obras" ON obras FOR INSERT WITH CHECK (
    auth.jwt() ->> 'role' IN ('admin', 'engenheiro')
);

CREATE POLICY "Responsáveis podem atualizar suas obras" ON obras FOR UPDATE USING (
    auth.uid() = responsavel_id OR 
    auth.jwt() ->> 'role' IN ('admin', 'engenheiro')
);

-- Políticas para RDOs
CREATE POLICY "Usuários podem ver RDOs de suas obras" ON rdos FOR SELECT USING (
    EXISTS (
        SELECT 1 FROM obras 
        WHERE obras.id = rdos.obra_id 
        AND (obras.responsavel_id = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro'))
    )
);

CREATE POLICY "Usuários podem criar RDOs" ON rdos FOR INSERT WITH CHECK (
    auth.uid() = criado_por AND
    EXISTS (
        SELECT 1 FROM obras 
        WHERE obras.id = obra_id 
        AND (obras.responsavel_id = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro', 'mestre_obra'))
    )
);

CREATE POLICY "Criadores podem atualizar próprios RDOs" ON rdos FOR UPDATE USING (
    auth.uid() = criado_por AND status = 'rascunho'
);

-- Políticas para tabelas relacionadas ao RDO
CREATE POLICY "Acesso baseado no RDO" ON rdo_atividades FOR ALL USING (
    EXISTS (
        SELECT 1 FROM rdos 
        WHERE rdos.id = rdo_atividades.rdo_id 
        AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro'))
    )
);

-- Aplicar política similar para todas as tabelas rdo_*
CREATE POLICY "Acesso baseado no RDO" ON rdo_mao_obra FOR ALL USING (
    EXISTS (SELECT 1 FROM rdos WHERE rdos.id = rdo_mao_obra.rdo_id AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro')))
);

CREATE POLICY "Acesso baseado no RDO" ON rdo_equipamentos FOR ALL USING (
    EXISTS (SELECT 1 FROM rdos WHERE rdos.id = rdo_equipamentos.rdo_id AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro')))
);

CREATE POLICY "Acesso baseado no RDO" ON rdo_ocorrencias FOR ALL USING (
    EXISTS (SELECT 1 FROM rdos WHERE rdos.id = rdo_ocorrencias.rdo_id AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro')))
);

CREATE POLICY "Acesso baseado no RDO" ON rdo_anexos FOR ALL USING (
    EXISTS (SELECT 1 FROM rdos WHERE rdos.id = rdo_anexos.rdo_id AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro')))
);

CREATE POLICY "Acesso baseado no RDO" ON rdo_inspecoes_solda FOR ALL USING (
    EXISTS (SELECT 1 FROM rdos WHERE rdos.id = rdo_inspecoes_solda.rdo_id AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro')))
);

CREATE POLICY "Acesso baseado no RDO" ON rdo_verificacoes_torque FOR ALL USING (
    EXISTS (SELECT 1 FROM rdos WHERE rdos.id = rdo_verificacoes_torque.rdo_id AND (rdos.criado_por = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro')))
);

-- Políticas para tarefas
CREATE POLICY "Usuários podem ver tarefas de suas obras" ON tarefas FOR SELECT USING (
    EXISTS (
        SELECT 1 FROM obras 
        WHERE obras.id = tarefas.obra_id 
        AND (obras.responsavel_id = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro'))
    ) OR responsavel_id = auth.uid()
);

CREATE POLICY "Usuários podem criar tarefas" ON tarefas FOR INSERT WITH CHECK (
    EXISTS (
        SELECT 1 FROM obras 
        WHERE obras.id = obra_id 
        AND (obras.responsavel_id = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro', 'mestre_obra'))
    )
);

-- Políticas para task_logs
CREATE POLICY "Usuários podem ver logs de suas tarefas" ON task_logs FOR SELECT USING (
    EXISTS (
        SELECT 1 FROM tarefas 
        WHERE tarefas.id = task_logs.task_id 
        AND (tarefas.responsavel_id = auth.uid() OR auth.jwt() ->> 'role' IN ('admin', 'engenheiro'))
    )
);

CREATE POLICY "Usuários podem criar logs" ON task_logs FOR INSERT WITH CHECK (
    auth.uid() = usuario_id
);

5. Configuração do Cliente Supabase

5.1 Configuração Básica

// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true
  },
  realtime: {
    params: {
      eventsPerSecond: 10
    }
  }
})

// Tipos TypeScript gerados automaticamente
export type Tables<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Row']
export type Enums<T extends keyof Database['public']['Enums']> = Database['public']['Enums'][T]

5.2 Hooks Customizados para Dados

// src/hooks/useRDOs.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { supabase } from '../lib/supabase'
import { Tables } from '../lib/supabase'

type RDO = Tables<'rdos'>
type RDOWithDetails = RDO & {
  atividades: Tables<'rdo_atividades'>[]
  mao_obra: Tables<'rdo_mao_obra'>[]
  equipamentos: Tables<'rdo_equipamentos'>[]
  ocorrencias: Tables<'rdo_ocorrencias'>[]
  anexos: Tables<'rdo_anexos'>[]
}

export function useRDOs(obraId: string) {
  return useQuery({
    queryKey: ['rdos', obraId],
    queryFn: async () => {
      const { data, error } = await supabase
        .from('rdos')
        .select(`
          *,
          atividades:rdo_atividades(*),
          mao_obra:rdo_mao_obra(*),
          equipamentos:rdo_equipamentos(*),
          ocorrencias:rdo_ocorrencias(*),
          anexos:rdo_anexos(*)
        `)
        .eq('obra_id', obraId)
        .order('data_relatorio', { ascending: false })
      
      if (error) throw error
      return data as RDOWithDetails[]
    }
  })
}

export function useCreateRDO() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: async (rdoData: Partial<RDO> & {
      atividades?: Partial<Tables<'rdo_atividades'>>[]
      mao_obra?: Partial<Tables<'rdo_mao_obra'>>[]
      equipamentos?: Partial<Tables<'rdo_equipamentos'>>[]
      ocorrencias?: Partial<Tables<'rdo_ocorrencias'>>[]
    }) => {
      const { atividades, mao_obra, equipamentos, ocorrencias, ...rdo } = rdoData
      
      // Criar RDO principal
      const { data: rdoCreated, error: rdoError } = await supabase
        .from('rdos')
        .insert(rdo)
        .select()
        .single()
      
      if (rdoError) throw rdoError
      
      // Inserir dados relacionados
      if (atividades?.length) {
        const { error } = await supabase
          .from('rdo_atividades')
          .insert(atividades.map(a => ({ ...a, rdo_id: rdoCreated.id })))
        if (error) throw error
      }
      
      if (mao_obra?.length) {
        const { error } = await supabase
          .from('rdo_mao_obra')
          .insert(mao_obra.map(m => ({ ...m, rdo_id: rdoCreated.id })))
        if (error) throw error
      }
      
      if (equipamentos?.length) {
        const { error } = await supabase
          .from('rdo_equipamentos')
          .insert(equipamentos.map(e => ({ ...e, rdo_id: rdoCreated.id })))
        if (error) throw error
      }
      
      if (ocorrencias?.length) {
        const { error } = await supabase
          .from('rdo_ocorrencias')
          .insert(ocorrencias.map(o => ({ ...o, rdo_id: rdoCreated.id })))
        if (error) throw error
      }
      
      return rdoCreated
    },
    onSuccess: (data) => {
      queryClient.invalidateQueries({ queryKey: ['rdos', data.obra_id] })
    }
  })
}

5.3 Store Zustand Integrado

// src/stores/rdoStore.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { supabase } from '../lib/supabase'
import { Tables } from '../lib/supabase'

type RDO = Tables<'rdos'>
type Obra = Tables<'obras'>

interface RDOState {
  // Estado
  currentObra: Obra | null
  rdos: RDO[]
  loading: boolean
  error: string | null
  
  // Ações
  setCurrentObra: (obra: Obra) => void
  loadRDOs: (obraId: string) => Promise<void>
  createRDO: (rdoData: Partial<RDO>) => Promise<RDO>
  updateRDO: (id: string, updates: Partial<RDO>) => Promise<void>
  deleteRDO: (id: string) => Promise<void>
  
  // Real-time
  subscribeToRDOs: (obraId: string) => () => void
}

export const useRDOStore = create<RDOState>()(persist(
  (set, get) => ({
    // Estado inicial
    currentObra: null,
    rdos: [],
    loading: false,
    error: null,
    
    // Ações
    setCurrentObra: (obra) => set({ currentObra: obra }),
    
    loadRDOs: async (obraId) => {
      set({ loading: true, error: null })
      try {
        const { data, error } = await supabase
          .from('rdos')
          .select('*')
          .eq('obra_id', obraId)
          .order('data_relatorio', { ascending: false })
        
        if (error) throw error
        set({ rdos: data, loading: false })
      } catch (error) {
        set({ error: (error as Error).message, loading: false })
      }
    },
    
    createRDO: async (rdoData) => {
      set({ loading: true, error: null })
      try {
        const { data, error } = await supabase
          .from('rdos')
          .insert(rdoData)
          .select()
          .single()
        
        if (error) throw error
        
        set(state => ({ 
          rdos: [data, ...state.rdos], 
          loading: false 
        }))
        
        return data
      } catch (error) {
        set({ error: (error as Error).message, loading: false })
        throw error
      }
    },
    
    updateRDO: async (id, updates) => {
      set({ loading: true, error: null })
      try {
        const { error } = await supabase
          .from('rdos')
          .update(updates)
          .eq('id', id)
        
        if (error) throw error
        
        set(state => ({
          rdos: state.rdos.map(rdo => 
            rdo.id === id ? { ...rdo, ...updates } : rdo
          ),
          loading: false
        }))
      } catch (error) {
        set({ error: (error as Error).message, loading: false })
      }
    },
    
    deleteRDO: async (id) => {
      set({ loading: true, error: null })
      try {
        const { error } = await supabase
          .from('rdos')
          .delete()
          .eq('id', id)
        
        if (error) throw error
        
        set(state => ({
          rdos: state.rdos.filter(rdo => rdo.id !== id),
          loading: false
        }))
      } catch (error) {
        set({ error: (error as Error).message, loading: false })
      }
    },
    
    subscribeToRDOs: (obraId) => {
      const subscription = supabase
        .channel(`rdos-${obraId}`)
        .on(
          'postgres_changes',
          {
            event: '*',
            schema: 'public',
            table: 'rdos',
            filter: `obra_id=eq.${obraId}`
          },
          (payload) => {
            const { eventType, new: newRecord, old: oldRecord } = payload
            
            set(state => {
              switch (eventType) {
                case 'INSERT':
                  return { rdos: [newRecord as RDO, ...state.rdos] }
                case 'UPDATE':
                  return {
                    rdos: state.rdos.map(rdo => 
                      rdo.id === newRecord.id ? newRecord as RDO : rdo
                    )
                  }
                case 'DELETE':
                  return {
                    rdos: state.rdos.filter(rdo => rdo.id !== oldRecord.id)
                  }
                default:
                  return state
              }
            })
          }
        )
        .subscribe()
      
      return () => subscription.unsubscribe()
    }
  }),
  {
    name: 'rdo-store',
    partialize: (state) => ({ currentObra: state.currentObra })
  }
))

6. Estratégias de Cache e Offline

6.1 Configuração do TanStack Query

// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
import { persistQueryClient } from '@tanstack/react-query-persist-client-core'
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutos
      gcTime: 10 * 60 * 1000, // 10 minutos
      retry: 3,
      refetchOnWindowFocus: false,
      refetchOnReconnect: true
    },
    mutations: {
      retry: 1
    }
  }
})

// Persistir cache no localStorage
const localStoragePersister = createSyncStoragePersister({
  storage: window.localStorage,
  key: 'rdo-cache'
})

persistQueryClient({
  queryClient,
  persister: localStoragePersister,
  maxAge: 24 * 60 * 60 * 1000 // 24 horas
})

export { queryClient }

6.2 Service Worker para Offline

// src/sw.ts
import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies'

// Precache de arquivos estáticos
precacheAndRoute(self.__WB_MANIFEST)
cleanupOutdatedCaches()

// Cache de API calls
registerRoute(
  ({ url }) => url.origin === 'https://your-supabase-url.supabase.co',
  new StaleWhileRevalidate({
    cacheName: 'supabase-api',
    plugins: [
      {
        cacheKeyWillBeUsed: async ({ request }) => {
          return `${request.url}?${Date.now()}`
        }
      }
    ]
  })
)

// Cache de imagens
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      {
        cacheExpiration: {
          maxEntries: 100,
          maxAgeSeconds: 30 * 24 * 60 * 60 // 30 dias
        }
      }
    ]
  })
)

7. Monitoramento e Analytics

7.1 Métricas de Performance

-- Views para relatórios e analytics
CREATE VIEW vw_obras_dashboard AS
SELECT 
    o.id,
    o.nome,
    o.status,
    o.progresso_geral,
    COUNT(r.id) as total_rdos,
    COUNT(CASE WHEN r.status = 'aprovado' THEN 1 END) as rdos_aprovados,
    COUNT(t.id) as total_tarefas,
    COUNT(CASE WHEN t.status = 'concluida' THEN 1 END) as tarefas_concluidas,
    AVG(CASE WHEN t.status = 'concluida' THEN t.progresso END) as progresso_medio_tarefas
FROM obras o
LEFT JOIN rdos r ON o.id = r.obra_id
LEFT JOIN tarefas t ON o.id = t.obra_id
GROUP BY o.id, o.nome, o.status, o.progresso_geral;

CREATE VIEW vw_produtividade_mensal AS
SELECT 
    DATE_TRUNC('month', r.data_relatorio) as mes,
    o.nome as obra,
    COUNT(r.id) as rdos_criados,
    COUNT(ra.id) as atividades_executadas,
    SUM(rm.quantidade * rm.horas_trabalhadas) as total_horas_trabalhadas
FROM rdos r
JOIN obras o ON r.obra_id = o.id
LEFT JOIN rdo_atividades ra ON r.id = ra.rdo_id
LEFT JOIN rdo_mao_obra rm ON r.id = rm.rdo_id
GROUP BY DATE_TRUNC('month', r.data_relatorio), o.nome
ORDER BY mes DESC;

7.2 Logs de Auditoria

-- Tabela de auditoria
CREATE TABLE audit_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    table_name VARCHAR(50) NOT NULL,
    record_id UUID NOT NULL,
    action VARCHAR(10) NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
    old_values JSONB,
    new_values JSONB,
    user_id UUID REFERENCES usuarios(id),
    timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Função de auditoria
CREATE OR REPLACE FUNCTION audit_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'DELETE' THEN
        INSERT INTO audit_logs (table_name, record_id, action, old_values, user_id)
        VALUES (TG_TABLE_NAME, OLD.id, TG_OP, row_to_json(OLD), auth.uid());
        RETURN OLD;
    ELSIF TG_OP = 'UPDATE' THEN
        INSERT INTO audit_logs (table_name, record_id, action, old_values, new_values, user_id)
        VALUES (TG_TABLE_NAME, NEW.id, TG_OP, row_to_json(OLD), row_to_json(NEW), auth.uid());
        RETURN NEW;
    ELSIF TG_OP = 'INSERT' THEN
        INSERT INTO audit_logs (table_name, record_id, action, new_values, user_id)
        VALUES (TG_TABLE_NAME, NEW.id, TG_OP, row_to_json(NEW), auth.uid());
        RETURN NEW;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- Aplicar triggers de auditoria
CREATE TRIGGER audit_rdos AFTER INSERT OR UPDATE OR DELETE ON rdos FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
CREATE TRIGGER audit_obras AFTER INSERT OR UPDATE OR DELETE ON obras FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();

8. Backup e Recuperação

8.1 Estratégia de Backup

-- Função para backup incremental
CREATE OR REPLACE FUNCTION create_incremental_backup()
RETURNS TABLE(table_name TEXT, records_count BIGINT) AS $$
DECLARE
    backup_timestamp TIMESTAMP := NOW();
BEGIN
    -- Criar tabela de controle de backup se não existir
    CREATE TABLE IF NOT EXISTS backup_control (
        id SERIAL PRIMARY KEY,
        table_name VARCHAR(50),
        last_backup TIMESTAMP,
        created_at TIMESTAMP DEFAULT NOW()
    );
    
    -- Backup das tabelas principais
    RETURN QUERY
    SELECT 'rdos'::TEXT, COUNT(*) FROM rdos WHERE updated_at > COALESCE(
        (SELECT last_backup FROM backup_control WHERE table_name = 'rdos'), 
        '1970-01-01'::TIMESTAMP
    );
    
    -- Atualizar controle de backup
    INSERT INTO backup_control (table_name, last_backup) 
    VALUES ('rdos', backup_timestamp)
    ON CONFLICT (table_name) DO UPDATE SET last_backup = backup_timestamp;
END;
$$ LANGUAGE plpgsql;

9. Configurações de Ambiente

9.1 Variáveis de Ambiente

# .env.local
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
VITE_SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# Configurações de cache
VITE_CACHE_TTL=300000
VITE_OFFLINE_ENABLED=true

# Configurações de upload
VITE_MAX_FILE_SIZE=10485760
VITE_ALLOWED_FILE_TYPES=image/*,application/pdf

9.2 Configuração de Produção

// src/config/database.ts
export const databaseConfig = {
  development: {
    supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
    supabaseKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
    enableRealtime: true,
    enableCache: true,
    cacheTimeout: 5 * 60 * 1000
  },
  production: {
    supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
    supabaseKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
    enableRealtime: true,
    enableCache: true,
    cacheTimeout: 10 * 60 * 1000,
    enableCompression: true,
    maxRetries: 3
  }
}

export const getCurrentConfig = () => {
  const env = import.meta.env.MODE
  return databaseConfig[env as keyof typeof databaseConfig] || databaseConfig.development
}

Esta documentação fornece uma base sólida para implementar uma conexão completa com banco de dados no projeto RDO Mobile, incluindo esquema de dados, segurança, performance e estratégias offline.