Solicitam viagens ou entregas pelo app. Autenticação via e-mail e senha com JWT. Perfil cliente.
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.
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.
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%).
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)
Entrega Tarifas de entrega +20% sobre a viagem
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.
usuarios
Documento único por usuário. Agora com o campo modalidades para o profissional escolher entre viagem, entrega ou ambos.
{
"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"
}
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).
{
"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
}
solicitacoes
Cada viagem ou entrega. O campo tipo decide qual conjunto de tarifas é aplicado no cálculo do valorTotal.
{
"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"
}
documentos_motorista
Documentos enviados com processamento OCR e score de similaridade facial (BioFace).
{
"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.
Caminho localizacoes/motoristas/{uid} atualizado a cada poucos segundos pelo app do motorista. Contém lat, lng, heading, speed, accuracy e updatedAt.
Caminho presenca/motoristas/{uid} com online, disponivel, modalidades e ultimaAtividade. Permite saber quem está ativo para viagem e/ou entrega sem consultar o Firestore.
Caminho corridasAtivas/{corridaId} com motoristaId, passageiroId, status, tipo e atualizadoEm. Leitura rápida em tempo real para o app.
{
"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
}
}
}
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.
CNH, CRLV e antecedentes criminais enviados no cadastro. Processados por OCR e verificados por BioFace.
Fotos capturadas no app para validação facial. Comparadas com a foto do documento via score de similaridade.
Avatares de clientes e motoristas exibidos no app e no painel administrativo.
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 };
}
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.
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.
Método principal de autenticação. Inclui verificação de e-mail, recuperação de senha e login sem senha (link mágico).
Google, Apple, Facebook, GitHub, Microsoft, Twitter. Reduz a fricção no cadastro e é gratuito até 50K MAU.
Permite que o usuário experimente o app antes de se registrar. Se decidir criar conta, os dados são preservados.
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);
}
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).
tipo.
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.
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;
}
}
}
{
"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"
}
}
}
}
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.
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' };
Monitor reativo via onSnapshot filtrando perfil == motorista e isOnline == true.
Contadores separados por tipo == viagem e tipo == entrega, com atualização contínua.
Somatório de valorTotal e taxaPlataforma sobre viagens e entregas concluídas.