Documentação técnica

Visão geral do ecossistema Federal Pro

Arquitetura 100% Spark (gratuita, sem cartão de crédito) com Firebase Auth (e-mail/senha), GeoFire client-side, Firestore como NoSQL principal, Realtime Database para localização, Cloud Storage para documentos, Cloud Functions para lógica serverless, FCM para notificações e espelhamento relacional em MySQL via PHP/MySQLi para o Dashboard Administrativo.

Firebase Spark E-mail + Senha Firestore + GeoFire Realtime Database Cloud Storage Cloud Functions FCM MySQL / MySQLi

1. Visão geral do sistema

O ecossistema do Federal Pro opera 100% no plano Spark (gratuito) do Firebase, sem necessidade de cartão de crédito. A autenticação é feita via Firebase Auth com e-mail e senha, geolocalização client-side via GeoFire (geofire-common), banco NoSQL (Firestore), Realtime Database para dados efêmeros, Cloud Storage para documentos e imagens, Cloud Functions para lógica de match e eventos, FCM para notificações push, espelhamento relacional em MySQL (via PHP/MySQLi) e interface web para o Dashboard Administrativo.

👤 Clientes / Passageiros

Solicitam viagens ou entregas pelo app. Autenticação via e-mail e senha com JWT. Perfil cliente.

🏍 Motoristas de Moto / Entregadores

Com perfil unificado, o profissional de moto escolhe no app se quer atuar em viagens, entregas ou ambos. A categoria é única e a tarifa de entrega é 20% maior.

🚗 Motoristas de Carro

Mesma flexibilidade: escolhe no app entre viagens, entregas ou ambos. Quatro categorias de viagem (light, pro, confort, black), cada uma com sua tarifa de entrega (+20%).

Plano Spark
Cartão de crédito Não exigido
Serviços ativos 12+
Entrega +20%

2. Modelo de tarifas e regras de negócio

Cada combinação de tipo de veículo e categoria possui tarifa base, preço por km, preço por minuto, valor mínimo e taxa percentual da plataforma. As tarifas de entrega são 20% maiores que as de viagem em todos os componentes (base, km, minuto e mínimo), mantendo a taxa da plataforma em 15%.

Viagem Tarifas de viagem (passageiros)

Moto Única
Tarifa baseR$ 3,50
Preço / kmR$ 1,20
Preço / minR$ 0,20
MínimoR$ 6,00
Taxa plataforma15%
Carro Light
Tarifa baseR$ 4,50
Preço / kmR$ 1,80
Preço / minR$ 0,25
MínimoR$ 8,00
Taxa plataforma15%
Carro Pro
Tarifa baseR$ 5,00
Preço / kmR$ 2,00
Preço / minR$ 0,30
MínimoR$ 9,50
Taxa plataforma15%
Carro Confort
Tarifa baseR$ 6,00
Preço / kmR$ 2,30
Preço / minR$ 0,35
MínimoR$ 11,00
Taxa plataforma15%
Carro Black
Tarifa baseR$ 8,00
Preço / kmR$ 3,00
Preço / minR$ 0,50
MínimoR$ 15,00
Taxa plataforma15%

Entrega Tarifas de entrega +20% sobre a viagem

Moto Única
Tarifa baseR$ 4,20
Preço / kmR$ 1,44
Preço / minR$ 0,24
MínimoR$ 7,20
Taxa plataforma15%
Carro Light
Tarifa baseR$ 5,40
Preço / kmR$ 2,16
Preço / minR$ 0,30
MínimoR$ 9,60
Taxa plataforma15%
Carro Pro
Tarifa baseR$ 6,00
Preço / kmR$ 2,40
Preço / minR$ 0,36
MínimoR$ 11,40
Taxa plataforma15%
Carro Confort
Tarifa baseR$ 7,20
Preço / kmR$ 2,76
Preço / minR$ 0,42
MínimoR$ 13,20
Taxa plataforma15%
Carro Black
Tarifa baseR$ 9,60
Preço / kmR$ 3,60
Preço / minR$ 0,60
MínimoR$ 18,00
Taxa plataforma15%

3. Estrutura do banco NoSQL (Firestore)

Quatro coleções principais suportam o sistema: usuários, tarifas, solicitações e documentos de motorista com OCR e BioFace. Cada documento de tarifa agora inclui o campo tipo para diferenciar viagem de entrega.

📦 Coleção usuarios

Documento único por usuário. Agora com o campo modalidades para o profissional escolher entre viagem, entrega ou ambos.

JSON usuarios/USR_98765
{
  "uid": "USR_98765",
  "nome": "Oswaldo Junior",
  "email": "oswaldo@federalpro.com.br",
  "celular": "19999998888",
  "perfil": "motorista",
  "modalidades": {
    "viagem": true,
    "entrega": true
  },
  "statusAprovacao": "aprovado",
  "isOnline": true,
  "geohash": "6gyf1234",
  "lat": -22.9056,
  "lng": -47.0608,
  "pushToken": "ExponentPushToken[xxxx]",
  "selfieUrl": "https://storage.googleapis.com/.../selfie.jpg",
  "selfieVerificada": true,
  "biometriaAtivada": true,
  "veiculo": {
    "tipo": "carro",
    "categoria": "pro",
    "placa": "ABC1D23",
    "modelo": "Toyota Corolla",
    "cor": "Preto",
    "ano": 2022
  },
  "ultimaAtualizacao": "2026-09-17T13:40:00Z"
}
💵 Coleção tarifas

Configuração de preços por tipo de veículo e categoria. Cada documento declara se é tarifa de viagem ou entrega. Ao todo são 10 documentos (5 viagens + 5 entregas).

JSON tarifas/carro_pro_entrega
{
  "id": "carro_pro_entrega",
  "tipo": "entrega",
  "tipoVeiculo": "carro",
  "categoria": "pro",
  "tarifaBase": 6.00,
  "precoPorKm": 2.40,
  "precoPorMinuto": 0.36,
  "precoMinimo": 11.40,
  "taxaPlataformaPercentual": 15.0
}
🚕 Coleção solicitacoes

Cada viagem ou entrega. O campo tipo decide qual conjunto de tarifas é aplicado no cálculo do valorTotal.

JSON solicitacoes/SOL_100200
{
  "id": "SOL_100200",
  "tipo": "viagem",
  "clienteId": "USR_cliente_01",
  "motoristaId": "USR_98765",
  "status": "em_andamento",
  "tipoVeiculoExigido": "carro",
  "categoriaExigida": "pro",
  "origem": { "endereco": "Av. Brasil, 1500", "lat": -22.9056, "lng": -47.0608 },
  "destino": { "endereco": "Rua Barão de Jaguara, 300", "lat": -22.9120, "lng": -47.0580 },
  "distanciaKm": 4.8,
  "duracaoMinutos": 12,
  "valorTotal": 18.20,
  "taxaPlataforma": 2.73,
  "valorMotorista": 15.47,
  "formaPagamento": "pix",
  "pago": true,
  "criadoEm": "2026-09-17T13:40:00Z"
}
🔍 Coleção documentos_motorista

Documentos enviados com processamento OCR e score de similaridade facial (BioFace).

JSON documentos_motorista/cnh_USR_98765
{
  "motoristaId": "USR_98765",
  "tipoDocumento": "cnh",
  "urlArquivo": "https://storage.googleapis.com/.../cnh.jpg",
  "status": "aprovado",
  "processadoPorOcr": true,
  "dadosOcr": {
    "numeroDocumento": "12345678900",
    "nomeExtraido": "OSWALDO BESELGA JUNIOR",
    "cpfExtraido": "123.456.789-00",
    "validadeDocumento": "2028-10-15",
    "categoriaCnh": "AB",
    "confiabilidadeOcr": 0.96
  },
  "scoreSimilaridadeFacial": 0.98,
  "enviadoEm": "2026-09-17T13:40:00Z"
}

4. Realtime Database — localização e presença

O Realtime Database é ideal para dados efêmeros e de alta frequência: localização em tempo real e presença online/offline dos motoristas. Enquanto o Firestore guarda os dados persistentes, o RTDB mantém o estado vivo do sistema.

📍 Localização em tempo real

Caminho localizacoes/motoristas/{uid} atualizado a cada poucos segundos pelo app do motorista. Contém lat, lng, heading, speed, accuracy e updatedAt.

🟢 Presença online/offline

Caminho presenca/motoristas/{uid} com online, disponivel, modalidades e ultimaAtividade. Permite saber quem está ativo para viagem e/ou entrega sem consultar o Firestore.

⚡ Corridas ativas (espelho leve)

Caminho corridasAtivas/{corridaId} com motoristaId, passageiroId, status, tipo e atualizadoEm. Leitura rápida em tempo real para o app.

JSON realtime-database.json
{
  "localizacoes": {
    "motoristas": {
      "USR_98765": {
        "lat": -22.9056,
        "lng": -47.0608,
        "heading": 180,
        "speed": 12.5,
        "accuracy": 8.0,
        "updatedAt": 1758120000000
      }
    }
  },
  "presenca": {
    "motoristas": {
      "USR_98765": {
        "online": true,
        "disponivel": true,
        "modalidades": { "viagem": true, "entrega": true },
        "ultimaAtividade": 1758120000000
      }
    }
  },
  "corridasAtivas": {
    "SOL_100200": {
      "tipo": "viagem",
      "motoristaId": "USR_98765",
      "passageiroId": "USR_cliente_01",
      "status": "em_andamento",
      "atualizadoEm": 1758120000000
    }
  }
}
Armazenamento 1 GB
Download / mês 10 GB
Conexões simultâneas 100

5. Cloud Storage — documentos e imagens

O Cloud Storage armazena selfies de verificação, CNH, CRLV, antecedentes criminais e fotos de perfil. Os arquivos são referenciados no Firestore por URL.

🪪 Documentos de motorista

CNH, CRLV e antecedentes criminais enviados no cadastro. Processados por OCR e verificados por BioFace.

🤳 Selfies de verificação

Fotos capturadas no app para validação facial. Comparadas com a foto do documento via score de similaridade.

🖼️ Fotos de perfil

Avatares de clientes e motoristas exibidos no app e no painel administrativo.

TypeScript services/storage.js
import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage';

export async function uploadDocumento(file, motoristaId, tipoDocumento) {
  const storage = getStorage();
  const caminho = `motoristas/${motoristaId}/${tipoDocumento}_${Date.now()}.jpg`;
  const storageRef = ref(storage, caminho);

  await uploadBytes(storageRef, file);
  const url = await getDownloadURL(storageRef);

  return { url, caminho };
}
Armazenamento 5 GB
Download / dia 1 GB
Uploads / dia 20K

6. Cloud Functions — lógica serverless no Spark

O plano Spark inclui uma cota gratuita de Cloud Functions. No Blaze, o serviço é cobrado por invocação após a cota. No Spark, se a cota for excedida, o serviço é pausado até o próximo ciclo.

Invocações / mês 125K
GB-s / mês 40K
CPU-s / mês 40K
JavaScript functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

// Gatilho: nova solicitação criada
exports.onNovaSolicitacao = functions.firestore
  .document('solicitacoes/{solicitacaoId}')
  .onCreate(async (snap, context) => {
    const solicitacao = snap.data();

    // Filtra motoristas que aceitam o tipo (viagem/entrega) da solicitação
    const motoristas = await buscarMotoristasProximos(
      solicitacao.origem.lat,
      solicitacao.origem.lng,
      solicitacao.tipoVeiculoExigido,
      solicitacao.categoriaExigida,
      solicitacao.tipo  // 'viagem' ou 'entrega'
    );

    const tokens = motoristas.map(m => m.pushToken).filter(Boolean);

    if (tokens.length > 0) {
      await admin.messaging().sendMulticast({
        tokens,
        notification: {
          title: solicitacao.tipo === 'entrega'
            ? 'Nova entrega disponível'
            : 'Nova corrida disponível',
          body: `${solicitacao.origem.endereco} → ${solicitacao.destino.endereco}`,
        },
        data: { solicitacaoId: context.params.solicitacaoId },
      });
    }
  });

// Gatilho: atualização de solicitação
exports.onSolicitacaoAtualizada = functions.firestore
  .document('solicitacoes/{solicitacaoId}')
  .onUpdate(async (change, context) => {
    const antes = change.before.data();
    const depois = change.after.data();

    if (antes.status !== 'aceita' && depois.status === 'aceita') {
      await admin.database()
        .ref(`/corridasAtivas/${context.params.solicitacaoId}`)
        .set({
          tipo: depois.tipo,
          motoristaId: depois.motoristaId,
          passageiroId: depois.clienteId,
          status: 'em_andamento',
          atualizadoEm: Date.now(),
        });
    }
  });

// Callable: busca motoristas por geohash, filtrando por modalidade
exports.buscarMotoristas = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Faça login.');
  }
  return { motoristas: [] };
});

7. Firebase Authentication — e-mail e senha

O plano Spark suporta autenticação por e-mail/senha, login social (Google, Apple, Facebook) e login anônimo gratuitamente até 50.000 usuários ativos por mês (MAU). A autenticação por telefone (SMS) exige upgrade para o Blaze.

📧 E-mail e senha

Método principal de autenticação. Inclui verificação de e-mail, recuperação de senha e login sem senha (link mágico).

🔗 Login social

Google, Apple, Facebook, GitHub, Microsoft, Twitter. Reduz a fricção no cadastro e é gratuito até 50K MAU.

👤 Login anônimo

Permite que o usuário experimente o app antes de se registrar. Se decidir criar conta, os dados são preservados.

JavaScript services/auth.js
import auth from '@react-native-firebase/auth';

export async function cadastrar(email, senha) {
  try {
    const credencial = await auth()
      .createUserWithEmailAndPassword(email, senha);
    return credencial.user;
  } catch (error) {
    if (error.code === 'auth/email-already-in-use') {
      throw new Error('Este e-mail já está em uso.');
    }
    if (error.code === 'auth/weak-password') {
      throw new Error('A senha é muito fraca.');
    }
    throw error;
  }
}

export async function login(email, senha) {
  try {
    const credencial = await auth()
      .signInWithEmailAndPassword(email, senha);
    return credencial.user;
  } catch (error) {
    if (error.code === 'auth/user-not-found') {
      throw new Error('Usuário não encontrado.');
    }
    if (error.code === 'auth/wrong-password') {
      throw new Error('Senha incorreta.');
    }
    throw error;
  }
}

export async function logout() {
  await auth().signOut();
}

export function onAuthChange(callback) {
  return auth().onAuthStateChanged(callback);
}
MAU gratuito 50K
Métodos 3+
Cartão de crédito Não

8. Script SQL — consolidação completa (MySQL)

Este script unifica todas as tabelas necessárias para sincronização relacional via PHP/MySQLi no Dashboard Administrativo. A tabela tarifas agora possui a coluna tipo e 10 registros (5 de viagem + 5 de entrega).

usuarios Perfis, veículo, geolocalização, selfie, biometria e modalidades.
tarifas 10 tarifas: 5 viagens + 5 entregas (+20%). Coluna tipo.
solicitacoes Viagens e entregas, com origem, destino e valores.
transacoes_financeiras Créditos, débitos, saques Pix e taxas da plataforma.
ocr_documentos Documentos processados com OCR (CNH, CRLV, antecedentes).
historico_reconhecimento_facial Logs de BioFace com score de similaridade.
avaliacoes Notas de 1 a 5 e comentários por solicitação.
mensagens_chat Chat interno associado a cada solicitação.
logs_acesso Auditoria de acessos por usuário, IP e dispositivo.
SQL federal_pro.sql
CREATE DATABASE IF NOT EXISTS `federal_pro` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `federal_pro`;

-- 1. USUÁRIOS
CREATE TABLE `usuarios` (
  `uid` VARCHAR(128) PRIMARY KEY,
  `nome` VARCHAR(150) NOT NULL,
  `email` VARCHAR(150) NOT NULL,
  `celular` VARCHAR(20) NOT NULL UNIQUE,
  `perfil` ENUM('cliente', 'motorista', 'entregador', 'admin') NOT NULL DEFAULT 'cliente',
  `aceita_viagem` TINYINT(1) NOT NULL DEFAULT 0,
  `aceita_entrega` TINYINT(1) NOT NULL DEFAULT 0,
  `status_aprovacao` ENUM('pendente', 'aprovado', 'bloqueado') NOT NULL DEFAULT 'pendente',
  `tipo_veiculo` ENUM('carro', 'moto') NULL,
  `categoria_veiculo` ENUM('unica', 'light', 'pro', 'confort', 'black') NULL,
  `placa` VARCHAR(10) NULL,
  `modelo` VARCHAR(100) NULL,
  `cor` VARCHAR(30) NULL,
  `ano` INT NULL,
  `is_online` TINYINT(1) DEFAULT 0,
  `geohash` VARCHAR(20) NULL,
  `lat` DECIMAL(10, 8) NULL,
  `lng` DECIMAL(11, 8) NULL,
  `push_token` VARCHAR(255) NULL,
  `selfie_url` VARCHAR(255) NULL,
  `selfie_verificada` TINYINT(1) DEFAULT 0,
  `biometria_ativada` TINYINT(1) DEFAULT 0,
  `ultima_checagem_facial` DATETIME NULL,
  `criado_em` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

-- 2. TARIFAS (5 viagens + 5 entregas com +20%)
CREATE TABLE `tarifas` (
  `id` VARCHAR(50) PRIMARY KEY,
  `tipo` ENUM('viagem', 'entrega') NOT NULL DEFAULT 'viagem',
  `tipo_veiculo` ENUM('carro', 'moto') NOT NULL,
  `categoria` VARCHAR(20) NOT NULL,
  `tarifa_base` DECIMAL(10,2) NOT NULL,
  `preco_por_km` DECIMAL(10,2) NOT NULL,
  `preco_por_minuto` DECIMAL(10,2) NOT NULL,
  `preco_minimo` DECIMAL(10,2) NOT NULL,
  `taxa_plataforma_percentual` DECIMAL(5,2) DEFAULT 15.00
) ENGINE=InnoDB;

-- VIAGENS
INSERT INTO `tarifas` VALUES
('viagem_moto_unica',    'viagem', 'moto',  'unica',   3.50, 1.20, 0.20,  6.00, 15.00),
('viagem_carro_light',   'viagem', 'carro', 'light',   4.50, 1.80, 0.25,  8.00, 15.00),
('viagem_carro_pro',     'viagem', 'carro', 'pro',     5.00, 2.00, 0.30,  9.50, 15.00),
('viagem_carro_confort', 'viagem', 'carro', 'confort', 6.00, 2.30, 0.35, 11.00, 15.00),
('viagem_carro_black',   'viagem', 'carro', 'black',   8.00, 3.00, 0.50, 15.00, 15.00);

-- ENTREGAS (valores de viagem + 20%)
INSERT INTO `tarifas` VALUES
('entrega_moto_unica',    'entrega', 'moto',  'unica',   4.20, 1.44, 0.24,  7.20, 15.00),
('entrega_carro_light',   'entrega', 'carro', 'light',   5.40, 2.16, 0.30,  9.60, 15.00),
('entrega_carro_pro',     'entrega', 'carro', 'pro',     6.00, 2.40, 0.36, 11.40, 15.00),
('entrega_carro_confort', 'entrega', 'carro', 'confort', 7.20, 2.76, 0.42, 13.20, 15.00),
('entrega_carro_black',   'entrega', 'carro', 'black',   9.60, 3.60, 0.60, 18.00, 15.00);

-- 3. SOLICITAÇÕES
CREATE TABLE `solicitacoes` (
  `id` VARCHAR(100) PRIMARY KEY,
  `tipo` ENUM('viagem', 'entrega') NOT NULL,
  `cliente_id` VARCHAR(128) NOT NULL,
  `motorista_id` VARCHAR(128) NULL,
  `status` ENUM('pendente', 'aceito', 'em_andamento', 'concluido', 'cancelado') DEFAULT 'pendente',
  `tipo_veiculo_exigido` ENUM('carro', 'moto') NOT NULL,
  `categoria_exigida` VARCHAR(20) NOT NULL,
  `tarifa_id` VARCHAR(50) NOT NULL,
  `origem_endereco` VARCHAR(255) NOT NULL,
  `origem_lat` DECIMAL(10, 8) NOT NULL,
  `origem_lng` DECIMAL(11, 8) NOT NULL,
  `destino_endereco` VARCHAR(255) NOT NULL,
  `destino_lat` DECIMAL(10, 8) NOT NULL,
  `destino_lng` DECIMAL(11, 8) NOT NULL,
  `destinatario_nome` VARCHAR(100) NULL,
  `destinatario_telefone` VARCHAR(20) NULL,
  `distancia_km` DECIMAL(8,2) NOT NULL,
  `duracao_minutos` INT NOT NULL,
  `valor_total` DECIMAL(10,2) NOT NULL,
  `taxa_plataforma` DECIMAL(10,2) NOT NULL,
  `valor_motorista` DECIMAL(10,2) NOT NULL,
  `forma_pagamento` ENUM('pix', 'dinheiro', 'cartao') NOT NULL,
  `pago` TINYINT(1) DEFAULT 0,
  `criado_em` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`cliente_id`) REFERENCES `usuarios`(`uid`),
  FOREIGN KEY (`motorista_id`) REFERENCES `usuarios`(`uid`),
  FOREIGN KEY (`tarifa_id`) REFERENCES `tarifas`(`id`)
) ENGINE=InnoDB;

-- 4. TRANSAÇÕES FINANCEIRAS
CREATE TABLE `transacoes_financeiras` (
  `id` VARCHAR(100) PRIMARY KEY,
  `user_id` VARCHAR(128) NOT NULL,
  `solicitacao_id` VARCHAR(100) NULL,
  `tipo` ENUM('credito', 'debito', 'saque_pix', 'taxa_plataforma') NOT NULL,
  `valor` DECIMAL(10,2) NOT NULL,
  `status` ENUM('pendente', 'concluido', 'falhou') DEFAULT 'concluido',
  `criado_em` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`user_id`) REFERENCES `usuarios`(`uid`),
  FOREIGN KEY (`solicitacao_id`) REFERENCES `solicitacoes`(`id`)
) ENGINE=InnoDB;

-- 5. OCR E DOCUMENTOS
CREATE TABLE `ocr_documentos` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `motorista_id` VARCHAR(128) NOT NULL,
  `tipo_documento` ENUM('cnh', 'crlv', 'antecedentes', 'foto_perfil') NOT NULL,
  `url_imagem` VARCHAR(255) NOT NULL,
  `nome_extraido` VARCHAR(150) NULL,
  `cpf_extraido` VARCHAR(20) NULL,
  `numero_documento` VARCHAR(50) NULL,
  `validade_documento` DATE NULL,
  `categoria_cnh` VARCHAR(10) NULL,
  `placa_extraida` VARCHAR(10) NULL,
  `renavam_extraido` VARCHAR(20) NULL,
  `confiabilidade_score` DECIMAL(5,2) NULL,
  `status_ocr` ENUM('sucesso', 'falha_leitura', 'divergencia_dados') NOT NULL,
  `status_aprovacao` ENUM('pendente', 'aprovado', 'rejeitado') DEFAULT 'pendente',
  `processado_em` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`motorista_id`) REFERENCES `usuarios`(`uid`)
) ENGINE=InnoDB;

-- 6. RECONHECIMENTO FACIAL E HISTÓRICO BIOFACE
CREATE TABLE `historico_reconhecimento_facial` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `user_id` VARCHAR(128) NOT NULL,
  `foto_capturada_url` VARCHAR(255) NOT NULL,
  `score_similaridade` DECIMAL(5,2) NULL,
  `status` ENUM('sucesso', 'falha', 'revisao_manual') NOT NULL,
  `motivo_falha` VARCHAR(255) NULL,
  `realizado_em` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`user_id`) REFERENCES `usuarios`(`uid`)
) ENGINE=InnoDB;

-- 7. AVALIAÇÕES
CREATE TABLE `avaliacoes` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `solicitacao_id` VARCHAR(100) NOT NULL,
  `avaliador_id` VARCHAR(128) NOT NULL,
  `avaliado_id` VARCHAR(128) NOT NULL,
  `nota` TINYINT NOT NULL CHECK (`nota` BETWEEN 1 AND 5),
  `comentario` TEXT NULL,
  `criado_em` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`solicitacao_id`) REFERENCES `solicitacoes`(`id`),
  FOREIGN KEY (`avaliador_id`) REFERENCES `usuarios`(`uid`),
  FOREIGN KEY (`avaliado_id`) REFERENCES `usuarios`(`uid`)
) ENGINE=InnoDB;

-- 8. CHAT INTERNO
CREATE TABLE `mensagens_chat` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `solicitacao_id` VARCHAR(100) NOT NULL,
  `remetente_id` VARCHAR(128) NOT NULL,
  `texto` TEXT NOT NULL,
  `criado_em` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`solicitacao_id`) REFERENCES `solicitacoes`(`id`),
  FOREIGN KEY (`remetente_id`) REFERENCES `usuarios`(`uid`)
) ENGINE=InnoDB;

-- 9. LOGS DE ACESSO
CREATE TABLE `logs_acesso` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `user_id` VARCHAR(128) NOT NULL,
  `celular` VARCHAR(20) NOT NULL,
  `acao` VARCHAR(50) NOT NULL,
  `ip_acesso` VARCHAR(45) NULL,
  `dispositivo` VARCHAR(100) NULL,
  `data_hora` DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`user_id`) REFERENCES `usuarios`(`uid`)
) ENGINE=InnoDB;

9. Segurança — regras do Firestore, RTDB e Storage

As regras de segurança controlam quem pode ler e escrever em cada documento, caminho e arquivo. O princípio é negar por padrão e liberar apenas o necessário.

Firestore Rules firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /usuarios/{userId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null
                    && request.auth.uid == userId;
      allow update: if request.auth != null
                    && request.auth.uid == userId
                    && request.resource.data.perfil == resource.data.perfil;
      allow delete: if false;
    }

    match /solicitacoes/{solicitacaoId} {
      allow create: if request.auth != null
                    && request.resource.data.clienteId == request.auth.uid
                    && request.resource.data.status == 'pendente';
      allow read: if request.auth != null
                  && (resource.data.clienteId == request.auth.uid
                      || resource.data.motoristaId == request.auth.uid);
      allow update: if request.auth != null
                    && (resource.data.clienteId == request.auth.uid
                        || resource.data.motoristaId == request.auth.uid);
    }

    match /documentos_motorista/{docId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
                   && request.auth.uid == resource.data.motoristaId;
    }

    match /tarifas/{tarifaId} {
      allow read: if request.auth != null;
      allow write: if false;
    }
  }
}
RTDB Rules database.rules.json
{
  "rules": {
    "localizacoes": {
      "motoristas": {
        "$uid": {
          ".read": "auth != null && auth.uid == $uid",
          ".write": "auth != null && auth.uid == $uid",
          ".validate": "newData.hasChildren(['lat', 'lng', 'updatedAt'])"
        }
      }
    },
    "presenca": {
      "motoristas": {
        "$uid": {
          ".read": "auth != null",
          ".write": "auth != null && auth.uid == $uid"
        }
      }
    },
    "corridasAtivas": {
      "$corridaId": {
        ".read": "auth != null && (
          data.child('passageiroId').val() == auth.uid ||
          data.child('motoristaId').val() == auth.uid
        )",
        ".write": "auth != null && auth.token.admin == true"
      }
    }
  }
}
Storage Rules storage.rules
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {

    match /motoristas/{motoristaId}/{documento} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
                   && request.auth.uid == motoristaId
                   && request.resource.size < 10 * 1024 * 1024;
    }

    match /perfil/{userId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
                   && request.auth.uid == userId;
    }
  }
}

10. Dashboard Administrativo Web

Painel em tempo real para monitorar motoristas online, corridas e entregas ativas, e o balanço financeiro consolidado — construído com Firestore onSnapshot para atualizações reativas.

JSX pages/DashboardAdmin.jsx
import React, { useEffect, useState } from 'react';
import { collection, query, where, onSnapshot, getDocs } from 'firebase/firestore';
import { db } from '../config/firebase';

export default function DashboardAdmin() {
  const [motoristasOnline, setMotoristasOnline] = useState([]);
  const [corridasAtivas, setCorridasAtivas] = useState([]);
  const [entregasAtivas, setEntregasAtivas] = useState([]);
  const [financeiro, setFinanceiro] = useState({ faturamentoBruto: 0, lucroPlataforma: 0 });

  useEffect(() => {
    const qMotoristas = query(
      collection(db, 'usuarios'),
      where('perfil', '==', 'motorista'),
      where('isOnline', '==', true)
    );
    const unsubMotoristas = onSnapshot(qMotoristas, (snap) => {
      setMotoristasOnline(snap.docs.map(d => ({ id: d.id, ...d.data() })));
    });

    const qCorridas = query(
      collection(db, 'solicitacoes'),
      where('tipo', '==', 'viagem'),
      where('status', 'in', ['pendente', 'aceito', 'em_andamento'])
    );
    const unsubCorridas = onSnapshot(qCorridas, (snap) => {
      setCorridasAtivas(snap.docs.map(d => ({ id: d.id, ...d.data() })));
    });

    const qEntregas = query(
      collection(db, 'solicitacoes'),
      where('tipo', '==', 'entrega'),
      where('status', 'in', ['pendente', 'aceito', 'em_andamento'])
    );
    const unsubEntregas = onSnapshot(qEntregas, (snap) => {
      setEntregasAtivas(snap.docs.map(d => ({ id: d.id, ...d.data() })));
    });

    const carregarFinanceiro = async () => {
      const q = query(collection(db, 'solicitacoes'), where('status', '==', 'concluido'));
      const snap = await getDocs(q);
      let bruto = 0, lucro = 0;
      snap.forEach(doc => {
        const d = doc.data();
        bruto += d.valorTotal || 0;
        lucro += d.taxaPlataforma || 0;
      });
      setFinanceiro({ faturamentoBruto: bruto, lucroPlataforma: lucro });
    };

    carregarFinanceiro();

    return () => {
      unsubMotoristas();
      unsubCorridas();
      unsubEntregas();
    };
  }, []);

  return (
    <div style={{ padding: 24, backgroundColor: '#F8FAFC', fontFamily: 'sans-serif' }}>
      <h2>Painel Administrativo - Federal Pro</h2>
      <div style={{ display: 'flex', gap: 16, marginBottom: 24, flexWrap: 'wrap' }}>
        <div style={cardStyle}>
          <h4>Motoristas Online</h4>
          <p style={valStyle}>{motoristasOnline.length}</p>
        </div>
        <div style={cardStyle}>
          <h4>Viagens Ativas</h4>
          <p style={valStyle}>{corridasAtivas.length}</p>
        </div>
        <div style={cardStyle}>
          <h4>Entregas Ativas</h4>
          <p style={valStyle}>{entregasAtivas.length}</p>
        </div>
        <div style={cardStyle}>
          <h4>Faturamento Bruto</h4>
          <p style={valStyle}>R$ {financeiro.faturamentoBruto.toFixed(2)}</p>
        </div>
        <div style={cardStyle}>
          <h4>Lucro da Plataforma</h4>
          <p style={valStyle}>R$ {financeiro.lucroPlataforma.toFixed(2)}</p>
        </div>
      </div>
    </div>
  );
}

const cardStyle = { padding: 16, backgroundColor: '#FFF', borderRadius: 8, width: 200, boxShadow: '0 2px 4px rgba(0,0,0,0.05)' };
const valStyle = { fontSize: 22, fontWeight: 'bold', margin: '8px 0 0 0', color: '#660099' };
● Motoristas Online

Monitor reativo via onSnapshot filtrando perfil == motorista e isOnline == true.

● Viagens e Entregas

Contadores separados por tipo == viagem e tipo == entrega, com atualização contínua.

● Financeiro

Somatório de valorTotal e taxaPlataforma sobre viagens e entregas concluídas.