entregas_app/docs/GUIA_IMPLEMENTACAO_SINCRONI...

1239 lines
33 KiB
Markdown
Raw Permalink Normal View History

# Guia de Implementação - Sincronização Offline
## Visão Geral
Este guia fornece instruções passo a passo para implementar a sincronização offline no aplicativo de entregas, baseado na análise completa da arquitetura existente.
## Fase 1: Preparação e Configuração
### 1.1 Instalação de Dependências
```bash
# Dependências para sincronização
npm install lz-string crypto-js
npm install @react-native-async-storage/async-storage
npm install expo-background-fetch expo-task-manager
# Dependências para desenvolvimento
npm install --save-dev @types/lz-string @types/crypto-js
```
### 1.2 Configuração de Variáveis de Ambiente
**Arquivo**: `.env`
```env
# Configurações de sincronização
SYNC_INTERVAL=900000
SYNC_RETRY_ATTEMPTS=3
SYNC_TIMEOUT=30000
MAX_CACHE_SIZE=100
ENCRYPTION_KEY=your-secret-encryption-key
# Configurações de API
API_BASE_URL=https://api.example.com
API_TIMEOUT=10000
```
### 1.3 Atualização do Banco de Dados
**Arquivo**: `src/services/database.ts`
```typescript
// Adicionar ao setupDatabase
export const setupDatabase = async (): Promise<void> => {
if (usingSQLite) {
return new Promise<void>((resolve, reject) => {
db.transaction(
(tx: any) => {
// Tabelas existentes...
// Nova tabela de controle de sincronização
tx.executeSql(
`CREATE TABLE IF NOT EXISTS sync_control (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT NOT NULL,
last_sync_timestamp INTEGER,
sync_status TEXT DEFAULT 'pending',
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
);`
);
// Nova tabela de conflitos
tx.executeSql(
`CREATE TABLE IF NOT EXISTS sync_conflicts (
id TEXT PRIMARY KEY,
table_name TEXT NOT NULL,
record_id TEXT NOT NULL,
local_data TEXT,
server_data TEXT,
conflict_fields TEXT,
resolution TEXT,
resolved_at INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);`
);
// Nova tabela de log de sincronização
tx.executeSql(
`CREATE TABLE IF NOT EXISTS sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_type TEXT NOT NULL,
table_name TEXT,
record_id TEXT,
action TEXT,
success INTEGER DEFAULT 1,
error_message TEXT,
duration INTEGER,
timestamp INTEGER DEFAULT (strftime('%s', 'now'))
);`
);
// Adicionar campos de controle às tabelas existentes
tx.executeSql(
`ALTER TABLE deliveries ADD COLUMN version INTEGER DEFAULT 1;`
);
tx.executeSql(
`ALTER TABLE deliveries ADD COLUMN last_modified INTEGER DEFAULT (strftime('%s', 'now'));`
);
tx.executeSql(
`ALTER TABLE deliveries ADD COLUMN sync_timestamp INTEGER;`
);
// Inserir configurações de sincronização
tx.executeSql(
`INSERT OR IGNORE INTO settings (key, value) VALUES
('initial_sync_complete', 'false'),
('last_sync_time', '0'),
('sync_interval', '900000'),
('auto_sync_enabled', 'true');`
);
},
(error: any) => reject(error),
() => resolve()
);
});
}
};
```
## Fase 2: Implementação dos Serviços Base
### 2.1 Serviço de Sincronização Principal
**Arquivo**: `src/services/syncService.ts`
```typescript
import { api } from './api';
import { executeQuery, saveSetting, getSetting } from './database';
import { offlineStorage } from './offlineStorage';
export enum SyncStatus {
SYNCED = 'synced',
PENDING = 'pending',
CONFLICT = 'conflict',
ERROR = 'error',
OFFLINE = 'offline'
}
export enum SyncType {
FULL = 'full',
INCREMENTAL = 'incremental',
SELECTIVE = 'selective'
}
interface SyncResult {
success: boolean;
totalRecords: number;
syncedRecords: number;
errors: Array<{ recordId: string; error: string }>;
duration: number;
}
class SyncService {
private isInitialized = false;
constructor() {
this.initializeSyncService();
}
private async initializeSyncService(): Promise<void> {
try {
// Verificar se já foi feita sincronização inicial
const initialSyncComplete = await getSetting('initial_sync_complete');
if (initialSyncComplete === 'false') {
console.log('Sincronização inicial necessária');
// Não iniciar automaticamente - aguardar ação do usuário
} else {
console.log('Sincronização inicial já foi realizada');
this.isInitialized = true;
}
} catch (error) {
console.error('Erro ao inicializar serviço de sincronização:', error);
}
}
// Sincronização inicial completa
async performInitialSync(): Promise<SyncResult> {
try {
console.log('=== INICIANDO SINCRONIZAÇÃO INICIAL ===');
const syncResult: SyncResult = {
success: true,
totalRecords: 0,
syncedRecords: 0,
errors: [],
duration: 0
};
const startTime = Date.now();
// 1. Verificar conectividade
const isOnline = await this.checkConnectivity();
if (!isOnline) {
throw new Error('Sem conexão com a internet');
}
// 2. Sincronizar dados de usuário
await this.syncUserData();
syncResult.syncedRecords += 1;
// 3. Sincronizar entregas
const deliveriesResult = await this.syncDeliveries();
syncResult.syncedRecords += deliveriesResult.count;
// 4. Sincronizar configurações
await this.syncSettings();
syncResult.syncedRecords += 1;
// 5. Marcar sincronização como completa
await saveSetting('initial_sync_complete', 'true');
await saveSetting('last_sync_time', Date.now().toString());
syncResult.duration = Date.now() - startTime;
syncResult.totalRecords = syncResult.syncedRecords;
this.isInitialized = true;
console.log('=== SINCRONIZAÇÃO INICIAL CONCLUÍDA ===');
return syncResult;
} catch (error) {
console.error('Erro na sincronização inicial:', error);
throw new Error(`Falha na sincronização inicial: ${error.message}`);
}
}
// Sincronização incremental
async performIncrementalSync(): Promise<SyncResult> {
try {
console.log('=== INICIANDO SINCRONIZAÇÃO INCREMENTAL ===');
const lastSyncTime = await getSetting('last_sync_time');
const syncResult: SyncResult = {
success: true,
totalRecords: 0,
syncedRecords: 0,
errors: [],
duration: 0
};
const startTime = Date.now();
// 1. Verificar conectividade
const isOnline = await this.checkConnectivity();
if (!isOnline) {
throw new Error('Sem conexão com a internet');
}
// 2. Buscar mudanças do servidor
const serverChanges = await this.getServerChangesSince(parseInt(lastSyncTime || '0'));
// 3. Buscar mudanças locais não sincronizadas
const localChanges = await this.getLocalUnsyncedChanges();
// 4. Aplicar mudanças do servidor
await this.applyServerChanges(serverChanges);
syncResult.syncedRecords += serverChanges.length;
// 5. Enviar mudanças locais
await this.sendLocalChanges(localChanges);
syncResult.syncedRecords += localChanges.length;
// 6. Atualizar timestamp de sincronização
await saveSetting('last_sync_time', Date.now().toString());
syncResult.duration = Date.now() - startTime;
syncResult.totalRecords = syncResult.syncedRecords;
console.log('=== SINCRONIZAÇÃO INCREMENTAL CONCLUÍDA ===');
return syncResult;
} catch (error) {
console.error('Erro na sincronização incremental:', error);
throw new Error(`Falha na sincronização incremental: ${error.message}`);
}
}
// Métodos auxiliares
private async checkConnectivity(): Promise<boolean> {
try {
// Implementar verificação de conectividade
const response = await fetch('https://www.google.com', {
method: 'HEAD',
timeout: 5000
});
return response.ok;
} catch {
return false;
}
}
private async syncUserData(): Promise<void> {
// Implementar sincronização de dados do usuário
console.log('Sincronizando dados do usuário...');
}
private async syncDeliveries(): Promise<{ count: number }> {
try {
const deliveries = await api.getDeliveries();
let count = 0;
for (const delivery of deliveries) {
await this.saveDeliveryLocally(delivery);
count += 1;
}
return { count };
} catch (error) {
console.error('Erro ao sincronizar entregas:', error);
throw error;
}
}
private async syncSettings(): Promise<void> {
// Implementar sincronização de configurações
console.log('Sincronizando configurações...');
}
private async saveDeliveryLocally(delivery: any): Promise<void> {
try {
await executeQuery(
`INSERT OR REPLACE INTO deliveries (
id, outId, customerName, street, streetNumber, neighborhood,
city, state, zipCode, customerPhone, lat, lng, deliverySeq,
routing, status, outDate, notes, version, last_modified, sync_timestamp
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
delivery.id, delivery.outId, delivery.customerName,
delivery.street, delivery.streetNumber, delivery.neighborhood,
delivery.city, delivery.state, delivery.zipCode,
delivery.customerPhone, delivery.lat, delivery.lng,
delivery.deliverySeq, delivery.routing, delivery.status,
delivery.outDate, delivery.notes, 1, Date.now(), Date.now()
]
);
} catch (error) {
console.error('Erro ao salvar entrega localmente:', error);
throw error;
}
}
private async getServerChangesSince(timestamp: number): Promise<any[]> {
// Implementar busca de mudanças do servidor
// Por enquanto, retornar array vazio
return [];
}
private async getLocalUnsyncedChanges(): Promise<any[]> {
try {
const result = await executeQuery(
"SELECT * FROM deliveries WHERE sync_timestamp IS NULL OR sync_timestamp < last_modified"
);
return result.rows._array;
} catch (error) {
console.error('Erro ao buscar mudanças locais:', error);
return [];
}
}
private async applyServerChanges(changes: any[]): Promise<void> {
for (const change of changes) {
await this.saveDeliveryLocally(change);
}
}
private async sendLocalChanges(changes: any[]): Promise<void> {
for (const change of changes) {
try {
await api.createDelivery(change);
// Marcar como sincronizado
await executeQuery(
"UPDATE deliveries SET sync_timestamp = ? WHERE id = ?",
[Date.now(), change.id]
);
} catch (error) {
console.error('Erro ao enviar mudança local:', error);
}
}
}
// Verificar se sincronização inicial foi realizada
async isInitialSyncComplete(): Promise<boolean> {
try {
const result = await getSetting('initial_sync_complete');
return result === 'true';
} catch (error) {
console.error('Erro ao verificar sincronização inicial:', error);
return false;
}
}
// Obter estatísticas de sincronização
async getSyncStats(): Promise<any> {
try {
const totalResult = await executeQuery("SELECT COUNT(*) as count FROM deliveries");
const syncedResult = await executeQuery(
"SELECT COUNT(*) as count FROM deliveries WHERE sync_timestamp IS NOT NULL"
);
const pendingResult = await executeQuery(
"SELECT COUNT(*) as count FROM deliveries WHERE sync_timestamp IS NULL"
);
return {
totalRecords: totalResult.rows._array[0].count,
syncedRecords: syncedResult.rows._array[0].count,
pendingRecords: pendingResult.rows._array[0].count,
lastSyncTime: await getSetting('last_sync_time')
};
} catch (error) {
console.error('Erro ao obter estatísticas de sincronização:', error);
return null;
}
}
}
export const syncService = new SyncService();
```
### 2.2 Contexto de Sincronização Inicial
**Arquivo**: `src/contexts/InitialSyncContext.tsx`
```typescript
import React, { createContext, useContext, useState, useEffect } from 'react';
import { syncService, SyncResult } from '../services/syncService';
interface InitialSyncContextData {
// Estados
isInitialSyncComplete: boolean;
syncProgress: number;
syncStatus: string;
lastSyncTime: number | null;
pendingChanges: number;
isLoading: boolean;
error: string | null;
// Métodos
startInitialSync: () => Promise<void>;
retryInitialSync: () => Promise<void>;
performIncrementalSync: () => Promise<void>;
getSyncStats: () => Promise<any>;
}
const InitialSyncContext = createContext<InitialSyncContextData>({} as InitialSyncContextData);
export const useInitialSync = () => {
const context = useContext(InitialSyncContext);
if (!context) {
throw new Error('useInitialSync deve ser usado dentro de um InitialSyncProvider');
}
return context;
};
export const InitialSyncProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isInitialSyncComplete, setIsInitialSyncComplete] = useState(false);
const [syncProgress, setSyncProgress] = useState(0);
const [syncStatus, setSyncStatus] = useState('idle');
const [lastSyncTime, setLastSyncTime] = useState<number | null>(null);
const [pendingChanges, setPendingChanges] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Verificar status inicial
useEffect(() => {
checkInitialSyncStatus();
}, []);
const checkInitialSyncStatus = async () => {
try {
const isComplete = await syncService.isInitialSyncComplete();
setIsInitialSyncComplete(isComplete);
if (isComplete) {
const stats = await syncService.getSyncStats();
if (stats) {
setLastSyncTime(parseInt(stats.lastSyncTime || '0'));
setPendingChanges(stats.pendingRecords);
}
}
} catch (error) {
console.error('Erro ao verificar status de sincronização:', error);
}
};
const startInitialSync = async () => {
setIsLoading(true);
setError(null);
setSyncStatus('starting');
setSyncProgress(0);
try {
// Simular progresso
const progressInterval = setInterval(() => {
setSyncProgress(prev => {
if (prev >= 90) {
clearInterval(progressInterval);
return prev;
}
return prev + 10;
});
}, 200);
const result = await syncService.performInitialSync();
clearInterval(progressInterval);
setSyncProgress(100);
setSyncStatus('completed');
setIsInitialSyncComplete(true);
setLastSyncTime(Date.now());
setPendingChanges(0);
console.log('Sincronização inicial concluída:', result);
} catch (error) {
console.error('Erro na sincronização inicial:', error);
setError(error.message);
setSyncStatus('error');
} finally {
setIsLoading(false);
}
};
const retryInitialSync = async () => {
setError(null);
await startInitialSync();
};
const performIncrementalSync = async () => {
setIsLoading(true);
setError(null);
setSyncStatus('syncing');
try {
const result = await syncService.performIncrementalSync();
setSyncStatus('completed');
setLastSyncTime(Date.now());
setPendingChanges(0);
console.log('Sincronização incremental concluída:', result);
} catch (error) {
console.error('Erro na sincronização incremental:', error);
setError(error.message);
setSyncStatus('error');
} finally {
setIsLoading(false);
}
};
const getSyncStats = async () => {
try {
return await syncService.getSyncStats();
} catch (error) {
console.error('Erro ao obter estatísticas:', error);
return null;
}
};
return (
<InitialSyncContext.Provider
value={{
isInitialSyncComplete,
syncProgress,
syncStatus,
lastSyncTime,
pendingChanges,
isLoading,
error,
startInitialSync,
retryInitialSync,
performIncrementalSync,
getSyncStats,
}}
>
{children}
</InitialSyncContext.Provider>
);
};
```
## Fase 3: Implementação das Telas
### 3.1 Tela de Sincronização Inicial
**Arquivo**: `src/screens/sync/InitialSyncScreen.tsx`
```typescript
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Alert,
ActivityIndicator,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons } from '@expo/vector-icons';
import { useInitialSync } from '../../contexts/InitialSyncContext';
import { COLORS, SHADOWS } from '../../constants/theme';
const InitialSyncScreen: React.FC = () => {
const {
syncProgress,
syncStatus,
isLoading,
error,
startInitialSync,
retryInitialSync,
} = useInitialSync();
const [hasStarted, setHasStarted] = useState(false);
const handleStartSync = async () => {
setHasStarted(true);
try {
await startInitialSync();
// Navegação será feita pelo contexto pai
} catch (error) {
Alert.alert('Erro', 'Falha na sincronização inicial');
}
};
const handleRetry = async () => {
try {
await retryInitialSync();
} catch (error) {
Alert.alert('Erro', 'Falha ao tentar novamente');
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'starting': return 'Iniciando sincronização...';
case 'syncing': return 'Sincronizando dados...';
case 'completed': return 'Sincronização concluída!';
case 'error': return 'Erro na sincronização';
default: return 'Pronto para sincronizar';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return COLORS.success;
case 'error': return COLORS.danger;
default: return COLORS.primary;
}
};
return (
<SafeAreaView style={styles.container}>
<LinearGradient
colors={[COLORS.primary, '#3B82F6']}
style={styles.header}
>
<View style={styles.headerContent}>
<Ionicons name="cloud-download" size={48} color="white" />
<Text style={styles.title}>Sincronização Inicial</Text>
<Text style={styles.subtitle}>
Baixando dados necessários para funcionamento offline
</Text>
</View>
</LinearGradient>
<View style={styles.content}>
<View style={styles.progressContainer}>
<View style={styles.progressBar}>
<View
style={[
styles.progressFill,
{ width: `${syncProgress}%` }
]}
/>
</View>
<Text style={styles.progressText}>
{Math.round(syncProgress)}% concluído
</Text>
</View>
<View style={styles.statusContainer}>
<Text style={[styles.statusText, { color: getStatusColor(syncStatus) }]}>
{getStatusText(syncStatus)}
</Text>
</View>
{error && (
<View style={styles.errorContainer}>
<Ionicons name="alert-circle" size={24} color={COLORS.danger} />
<Text style={styles.errorText}>{error}</Text>
</View>
)}
<View style={styles.buttonContainer}>
{!hasStarted || syncStatus === 'error' ? (
<TouchableOpacity
style={[styles.syncButton, isLoading && styles.syncButtonDisabled]}
onPress={syncStatus === 'error' ? handleRetry : handleStartSync}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="white" />
) : (
<Ionicons
name={syncStatus === 'error' ? 'refresh' : 'cloud-download'}
size={24}
color="white"
/>
)}
<Text style={styles.syncButtonText}>
{syncStatus === 'error' ? 'Tentar Novamente' : 'Iniciar Sincronização'}
</Text>
</TouchableOpacity>
) : (
<View style={styles.completedContainer}>
<Ionicons name="checkmark-circle" size={48} color={COLORS.success} />
<Text style={styles.completedText}>
Sincronização concluída com sucesso!
</Text>
</View>
)}
</View>
<View style={styles.infoContainer}>
<Text style={styles.infoText}>
• Dados de entregas serão baixados
</Text>
<Text style={styles.infoText}>
• Configurações serão sincronizadas
</Text>
<Text style={styles.infoText}>
• Aplicativo funcionará offline após sincronização
</Text>
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
header: {
paddingVertical: 40,
paddingHorizontal: 20,
alignItems: 'center',
},
headerContent: {
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: 'white',
marginTop: 16,
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: 'rgba(255, 255, 255, 0.8)',
textAlign: 'center',
},
content: {
flex: 1,
padding: 20,
},
progressContainer: {
marginBottom: 30,
},
progressBar: {
height: 8,
backgroundColor: COLORS.border,
borderRadius: 4,
overflow: 'hidden',
marginBottom: 8,
},
progressFill: {
height: '100%',
backgroundColor: COLORS.primary,
borderRadius: 4,
},
progressText: {
fontSize: 16,
fontWeight: '600',
color: COLORS.text,
textAlign: 'center',
},
statusContainer: {
alignItems: 'center',
marginBottom: 20,
},
statusText: {
fontSize: 18,
fontWeight: '600',
},
errorContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FEF2F2',
padding: 16,
borderRadius: 12,
marginBottom: 20,
},
errorText: {
fontSize: 14,
color: COLORS.danger,
marginLeft: 8,
flex: 1,
},
buttonContainer: {
marginBottom: 30,
},
syncButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: COLORS.primary,
paddingVertical: 16,
paddingHorizontal: 24,
borderRadius: 12,
...SHADOWS.medium,
},
syncButtonDisabled: {
backgroundColor: COLORS.textLight,
},
syncButtonText: {
fontSize: 16,
fontWeight: '600',
color: 'white',
marginLeft: 8,
},
completedContainer: {
alignItems: 'center',
padding: 20,
},
completedText: {
fontSize: 18,
fontWeight: '600',
color: COLORS.success,
marginTop: 16,
textAlign: 'center',
},
infoContainer: {
backgroundColor: COLORS.secondary,
padding: 20,
borderRadius: 12,
},
infoText: {
fontSize: 14,
color: COLORS.textLight,
marginBottom: 8,
},
});
export default InitialSyncScreen;
```
### 3.2 Atualização da Navegação
**Arquivo**: `src/navigation/index.tsx`
```typescript
// Adicionar import
import InitialSyncScreen from '../screens/sync/InitialSyncScreen';
// Adicionar ao AuthNavigator
const AuthNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="InitialSync" component={InitialSyncScreen} />
</Stack.Navigator>
);
};
// Atualizar Navigation component
const Navigation = () => {
const { user, isLoading } = useAuth();
const { isInitialSyncComplete } = useInitialSync();
useEffect(() => {
console.log('=== DEBUG: NAVIGATION STATE ===');
console.log('isLoading:', isLoading);
console.log('user:', user ? 'Logado' : 'Não logado');
console.log('isInitialSyncComplete:', isInitialSyncComplete);
if (!isLoading && !user) {
console.log('Redirecionando para Auth...');
navigationRef.current?.reset({
index: 0,
routes: [{ name: 'Auth' }]
});
} else if (!isLoading && user && !isInitialSyncComplete) {
console.log('Redirecionando para InitialSync...');
navigationRef.current?.reset({
index: 0,
routes: [{ name: 'InitialSync' }]
});
}
}, [user, isLoading, isInitialSyncComplete]);
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "#FFFFFF" }}>
<Text>Carregando...</Text>
</View>
);
}
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{user ? (
<>
{!isInitialSyncComplete ? (
<Stack.Screen name="InitialSync" component={InitialSyncScreen} />
) : (
<>
<Stack.Screen
name="Routing"
component={RoutingScreen}
options={{
headerShown: false,
presentation: 'modal'
}}
/>
<Stack.Screen name="Main" component={TabNavigator} />
</>
)}
</>
) : (
<Stack.Screen name="Auth" component={AuthNavigator} />
)}
</Stack.Navigator>
);
};
```
### 3.3 Atualização do App.tsx
**Arquivo**: `App.tsx`
```typescript
// Adicionar import
import { InitialSyncProvider } from "./src/contexts/InitialSyncContext";
// Atualizar estrutura de providers
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider onLayout={onLayoutRootView}>
<AuthProvider>
<InitialSyncProvider>
<SyncProvider>
<DeliveriesProvider>
<OfflineProvider>
<NavigationContainer ref={navigationRef}>
{Platform.OS === 'android' ? (
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
<Navigation />
</SafeAreaView>
) : (
<Navigation />
)}
<StatusBar style="light" backgroundColor={COLORS.primary} />
</NavigationContainer>
</OfflineProvider>
</DeliveriesProvider>
</SyncProvider>
</InitialSyncProvider>
</AuthProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
```
## Fase 4: Implementação de Sincronização Incremental
### 4.1 Hook para Sincronização Automática
**Arquivo**: `src/hooks/useAutoSync.ts`
```typescript
import { useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';
import { useInitialSync } from '../contexts/InitialSyncContext';
import { useSync } from '../contexts/SyncContext';
export const useAutoSync = () => {
const { isInitialSyncComplete, performIncrementalSync } = useInitialSync();
const { isOnline } = useSync();
const appState = useRef(AppState.currentState);
const syncTimeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
const handleAppStateChange = (nextAppState: AppStateStatus) => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === 'active' &&
isInitialSyncComplete &&
isOnline
) {
// App voltou ao foreground e está online
console.log('App ativo - verificando sincronização');
scheduleSync();
}
appState.current = nextAppState;
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => {
subscription?.remove();
if (syncTimeoutRef.current) {
clearTimeout(syncTimeoutRef.current);
}
};
}, [isInitialSyncComplete, isOnline]);
const scheduleSync = () => {
// Aguardar 5 segundos antes de sincronizar
syncTimeoutRef.current = setTimeout(async () => {
try {
console.log('Executando sincronização automática');
await performIncrementalSync();
} catch (error) {
console.error('Erro na sincronização automática:', error);
}
}, 5000);
};
return {
scheduleSync,
};
};
```
### 4.2 Atualização do HomeScreen
**Arquivo**: `src/screens/main/HomeScreen.tsx`
```typescript
// Adicionar imports
import { useInitialSync } from '../../contexts/InitialSyncContext';
import { useAutoSync } from '../../hooks/useAutoSync';
// Adicionar ao componente
const HomeScreen = () => {
// ... código existente ...
const {
isInitialSyncComplete,
pendingChanges,
performIncrementalSync,
getSyncStats
} = useInitialSync();
const { scheduleSync } = useAutoSync();
// Usar auto sync
useEffect(() => {
if (isInitialSyncComplete) {
scheduleSync();
}
}, [isInitialSyncComplete, scheduleSync]);
// ... resto do código ...
};
```
## Fase 5: Testes e Validação
### 5.1 Script de Teste
**Arquivo**: `scripts/test-sync.js`
```javascript
const { execSync } = require('child_process');
console.log('=== TESTANDO SINCRONIZAÇÃO OFFLINE ===');
// Teste 1: Verificar se banco de dados foi criado
console.log('1. Verificando estrutura do banco...');
try {
execSync('npx react-native run-android --variant=debug', { stdio: 'inherit' });
console.log('✅ Banco de dados criado com sucesso');
} catch (error) {
console.error('❌ Erro ao criar banco de dados:', error.message);
}
// Teste 2: Verificar sincronização inicial
console.log('2. Testando sincronização inicial...');
// Implementar testes específicos
// Teste 3: Verificar modo offline
console.log('3. Testando modo offline...');
// Implementar testes específicos
console.log('=== TESTES CONCLUÍDOS ===');
```
### 5.2 Checklist de Implementação
```markdown
## Checklist de Implementação
### Fase 1: Preparação
- [ ] Instalar dependências necessárias
- [ ] Configurar variáveis de ambiente
- [ ] Atualizar estrutura do banco de dados
- [ ] Criar tabelas de sincronização
### Fase 2: Serviços Base
- [ ] Implementar SyncService
- [ ] Criar InitialSyncContext
- [ ] Implementar métodos de sincronização
- [ ] Adicionar tratamento de erros
### Fase 3: Interface
- [ ] Criar InitialSyncScreen
- [ ] Atualizar navegação
- [ ] Integrar com App.tsx
- [ ] Testar fluxo de navegação
### Fase 4: Sincronização Automática
- [ ] Implementar useAutoSync hook
- [ ] Adicionar sincronização incremental
- [ ] Atualizar HomeScreen
- [ ] Testar sincronização automática
### Fase 5: Testes
- [ ] Testar sincronização inicial
- [ ] Testar modo offline
- [ ] Testar sincronização incremental
- [ ] Validar performance
- [ ] Testar tratamento de erros
### Fase 6: Otimizações
- [ ] Implementar compressão de dados
- [ ] Adicionar cache inteligente
- [ ] Otimizar queries do banco
- [ ] Implementar logs de debug
```
## Fase 6: Deploy e Monitoramento
### 6.1 Configuração de Build
**Arquivo**: `eas.json`
```json
{
"cli": {
"version": ">= 5.9.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"env": {
"API_BASE_URL": "https://api-staging.example.com"
}
},
"production": {
"env": {
"API_BASE_URL": "https://api.example.com"
}
}
},
"submit": {
"production": {}
}
}
```
### 6.2 Monitoramento de Sincronização
**Arquivo**: `src/services/syncMonitor.ts`
```typescript
class SyncMonitor {
private metrics: Map<string, any> = new Map();
recordSyncAttempt(type: string, success: boolean, duration: number) {
const key = `${type}_${new Date().toISOString().split('T')[0]}`;
const current = this.metrics.get(key) || {
attempts: 0,
successes: 0,
failures: 0,
totalDuration: 0,
};
current.attempts += 1;
current.totalDuration += duration;
if (success) {
current.successes += 1;
} else {
current.failures += 1;
}
this.metrics.set(key, current);
}
getMetrics(): any {
return Object.fromEntries(this.metrics);
}
exportMetrics(): string {
return JSON.stringify(this.getMetrics(), null, 2);
}
}
export const syncMonitor = new SyncMonitor();
```
## Conclusão
Este guia fornece uma implementação completa e passo a passo da sincronização offline no aplicativo de entregas. A implementação é modular e pode ser feita incrementalmente, permitindo testes e validação em cada fase.
**Próximos Passos**:
1. Implementar Fase 1 (Preparação)
2. Testar estrutura do banco de dados
3. Implementar Fase 2 (Serviços Base)
4. Testar sincronização inicial
5. Continuar com as demais fases
**Considerações Importantes**:
- Sempre testar em ambiente de desenvolvimento primeiro
- Implementar logs detalhados para debugging
- Considerar performance e uso de bateria
- Implementar fallbacks para cenários de erro
- Monitorar uso de dados e armazenamento