# Estratégia de Sincronização Offline - Implementação Completa ## Visão Geral Este documento detalha a estratégia completa para implementar sincronização offline no aplicativo de entregas, permitindo que o aplicativo funcione sem dependência de internet após uma sincronização inicial completa. ## Arquitetura de Sincronização ### 1. Fluxo de Sincronização ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Aplicativo │ │ Servidor │ │ Banco Local │ │ │ │ │ │ (SQLite) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ 1. Login │ │ ├──────────────────────►│ │ │◄──────────────────────┤ │ │ │ │ │ 2. Sync Inicial │ │ ├──────────────────────►│ │ │◄──────────────────────┤ │ │ │ │ │ 3. Salvar Local │ │ ├─────────────────────────────────────────────►│ │ │ │ │ 4. Modo Offline │ │ │ (Usar dados locais)│ │ ├─────────────────────────────────────────────►│ │ │ │ │ 5. Sync Incremental │ │ │ (Quando online) │ │ ├──────────────────────►│ │ │◄──────────────────────┤ │ │ │ │ │ 6. Atualizar Local │ │ ├─────────────────────────────────────────────►│ ``` ### 2. Estados de Sincronização ```typescript enum SyncStatus { SYNCED = 'synced', // Sincronizado com servidor PENDING = 'pending', // Aguardando sincronização CONFLICT = 'conflict', // Conflito detectado ERROR = 'error', // Erro na sincronização OFFLINE = 'offline' // Modo offline ativo } enum SyncType { FULL = 'full', // Sincronização completa INCREMENTAL = 'incremental', // Sincronização incremental SELECTIVE = 'selective' // Sincronização seletiva } ``` ## Implementação do Sistema de Sincronização ### 1. Contexto de Sincronização Expandido **Arquivo**: `src/contexts/InitialSyncContext.tsx` ```typescript interface InitialSyncContextData { // Estados isInitialSyncComplete: boolean; syncProgress: number; syncStatus: SyncStatus; lastSyncTime: number | null; pendingChanges: number; // Métodos startInitialSync: () => Promise; retryInitialSync: () => Promise; performIncrementalSync: () => Promise; performSelectiveSync: (ids: string[]) => Promise; resolveConflict: (conflictId: string, resolution: ConflictResolution) => Promise; // Utilitários getSyncStats: () => Promise; clearSyncData: () => Promise; } interface SyncStats { totalRecords: number; syncedRecords: number; pendingRecords: number; conflictedRecords: number; errorRecords: number; lastSyncDuration: number; averageSyncTime: number; } interface ConflictResolution { type: 'server_wins' | 'client_wins' | 'merge'; data?: any; } ``` ### 2. Serviço de Sincronização Principal **Arquivo**: `src/services/syncService.ts` ```typescript class SyncService { private api: ApiService; private database: DatabaseService; private offlineStorage: OfflineStorageService; private syncQueue: SyncQueue; private conflictResolver: ConflictResolver; constructor() { this.api = new ApiService(); this.database = new DatabaseService(); this.offlineStorage = new OfflineStorageService(); this.syncQueue = new SyncQueue(); this.conflictResolver = new ConflictResolver(); } // Sincronização inicial completa async performInitialSync(): Promise { try { console.log('=== INICIANDO SINCRONIZAÇÃO INICIAL ==='); const syncResult: SyncResult = { success: true, totalRecords: 0, syncedRecords: 0, errors: [], duration: 0 }; const startTime = Date.now(); // 1. Sincronizar dados de usuário await this.syncUserData(); syncResult.syncedRecords += 1; // 2. Sincronizar entregas const deliveriesResult = await this.syncDeliveries(); syncResult.syncedRecords += deliveriesResult.count; // 3. Sincronizar configurações await this.syncSettings(); syncResult.syncedRecords += 1; // 4. Sincronizar dados de referência await this.syncReferenceData(); syncResult.syncedRecords += 5; // Aproximadamente // 5. Marcar sincronização como completa await this.database.saveSetting('initial_sync_complete', 'true'); await this.database.saveSetting('last_sync_time', Date.now().toString()); syncResult.duration = Date.now() - startTime; syncResult.totalRecords = syncResult.syncedRecords; console.log('=== SINCRONIZAÇÃO INICIAL CONCLUÍDA ==='); console.log(`Total de registros: ${syncResult.totalRecords}`); console.log(`Duração: ${syncResult.duration}ms`); return syncResult; } catch (error) { console.error('Erro na sincronização inicial:', error); throw new SyncError('Falha na sincronização inicial', error); } } // Sincronização incremental async performIncrementalSync(): Promise { try { console.log('=== INICIANDO SINCRONIZAÇÃO INCREMENTAL ==='); const lastSyncTime = await this.database.getSetting('last_sync_time'); const syncResult: SyncResult = { success: true, totalRecords: 0, syncedRecords: 0, errors: [], duration: 0 }; const startTime = Date.now(); // 1. Buscar mudanças do servidor desde última sincronização const serverChanges = await this.api.getChangesSince(lastSyncTime); // 2. Buscar mudanças locais não sincronizadas const localChanges = await this.database.getUnsyncedRecords(); // 3. Resolver conflitos const conflicts = await this.detectConflicts(serverChanges, localChanges); await this.resolveConflicts(conflicts); // 4. Aplicar mudanças do servidor await this.applyServerChanges(serverChanges); // 5. Enviar mudanças locais await this.sendLocalChanges(localChanges); // 6. Atualizar timestamp de sincronização await this.database.saveSetting('last_sync_time', Date.now().toString()); syncResult.duration = Date.now() - startTime; console.log('=== SINCRONIZAÇÃO INCREMENTAL CONCLUÍDA ==='); return syncResult; } catch (error) { console.error('Erro na sincronização incremental:', error); throw new SyncError('Falha na sincronização incremental', error); } } // Sincronização seletiva async performSelectiveSync(recordIds: string[]): Promise { try { console.log(`=== INICIANDO SINCRONIZAÇÃO SELETIVA (${recordIds.length} registros) ===`); const syncResult: SyncResult = { success: true, totalRecords: recordIds.length, syncedRecords: 0, errors: [], duration: 0 }; const startTime = Date.now(); for (const recordId of recordIds) { try { await this.syncSingleRecord(recordId); syncResult.syncedRecords += 1; } catch (error) { syncResult.errors.push({ recordId, error: error.message }); } } syncResult.duration = Date.now() - startTime; console.log('=== SINCRONIZAÇÃO SELETIVA CONCLUÍDA ==='); return syncResult; } catch (error) { console.error('Erro na sincronização seletiva:', error); throw new SyncError('Falha na sincronização seletiva', error); } } // Métodos auxiliares private async syncUserData(): Promise { const userData = await this.api.getCurrentUser(); await this.database.saveUser(userData); } private async syncDeliveries(): Promise<{ count: number }> { const deliveries = await this.api.getDeliveries(); let count = 0; for (const delivery of deliveries) { await this.database.saveDelivery(delivery); count += 1; } return { count }; } private async syncSettings(): Promise { const settings = await this.api.getSettings(); for (const [key, value] of Object.entries(settings)) { await this.database.saveSetting(key, value); } } private async syncReferenceData(): Promise { // Sincronizar dados de referência como: // - Lista de produtos // - Configurações de entrega // - Dados de clientes // - Configurações de rota } } ``` ### 3. Fila de Sincronização **Arquivo**: `src/services/syncQueue.ts` ```typescript interface SyncQueueItem { id: string; type: 'create' | 'update' | 'delete'; table: string; recordId: string; data: any; timestamp: number; retryCount: number; maxRetries: number; priority: 'high' | 'normal' | 'low'; } class SyncQueue { private queue: SyncQueueItem[] = []; private processing: boolean = false; // Adicionar item à fila async addItem(item: Omit): Promise { const queueItem: SyncQueueItem = { ...item, id: `sync_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now(), retryCount: 0, maxRetries: item.maxRetries || 3 }; this.queue.push(queueItem); this.queue.sort((a, b) => this.getPriorityValue(b.priority) - this.getPriorityValue(a.priority)); console.log(`Item adicionado à fila: ${queueItem.id}`); return queueItem.id; } // Processar fila async processQueue(): Promise { if (this.processing || this.queue.length === 0) { return; } this.processing = true; console.log(`Processando fila com ${this.queue.length} itens`); while (this.queue.length > 0) { const item = this.queue.shift(); if (!item) break; try { await this.processItem(item); console.log(`Item processado com sucesso: ${item.id}`); } catch (error) { console.error(`Erro ao processar item ${item.id}:`, error); item.retryCount += 1; if (item.retryCount < item.maxRetries) { // Reagendar item this.queue.push(item); console.log(`Item reagendado: ${item.id} (tentativa ${item.retryCount})`); } else { console.error(`Item falhou após ${item.maxRetries} tentativas: ${item.id}`); } } } this.processing = false; console.log('Processamento da fila concluído'); } private async processItem(item: SyncQueueItem): Promise { switch (item.type) { case 'create': await this.api.createRecord(item.table, item.data); break; case 'update': await this.api.updateRecord(item.table, item.recordId, item.data); break; case 'delete': await this.api.deleteRecord(item.table, item.recordId); break; } // Marcar como sincronizado no banco local await this.database.markAsSynced(item.table, item.recordId); } private getPriorityValue(priority: string): number { switch (priority) { case 'high': return 3; case 'normal': return 2; case 'low': return 1; default: return 2; } } } ``` ### 4. Resolução de Conflitos **Arquivo**: `src/services/conflictResolver.ts` ```typescript interface Conflict { id: string; table: string; recordId: string; localData: any; serverData: any; conflictFields: string[]; timestamp: number; } class ConflictResolver { // Detectar conflitos async detectConflicts(serverChanges: any[], localChanges: any[]): Promise { const conflicts: Conflict[] = []; for (const serverChange of serverChanges) { const localChange = localChanges.find(lc => lc.id === serverChange.id); if (localChange) { const conflictFields = this.findConflictFields(serverChange, localChange); if (conflictFields.length > 0) { conflicts.push({ id: `conflict_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, table: serverChange.table, recordId: serverChange.id, localData: localChange, serverData: serverChange, conflictFields, timestamp: Date.now() }); } } } return conflicts; } // Resolver conflito async resolveConflict(conflict: Conflict, resolution: ConflictResolution): Promise { let resolvedData: any; switch (resolution.type) { case 'server_wins': resolvedData = conflict.serverData; break; case 'client_wins': resolvedData = conflict.localData; break; case 'merge': resolvedData = this.mergeData(conflict.localData, conflict.serverData, resolution.data); break; } // Atualizar banco local com dados resolvidos await this.database.updateRecord(conflict.table, conflict.recordId, resolvedData); // Marcar conflito como resolvido await this.database.markConflictResolved(conflict.id); } private findConflictFields(serverData: any, localData: any): string[] { const conflictFields: string[] = []; const fieldsToCheck = ['status', 'notes', 'photos', 'signature', 'completed_time']; for (const field of fieldsToCheck) { if (serverData[field] !== localData[field]) { conflictFields.push(field); } } return conflictFields; } private mergeData(localData: any, serverData: any, mergeRules?: any): any { const merged = { ...localData }; // Aplicar regras de merge específicas if (mergeRules) { for (const [field, rule] of Object.entries(mergeRules)) { switch (rule) { case 'latest': merged[field] = serverData[field]; break; case 'append': if (Array.isArray(merged[field]) && Array.isArray(serverData[field])) { merged[field] = [...merged[field], ...serverData[field]]; } break; case 'combine': merged[field] = `${merged[field]} ${serverData[field]}`.trim(); break; } } } return merged; } } ``` ## Estrutura de Banco de Dados para Sincronização ### 1. Tabelas Adicionais ```sql -- Tabela de controle de sincronização 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')) ); -- Tabela de conflitos 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')) ); -- Tabela de log de sincronização 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 ALTER TABLE deliveries ADD COLUMN version INTEGER DEFAULT 1; ALTER TABLE deliveries ADD COLUMN last_modified INTEGER DEFAULT (strftime('%s', 'now')); ALTER TABLE deliveries ADD COLUMN sync_timestamp INTEGER; ALTER TABLE deliveries ADD COLUMN conflict_resolution TEXT; ``` ### 2. Índices para Performance ```sql -- Índices para sincronização CREATE INDEX IF NOT EXISTS idx_deliveries_sync_timestamp ON deliveries(sync_timestamp); CREATE INDEX IF NOT EXISTS idx_deliveries_last_modified ON deliveries(last_modified); CREATE INDEX IF NOT EXISTS idx_deliveries_version ON deliveries(version); CREATE INDEX IF NOT EXISTS idx_sync_log_timestamp ON sync_log(timestamp); CREATE INDEX IF NOT EXISTS idx_sync_conflicts_resolved ON sync_conflicts(resolved_at); ``` ## Implementação de Endpoints para Sincronização ### 1. Endpoints do Servidor ```typescript // Endpoints necessários no servidor interface SyncEndpoints { // Sincronização inicial 'GET /v1/sync/initial': () => Promise; // Sincronização incremental 'GET /v1/sync/changes': (since: number) => Promise; 'POST /v1/sync/changes': (changes: ChangeSet) => Promise; // Sincronização seletiva 'POST /v1/sync/selective': (ids: string[]) => Promise; // Resolução de conflitos 'POST /v1/sync/conflicts/resolve': (conflicts: ConflictResolution[]) => Promise; // Status de sincronização 'GET /v1/sync/status': () => Promise; } ``` ### 2. Implementação no Cliente ```typescript // src/services/api.ts - Métodos adicionais class ApiService { // Obter dados para sincronização inicial async getInitialSyncData(): Promise { const response = await this.request('/v1/sync/initial'); return response.data; } // Obter mudanças desde timestamp async getChangesSince(timestamp: number): Promise { const response = await this.request(`/v1/sync/changes?since=${timestamp}`); return response.data; } // Enviar mudanças locais async sendLocalChanges(changes: ChangeSet): Promise { const response = await this.request('/v1/sync/changes', { method: 'POST', body: JSON.stringify(changes) }); return response.data; } // Sincronização seletiva async performSelectiveSync(ids: string[]): Promise { const response = await this.request('/v1/sync/selective', { method: 'POST', body: JSON.stringify({ ids }) }); return response.data; } // Resolver conflitos async resolveConflicts(resolutions: ConflictResolution[]): Promise { await this.request('/v1/sync/conflicts/resolve', { method: 'POST', body: JSON.stringify({ resolutions }) }); } // Obter status de sincronização async getSyncStatus(): Promise { const response = await this.request('/v1/sync/status'); return response.data; } } ``` ## Interface de Usuário para Sincronização ### 1. Tela de Sincronização Inicial ```typescript // src/screens/sync/InitialSyncScreen.tsx const InitialSyncScreen: React.FC = () => { const { startInitialSync, syncProgress, syncStatus } = useInitialSync(); const [isLoading, setIsLoading] = useState(false); const handleStartSync = async () => { setIsLoading(true); try { await startInitialSync(); // Navegar para tela principal após sincronização navigation.replace('Main'); } catch (error) { Alert.alert('Erro', 'Falha na sincronização inicial'); } finally { setIsLoading(false); } }; return ( Sincronização Inicial Baixando dados necessários para funcionamento offline {Math.round(syncProgress * 100)}% concluído Status: {getStatusText(syncStatus)} {isLoading ? 'Sincronizando...' : 'Iniciar Sincronização'} ); }; ``` ### 2. Componente de Status de Sincronização ```typescript // src/components/SyncStatusIndicator.tsx const SyncStatusIndicator: React.FC = () => { const { syncStatus, lastSyncTime, pendingChanges } = useSync(); const [showDetails, setShowDetails] = useState(false); const getStatusColor = (status: SyncStatus) => { switch (status) { case SyncStatus.SYNCED: return COLORS.success; case SyncStatus.PENDING: return COLORS.warning; case SyncStatus.CONFLICT: return COLORS.danger; case SyncStatus.ERROR: return COLORS.danger; case SyncStatus.OFFLINE: return COLORS.textLight; default: return COLORS.textLight; } }; const getStatusIcon = (status: SyncStatus) => { switch (status) { case SyncStatus.SYNCED: return 'checkmark-circle'; case SyncStatus.PENDING: return 'time'; case SyncStatus.CONFLICT: return 'warning'; case SyncStatus.ERROR: return 'close-circle'; case SyncStatus.OFFLINE: return 'cloud-offline'; default: return 'help-circle'; } }; return ( setShowDetails(!showDetails)} > {getStatusText(syncStatus)} {pendingChanges > 0 && ( {pendingChanges} )} {showDetails && ( Última sincronização: {formatDate(lastSyncTime)} Mudanças pendentes: {pendingChanges} )} ); }; ``` ### 3. Tela de Resolução de Conflitos ```typescript // src/screens/sync/ConflictResolutionScreen.tsx const ConflictResolutionScreen: React.FC = () => { const { conflicts, resolveConflict } = useSync(); const [selectedConflict, setSelectedConflict] = useState(null); const handleResolveConflict = async (resolution: ConflictResolution) => { if (!selectedConflict) return; try { await resolveConflict(selectedConflict.id, resolution); setSelectedConflict(null); Alert.alert('Sucesso', 'Conflito resolvido com sucesso'); } catch (error) { Alert.alert('Erro', 'Falha ao resolver conflito'); } }; return ( Resolução de Conflitos item.id} renderItem={({ item }) => ( setSelectedConflict(item)} > Conflito em {item.table} - {item.recordId} Campos: {item.conflictFields.join(', ')} )} /> {selectedConflict && ( setSelectedConflict(null)} /> )} ); }; ``` ## Estratégias de Otimização ### 1. Compressão de Dados ```typescript // src/utils/compression.ts import { compress, decompress } from 'lz-string'; export class DataCompression { static compress(data: any): string { const jsonString = JSON.stringify(data); return compress(jsonString); } static decompress(compressedData: string): any { const jsonString = decompress(compressedData); return JSON.parse(jsonString); } static async compressFile(filePath: string): Promise { const fileContent = await FileSystem.readAsStringAsync(filePath); return this.compress(fileContent); } } ``` ### 2. Cache Inteligente ```typescript // src/services/cacheService.ts class CacheService { private cache = new Map(); private maxSize = 100; // MB private currentSize = 0; async set(key: string, data: any, ttl: number = 3600000): Promise { const compressedData = DataCompression.compress(data); const size = compressedData.length; // Verificar se há espaço suficiente if (this.currentSize + size > this.maxSize * 1024 * 1024) { await this.evictOldEntries(); } this.cache.set(key, { data: compressedData, timestamp: Date.now(), ttl, size }); this.currentSize += size; } async get(key: string): Promise { const entry = this.cache.get(key); if (!entry) return null; // Verificar se expirou if (Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); this.currentSize -= entry.size; return null; } return DataCompression.decompress(entry.data); } private async evictOldEntries(): Promise { const entries = Array.from(this.cache.entries()) .sort((a, b) => a[1].timestamp - b[1].timestamp); // Remover 20% das entradas mais antigas const toRemove = Math.floor(entries.length * 0.2); for (let i = 0; i < toRemove; i++) { const [key, entry] = entries[i]; this.cache.delete(key); this.currentSize -= entry.size; } } } ``` ### 3. Sincronização em Background ```typescript // src/services/backgroundSync.ts import * as BackgroundFetch from 'expo-background-fetch'; import * as TaskManager from 'expo-task-manager'; const BACKGROUND_SYNC_TASK = 'background-sync'; TaskManager.defineTask(BACKGROUND_SYNC_TASK, async () => { try { const syncService = new SyncService(); await syncService.performIncrementalSync(); return BackgroundFetch.BackgroundFetchResult.NewData; } catch (error) { console.error('Erro na sincronização em background:', error); return BackgroundFetch.BackgroundFetchResult.Failed; } }); export class BackgroundSyncService { static async registerBackgroundSync(): Promise { try { await BackgroundFetch.registerTaskAsync(BACKGROUND_SYNC_TASK, { minimumInterval: 15 * 60, // 15 minutos stopOnTerminate: false, startOnBoot: true, }); } catch (error) { console.error('Erro ao registrar sincronização em background:', error); } } static async unregisterBackgroundSync(): Promise { try { await BackgroundFetch.unregisterTaskAsync(BACKGROUND_SYNC_TASK); } catch (error) { console.error('Erro ao desregistrar sincronização em background:', error); } } } ``` ## Monitoramento e Logs ### 1. Sistema de Logs ```typescript // src/services/logger.ts interface LogEntry { level: 'debug' | 'info' | 'warn' | 'error'; message: string; timestamp: number; context?: any; userId?: string; } class Logger { private logs: LogEntry[] = []; private maxLogs = 1000; log(level: LogEntry['level'], message: string, context?: any): void { const logEntry: LogEntry = { level, message, timestamp: Date.now(), context, userId: this.getCurrentUserId() }; this.logs.push(logEntry); // Manter apenas os logs mais recentes if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(-this.maxLogs); } // Log no console para desenvolvimento if (__DEV__) { console[level](`[${new Date().toISOString()}] ${message}`, context); } } async exportLogs(): Promise { return JSON.stringify(this.logs, null, 2); } async clearLogs(): Promise { this.logs = []; } private getCurrentUserId(): string | undefined { // Implementar obtenção do ID do usuário atual return undefined; } } export const logger = new Logger(); ``` ### 2. Métricas de Performance ```typescript // src/services/metrics.ts interface SyncMetrics { totalSyncs: number; successfulSyncs: number; failedSyncs: number; averageSyncTime: number; totalDataTransferred: number; conflictsResolved: number; } class MetricsService { private metrics: SyncMetrics = { totalSyncs: 0, successfulSyncs: 0, failedSyncs: 0, averageSyncTime: 0, totalDataTransferred: 0, conflictsResolved: 0 }; recordSync(success: boolean, duration: number, dataSize: number): void { this.metrics.totalSyncs += 1; if (success) { this.metrics.successfulSyncs += 1; } else { this.metrics.failedSyncs += 1; } this.metrics.totalDataTransferred += dataSize; // Calcular tempo médio const totalTime = this.metrics.averageSyncTime * (this.metrics.totalSyncs - 1) + duration; this.metrics.averageSyncTime = totalTime / this.metrics.totalSyncs; } recordConflictResolution(): void { this.metrics.conflictsResolved += 1; } getMetrics(): SyncMetrics { return { ...this.metrics }; } async exportMetrics(): Promise { return JSON.stringify(this.metrics, null, 2); } } export const metrics = new MetricsService(); ``` ## Considerações de Segurança ### 1. Criptografia de Dados Sensíveis ```typescript // src/utils/encryption.ts import CryptoJS from 'crypto-js'; export class DataEncryption { private static readonly SECRET_KEY = 'your-secret-key'; // Em produção, usar variável de ambiente static encrypt(data: string): string { return CryptoJS.AES.encrypt(data, this.SECRET_KEY).toString(); } static decrypt(encryptedData: string): string { const bytes = CryptoJS.AES.decrypt(encryptedData, this.SECRET_KEY); return bytes.toString(CryptoJS.enc.Utf8); } static encryptObject(obj: any): string { return this.encrypt(JSON.stringify(obj)); } static decryptObject(encryptedData: string): T { const decrypted = this.decrypt(encryptedData); return JSON.parse(decrypted); } } ``` ### 2. Validação de Dados ```typescript // src/utils/validation.ts export class DataValidation { static validateDelivery(delivery: any): boolean { const requiredFields = ['id', 'outId', 'customerName', 'status']; for (const field of requiredFields) { if (!delivery[field]) { throw new Error(`Campo obrigatório ausente: ${field}`); } } // Validar status const validStatuses = ['pending', 'in_progress', 'delivered', 'failed']; if (!validStatuses.includes(delivery.status)) { throw new Error(`Status inválido: ${delivery.status}`); } return true; } static sanitizeData(data: any): any { // Remover campos desnecessários const sanitized = { ...data }; delete sanitized.internalId; delete sanitized.tempData; return sanitized; } } ``` ## Conclusão Esta estratégia de sincronização offline fornece uma solução completa e robusta para permitir que o aplicativo funcione sem dependência de internet. A implementação inclui: 1. **Sincronização inicial completa** para carregar todos os dados necessários 2. **Sincronização incremental** para manter dados atualizados 3. **Resolução automática de conflitos** com interface para resolução manual 4. **Fila de sincronização** com retry automático 5. **Monitoramento e logs** para debugging e análise 6. **Otimizações de performance** com compressão e cache 7. **Segurança** com criptografia e validação A implementação pode ser feita de forma incremental, começando com a sincronização inicial e expandindo gradualmente para incluir todas as funcionalidades avançadas.