# 🚚 **SISTEMA DE ROTEIRIZAÇÃO TSP - EXPO REACT NATIVE** ## **📋 VISÃO GERAL** Sistema completo de otimização de rotas de entrega implementando o algoritmo **Traveling Salesman Problem (TSP)** usando **Nearest Neighbor** para organizar entregas pela distância mais eficiente. **URL Base da API:** `https://api.entrega.homologacao.jurunense.com` --- ## **🏗️ ARQUITETURA DO SISTEMA** ### **Estrutura de Arquivos** ``` src/ ├── screens/ │ └── main/ │ └── RoutingScreen.tsx # Tela principal de roteirização ├── services/ │ └── api.ts # Serviços de API e algoritmos TSP ├── types/ │ └── index.ts # Tipos TypeScript ├── contexts/ │ └── AuthContext.tsx # Contexto de autenticação └── config/ └── env.ts # Configurações de ambiente ``` ### **Componentes Principais** - **RoutingScreen**: Interface para execução de roteirização - **ApiService**: Classe para comunicação com API - **Algoritmos TSP**: Funções de otimização de rota --- ## **🔧 IMPLEMENTAÇÃO PASSO A PASSO** ### **PASSO 1: CONFIGURAÇÃO DE AMBIENTE** ```typescript // src/config/env.ts export const API_BASE_URL = 'https://api.entrega.homologacao.jurunense.com'; export const AUTH_TOKEN_KEY = 'AUTH_TOKEN'; export const USER_DATA_KEY = 'USER_DATA'; // Função para converter coordenadas (vírgula → ponto) export const convertCoordinate = (coord: any): number => { if (coord === null || coord === undefined || coord === '') { return 0; } if (typeof coord === 'number') { return coord; } if (typeof coord === 'string') { const normalized = coord.trim().replace(',', '.'); const parsed = parseFloat(normalized); return isNaN(parsed) ? 0 : parsed; } return 0; }; ``` ### **PASSO 2: TIPOS TYPESCRIPT** ```typescript // src/types/index.ts export interface Delivery { id?: string; outId: number; customerId: number; customerName: string; street: string; streetNumber: string; neighborhood: string; city: string; state: string; zipCode: string; lat: number | string | null; lng: number | string | null; coordinates?: { latitude: number | string; longitude: number | string; }; deliverySeq: number; routing: number; // 0 = não roteirizada, 1 = roteirizada status: 'pending' | 'in_progress' | 'delivered' | 'failed'; outDate: string; latFrom?: string; lngFrom?: string; } export interface RoutingData { outId: number; customerId: number; deliverySeq: number; lat: number; lng: number; } ``` ### **PASSO 3: FUNÇÕES UTILITÁRIAS** ```typescript // src/services/api.ts // 1. Cálculo de distância (Fórmula de Haversine) export const calculateDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { const R = 6371; // Raio da Terra em km const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; }; // 2. Centro de distribuição dinâmico export const getDistributionCenter = (deliveries: Delivery[]): { latitude: number; longitude: number; address: string } => { const deliveryWithCoords = deliveries.find(delivery => delivery.latFrom && delivery.lngFrom && delivery.latFrom !== 'null' && delivery.lngFrom !== 'null' && delivery.latFrom !== '' && delivery.lngFrom !== '' ); if (deliveryWithCoords && deliveryWithCoords.latFrom && deliveryWithCoords.lngFrom) { const lat = parseFloat(deliveryWithCoords.latFrom); const lng = parseFloat(deliveryWithCoords.lngFrom); if (!isNaN(lat) && !isNaN(lng)) { return { latitude: lat, longitude: lng, address: "Centro de Distribuição (API)" }; } } // Fallback para coordenadas padrão return { latitude: -1.3654, longitude: -48.3722, address: "Centro de Distribuição (Padrão)" }; }; ``` ### **PASSO 4: ALGORITMO TSP - NEAREST NEIGHBOR** ```typescript // src/services/api.ts const nearestNeighborTSP = (distanceMatrix: number[][], deliveries: Delivery[]): Delivery[] => { console.log('=== 🔍 EXECUTANDO ALGORITMO NEAREST NEIGHBOR ==='); const n = deliveries.length; const visited = new Array(n).fill(false); const route: Delivery[] = []; // Começar do centro de distribuição (índice 0 na matriz) let currentIndex = 0; visited[0] = true; // Adicionar primeira entrega (mais próxima do centro) if (n > 0) { route.push(deliveries[0]); console.log(`📍 Primeira entrega: ${deliveries[0].customerName} (mais próxima do centro)`); } // Encontrar o próximo ponto mais próximo for (let step = 1; step < n; step++) { let minDistance = Infinity; let nextIndex = -1; // Procurar o ponto não visitado mais próximo for (let i = 1; i < n; i++) { // Pular índice 0 (centro de distribuição) if (!visited[i]) { const distance = distanceMatrix[currentIndex][i]; if (distance < minDistance) { minDistance = distance; nextIndex = i; } } } if (nextIndex !== -1) { visited[nextIndex] = true; route.push(deliveries[nextIndex]); console.log(`📍 Próxima entrega: ${deliveries[nextIndex].customerName} (distância: ${minDistance.toFixed(2)} km)`); currentIndex = nextIndex; } } console.log(`✅ Rota otimizada criada com ${route.length} entregas`); return route; }; ``` ### **PASSO 5: FUNÇÃO PRINCIPAL DE OTIMIZAÇÃO** ```typescript // src/services/api.ts export const optimizeRouteWithTSP = async (deliveries: Delivery[]): Promise => { console.log('=== 🚚 INICIANDO OTIMIZAÇÃO DE ROTA COM ALGORITMO TSP ==='); console.log('Total de entregas para otimizar:', deliveries.length); // 1. Obter centro de distribuição const distributionCenter = getDistributionCenter(deliveries); console.log('Centro usado:', distributionCenter); // 2. Preparar entregas garantindo coordenadas const preparedDeliveries: Delivery[] = []; for (let index = 0; index < deliveries.length; index++) { const delivery = deliveries[index]; let latNum: number | null = null; let lngNum: number | null = null; // Tentar usar lat/lng já existentes if (typeof delivery.lat === 'number' && typeof delivery.lng === 'number' && !isNaN(delivery.lat) && !isNaN(delivery.lng)) { latNum = delivery.lat; lngNum = delivery.lng; } // Normalizar strings (vírgula → ponto) if ((latNum === null || lngNum === null) && (typeof delivery.lat === 'string' || typeof delivery.lng === 'string')) { const maybeLat = convertCoordinate(delivery.lat); const maybeLng = convertCoordinate(delivery.lng); if (typeof maybeLat === 'number' && !isNaN(maybeLat)) latNum = maybeLat; if (typeof maybeLng === 'number' && !isNaN(maybeLng)) lngNum = maybeLng; } // Usar coordinates se existirem if ((latNum === null || lngNum === null) && delivery.coordinates) { const maybeLat = convertCoordinate((delivery.coordinates as any).latitude); const maybeLng = convertCoordinate((delivery.coordinates as any).longitude); if (typeof maybeLat === 'number' && !isNaN(maybeLat)) latNum = maybeLat; if (typeof maybeLng === 'number' && !isNaN(maybeLng)) lngNum = maybeLng; } // 4. Se ainda não tem coordenadas, tentar geocodificar if (latNum === null || lngNum === null) { try { const coords = await getCoordinatesFromAddress({ address: delivery.street || '', addressNumber: delivery.streetNumber || '', neighborhood: delivery.neighborhood || '', city: delivery.city || '', state: delivery.state || '' }); if (coords) { latNum = coords.latitude; lngNum = coords.longitude; console.log('✅ Geolocalização obtida:', coords); } } catch (geoErr) { console.warn('⚠️ Falha ao geolocalizar:', geoErr); } } preparedDeliveries.push({ ...delivery, lat: typeof latNum === 'number' ? latNum : delivery.lat, lng: typeof lngNum === 'number' ? lngNum : delivery.lng, coordinates: (typeof latNum === 'number' && typeof lngNum === 'number') ? { latitude: latNum, longitude: lngNum } : delivery.coordinates }); } // 3. Separar entregas com e sem coordenadas const withCoords = preparedDeliveries.filter(d => typeof d.lat === 'number' && typeof d.lng === 'number' && !isNaN(d.lat as number) && !isNaN(d.lng as number) ); const withoutCoords = preparedDeliveries.filter(d => !(typeof d.lat === 'number' && typeof d.lng === 'number' && !isNaN(d.lat as number) && !isNaN(d.lng as number)) ); if (withCoords.length === 0) { console.log('❌ Nenhuma entrega com coordenadas válidas'); return preparedDeliveries.map((d, i) => ({ ...d, deliverySeq: i + 1 })); } console.log(`✅ ${withCoords.length} entregas com coordenadas válidas`); // 4. Calcular matriz de distâncias const distanceMatrix: number[][] = []; const allPoints = [distributionCenter, ...withCoords.map(d => ({ latitude: d.lat as number, longitude: d.lng as number }))]; for (let i = 0; i < allPoints.length; i++) { distanceMatrix[i] = []; for (let j = 0; j < allPoints.length; j++) { if (i === j) { distanceMatrix[i][j] = 0; } else { distanceMatrix[i][j] = calculateDistance( allPoints[i].latitude, allPoints[i].longitude, allPoints[j].latitude, allPoints[j].longitude ); } } } // 5. Executar algoritmo TSP const optimizedWithCoords = nearestNeighborTSP(distanceMatrix, withCoords); // 6. Montar rota final const finalRoute: Delivery[] = [...optimizedWithCoords, ...withoutCoords]; // 7. Aplicar deliverySeq sequencialmente const finalDeliveries = finalRoute.map((delivery, idx) => { const newSeq = idx + 1; // Garantir que comece em 1 console.log(`🔄 Atualizando ${delivery.customerName}: deliverySeq ${delivery.deliverySeq} → ${newSeq}`); return { ...delivery, deliverySeq: newSeq, distance: (delivery as any).distance || 0 }; }); console.log('=== 🎯 ENTREGAS COM DELIVERYSEQ ATUALIZADO ==='); finalDeliveries.forEach((delivery, index) => { console.log(`📦 Entrega ${index + 1} (deliverySeq: ${delivery.deliverySeq}): ${delivery.customerName}`); }); return finalDeliveries; }; ``` ### **PASSO 6: SERVIÇO DE API** ```typescript // src/services/api.ts class ApiService { private baseUrl: string; private token: string | null = null; constructor() { this.baseUrl = 'https://api.entrega.homologacao.jurunense.com'; this.loadToken(); } // Enviar ordem de roteamento async sendRoutingOrder(routingData: RoutingData[]): Promise { try { console.log('=== DEBUG: INICIANDO sendRoutingOrder ==='); console.log('Dados recebidos:', JSON.stringify(routingData, null, 2)); // Validar e normalizar coordenadas const validatedRoutingData = routingData.map((item, index) => { let lat: any = item.lat; let lng: any = item.lng; // Converter strings com vírgula para ponto if (typeof lat === 'string') { lat = parseFloat(lat.replace(',', '.')); if (isNaN(lat)) { console.warn(`⚠️ Coordenada lat inválida: ${item.lat}, usando 0`); lat = 0; } } if (typeof lng === 'string') { lng = parseFloat(lng.replace(',', '.')); if (isNaN(lng)) { console.warn(`⚠️ Coordenada lng inválida: ${item.lng}, usando 0`); lng = 0; } } // Garantir que sejam números lat = Number(lat) || 0; lng = Number(lng) || 0; return { ...item, lat, lng }; }); const token = await this.loadToken(); if (!token) throw new Error('Token não encontrado'); const ENDPOINT = `${this.baseUrl}/v1/driver/routing`; const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }; const response = await fetch(ENDPOINT, { method: 'POST', headers, body: JSON.stringify(validatedRoutingData) }); if (response.status === 401) { throw new Error('Sessão expirada. Faça login novamente.'); } const result = await response.json(); if (!response.ok || !result.success) { throw new Error(result.message || 'Erro ao enviar ordem de roteamento'); } console.log('✅ Roteirização bem-sucedida'); return result.data; } catch (error) { console.error('❌ Erro em sendRoutingOrder:', error); throw error; } } // Carregar entregas async getDeliveries(): Promise { try { const token = await this.loadToken(); if (!token) throw new Error('Token não encontrado'); const response = await fetch(`${this.baseUrl}/v1/driver/deliveries`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.status === 401) { throw new Error('Sessão expirada'); } const result = await response.json(); return Array.isArray(result) ? result : []; } catch (error) { console.error('❌ Erro ao carregar entregas:', error); throw error; } } private async loadToken(): Promise { try { const storedToken = await AsyncStorage.getItem(AUTH_TOKEN_KEY); if (storedToken) { this.token = storedToken; return storedToken; } return null; } catch (error) { console.error("Erro ao carregar token:", error); return null; } } } export const api = new ApiService(); ``` ### **PASSO 7: TELA PRINCIPAL DE ROTEIRIZAÇÃO** ```typescript // src/screens/main/RoutingScreen.tsx import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Alert, RefreshControl } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { COLORS, SHADOWS } from '../../constants/theme'; import { Delivery } from '../../types'; import { api, optimizeRouteWithTSP } from '../../services/api'; const RoutingScreen: React.FC<{ navigation: any; route: any }> = ({ navigation, route }) => { const [deliveries, setDeliveries] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isRouting, setIsRouting] = useState(false); const [routingProgress, setRoutingProgress] = useState(0); const [error, setError] = useState(null); // Carregar entregas iniciais useEffect(() => { loadDeliveries(); }, []); // Carregar entregas do endpoint const loadDeliveries = async () => { try { setIsLoading(true); setError(null); const response = await api.getDeliveries(); if (response) { const deliveriesData = Array.isArray(response) ? response : []; console.log('📦 Entregas processadas:', deliveriesData.length); // Ordenar entregas usando algoritmo TSP try { const sortedDeliveries = await optimizeRouteWithTSP(deliveriesData); console.log('✅ Entregas ordenadas com sucesso:', sortedDeliveries.length); setDeliveries(sortedDeliveries); // Verificar se já tem roteirização const hasRouting = deliveriesData.some((delivery: Delivery) => delivery.routing === 1); if (hasRouting) { console.log('✅ Já tem roteirização - Redirecionando'); navigation.reset({ index: 0, routes: [{ name: 'Main' as never }], }); } } catch (sortError) { console.error('❌ Erro ao ordenar entregas:', sortError); setDeliveries(deliveriesData); } } } catch (error: any) { console.error('❌ Erro ao carregar entregas:', error); setError(error.message || 'Erro ao carregar entregas'); } finally { setIsLoading(false); } }; // Executar roteirização const executeRouting = async () => { try { setIsRouting(true); setRoutingProgress(0); setError(null); // Filtrar entregas sem roteirização const deliveriesToRoute = deliveries.filter(delivery => delivery.routing === 0); if (deliveriesToRoute.length === 0) { Alert.alert('Info', 'Todas as entregas já estão roteirizadas'); return; } setRoutingProgress(20); // SOLUÇÃO DEFINITIVA: OTIMIZAR ROTA ANTES DE ENVIAR PARA API console.log('🎯 OTIMIZANDO ROTA COM ALGORITMO TSP ANTES DO ENVIO...'); const optimizedDeliveries = await optimizeRouteWithTSP(deliveriesToRoute); console.log('✅ Rota otimizada com sucesso'); if (optimizedDeliveries && optimizedDeliveries.length > 0) { // Preparar dados OTIMIZADOS para roteirização const optimizedRoutingData = optimizedDeliveries.map((delivery) => ({ outId: delivery.outId, customerId: delivery.customerId, deliverySeq: delivery.deliverySeq, // JÁ OTIMIZADO (1, 2, 3, ...) lat: delivery.lat as number, lng: delivery.lng as number })); setRoutingProgress(60); // Executar roteirização com dados OTIMIZADOS const routingResponse = await api.sendRoutingOrder(optimizedRoutingData); setRoutingProgress(80); if (routingResponse) { console.log('✅ Roteirização com sequência otimizada executada com sucesso!'); // Atualizar estado local setDeliveries(optimizedDeliveries); setRoutingProgress(100); Alert.alert( 'Roteirização Concluída! 🎉', `As entregas foram organizadas e ordenadas com sucesso!\n\nTotal: ${optimizedDeliveries.length} entregas\nPróxima: ${optimizedDeliveries[0]?.customerName || 'N/A'}`, [ { text: 'OK', onPress: () => { navigation.reset({ index: 0, routes: [{ name: 'Main' as never, params: { screen: 'Home', params: { routingUpdated: true, refreshDeliveries: true } } }], }); } } ] ); } } } catch (error: any) { console.error('Erro na roteirização:', error); setError(error.message || 'Erro ao executar roteirização'); Alert.alert('Erro', 'Falha na roteirização. Tente novamente.'); } finally { setIsRouting(false); setRoutingProgress(0); } }; // Refresh control const onRefresh = async () => { try { await loadDeliveries(); } catch (error: any) { console.error('Erro no refresh:', error); } }; if (isLoading) { return ( Carregando entregas... ); } return ( {/* Header */} navigation.reset({ index: 0, routes: [{ name: 'Main' as never }], })} > Roteirização de Entregas {/* Conteúdo */} }> {/* Resumo */} Resumo das Entregas {deliveries.length} Total {deliveries.filter(d => d.routing === 0).length} Pendentes {deliveries.filter(d => d.routing === 1).length} Roteirizadas {/* Informações de Ordenação */} {deliveries.length > 0 && ( 📍 Ordenação por Sequência Próxima entrega: {deliveries[0]?.customerName || 'N/A'} Sequência: #{deliveries[0]?.deliverySeq || 'N/A'} {deliveries[0]?.distance && ( Distância: {deliveries[0].distance.toFixed(2)} km )} )} {/* Botão de Roteirização */} {deliveries.some(d => d.routing === 0) && ( {isRouting ? ( Roteirizando... {routingProgress}% ) : ( <> Executar Roteirização )} )} {/* Lista de Entregas */} Entregas ({deliveries.length}) {deliveries.map((delivery, index) => ( #{delivery.outId} {getStatusText(delivery.status)} {delivery.customerName} {delivery.street}, {delivery.streetNumber} {delivery.neighborhood}, {delivery.city} {delivery.routing === 0 ? 'Aguardando roteirização' : 'Roteirizada'} ))} {/* Mensagem de erro */} {error && ( {error} )} ); }; // Funções auxiliares const getStatusColor = (status: string) => { switch (status) { case 'delivered': return COLORS.success; case 'in_progress': return COLORS.info; case 'failed': return COLORS.danger; default: return COLORS.warning; } }; const getStatusText = (status: string) => { switch (status) { case 'delivered': return 'Entregue'; case 'in_progress': return 'Em andamento'; case 'failed': return 'Falhou'; default: return 'Pendente'; } }; // Estilos (implementar conforme necessário) const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5F5F5', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingTop: 50, paddingBottom: 20, paddingHorizontal: 20, }, backButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.2)', alignItems: 'center', justifyContent: 'center', }, headerTitle: { fontSize: 18, fontWeight: 'bold', color: 'white', flex: 1, textAlign: 'center', }, refreshButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.2)', alignItems: 'center', justifyContent: 'center', }, content: { flex: 1, padding: 20, }, summaryCard: { backgroundColor: 'white', borderRadius: 12, padding: 20, marginBottom: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, summaryTitle: { fontSize: 16, fontWeight: '600', color: '#333', marginBottom: 16, }, summaryStats: { flexDirection: 'row', justifyContent: 'space-around', }, statItem: { alignItems: 'center', }, statNumber: { fontSize: 24, fontWeight: 'bold', color: '#007AFF', }, statLabel: { fontSize: 12, color: '#666', marginTop: 4, }, routingButton: { marginBottom: 20, borderRadius: 12, overflow: 'hidden', }, routingButtonDisabled: { opacity: 0.7, }, routingButtonGradient: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 16, paddingHorizontal: 24, }, routingButtonText: { color: 'white', fontSize: 16, fontWeight: '600', marginLeft: 8, }, routingProgress: { flexDirection: 'row', alignItems: 'center', }, routingProgressText: { color: 'white', fontSize: 14, fontWeight: '500', marginLeft: 8, }, deliveriesSection: { marginBottom: 20, }, sectionTitle: { fontSize: 18, fontWeight: '600', color: '#333', marginBottom: 16, }, deliveryItem: { backgroundColor: 'white', borderRadius: 12, padding: 16, marginBottom: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 2, }, deliveryHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, deliveryNumber: { fontSize: 14, fontWeight: '600', color: '#007AFF', }, statusBadge: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 8, }, statusText: { fontSize: 10, fontWeight: '500', color: 'white', }, customerName: { fontSize: 16, fontWeight: '600', color: '#333', marginBottom: 4, }, address: { fontSize: 14, color: '#666', marginBottom: 8, }, deliveryMeta: { flexDirection: 'row', justifyContent: 'space-between', }, metaItem: { flexDirection: 'row', alignItems: 'center', }, metaText: { fontSize: 12, color: '#666', marginLeft: 4, }, loadingContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#F5F5F5', }, loadingText: { marginTop: 16, fontSize: 16, color: '#333', }, errorContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#FEE2E2', padding: 16, borderRadius: 12, marginTop: 20, }, errorText: { marginLeft: 8, fontSize: 14, color: '#DC2626', flex: 1, }, orderingInfo: { marginTop: 16, padding: 16, backgroundColor: '#E0F2FE', borderRadius: 8, borderLeftWidth: 4, borderLeftColor: '#007AFF', }, orderingTitle: { fontSize: 14, fontWeight: '600', color: '#007AFF', marginBottom: 8, }, orderingText: { fontSize: 12, color: '#666', marginBottom: 4, }, }); export default RoutingScreen; ``` --- ## **🚀 FLUXO COMPLETO DE ROTEIRIZAÇÃO** ### **1. CARREGAMENTO INICIAL** - ✅ Carregar entregas da API (`/v1/driver/deliveries`) - ✅ Verificar se já tem roteirização (`routing === 1`) - ✅ Se sim → redirecionar para Home - ✅ Se não → mostrar tela de roteirização ### **2. EXECUÇÃO DE ROTEIRIZAÇÃO** - ✅ Filtrar entregas pendentes (`routing === 0`) - ✅ **OTIMIZAR ROTA COM TSP** (antes de enviar para API) - ✅ Preparar dados otimizados - ✅ Enviar para API com `deliverySeq` já otimizado - ✅ Atualizar estado local - ✅ Redirecionar para Home ### **3. ALGORITMO TSP** - ✅ Obter centro de distribuição - ✅ Preparar coordenadas (normalizar + geolocalizar) - ✅ Calcular matriz de distâncias - ✅ Executar Nearest Neighbor - ✅ Aplicar sequência sequencial (1, 2, 3, ...) --- ## ** PONTOS CRÍTICOS DE IMPLEMENTAÇÃO** ### **1. ORDEM DE EXECUÇÃO CORRETA** ```typescript // ❌ ERRADO: Enviar para API primeiro await api.sendRoutingOrder(routingData); const optimized = await optimizeRouteWithTSP(deliveries); // ✅ CORRETO: Otimizar primeiro, depois enviar const optimized = await optimizeRouteWithTSP(deliveries); await api.sendRoutingOrder(optimizedRoutingData); ``` ### **2. NORMALIZAÇÃO DE COORDENADAS** ```typescript // Sempre converter vírgula para ponto const lat = parseFloat(coord.replace(',', '.')); ``` ### **3. SEQUÊNCIA INICIAL** ```typescript // Garantir que deliverySeq comece em 1, não em 0 const newSeq = idx + 1; // ✅ Correto const newSeq = idx; // ❌ Errado (começa em 0) ``` ### **4. TRATAMENTO DE ERROS** ```typescript try { // Lógica principal } catch (error: any) { console.error('Erro detalhado:', error); // Sempre reabilitar botões e mostrar feedback } finally { // Limpeza obrigatória setIsRouting(false); setRoutingProgress(0); } ``` --- ## **📱 DEPENDÊNCIAS NECESSÁRIAS** ```json { "dependencies": { "@react-native-async-storage/async-storage": "^1.19.0", "expo-linear-gradient": "^12.0.0", "@expo/vector-icons": "^13.0.0", "react-native-safe-area-context": "^4.7.0" } } ``` --- ## **🔗 ENDPOINTS DA API** ### **Base URL:** `https://api.entrega.homologacao.jurunense.com` | Endpoint | Método | Descrição | |----------|--------|-----------| | `/v1/driver/deliveries` | GET | Carregar lista de entregas | | `/v1/driver/routing` | POST | Enviar ordem de roteirização | | `/v1/geolocation/google` | GET | Geocodificar endereços | --- ## **📊 ESTRUTURA DE DADOS** ### **Entrada da API (sendRoutingOrder)** ```json [ { "outId": 3673, "customerId": 422973, "deliverySeq": 1, "lat": -1.3461972, "lng": -48.3938122 } ] ``` ### **Resposta da API** ```json { "success": true, "message": "Roteirização de entrega atualizada com sucesso!", "data": { "message": "Roteirização de entrega atualizada com sucesso!" }, "timestamp": "2025-08-18T14:50:34.618Z", "statusCode": 201 } ``` --- ## **🎯 RESULTADO ESPERADO** Após implementação, o sistema deve: 1. **Carregar entregas** automaticamente da API 2. **Otimizar rotas** usando algoritmo TSP (Nearest Neighbor) 3. **Enviar sequência** otimizada para API (`/v1/driver/routing`) 4. **Persistir dados** no banco de dados 5. **Redirecionar** para tela principal 6. **Manter ordem** das entregas em todas as telas **Este sistema garante que as entregas sejam sempre ordenadas pela distância mais eficiente, começando do centro de distribuição e seguindo o algoritmo do caixeiro viajante! 🚚✨** --- ## **📝 NOTAS IMPORTANTES** - **URL da API**: `https://api.entrega.homologacao.jurunense.com` - **Algoritmo**: Nearest Neighbor TSP - **Sequência**: Sempre começa em 1 (não em 0) - **Coordenadas**: Sempre normalizar (vírgula → ponto) - **Ordem**: Otimizar ANTES de enviar para API - **Tratamento de Erro**: Sempre implementar try-catch-finally