entregas_app/docs/ESTRATEGIA_SINCRONIZACAO_OF...

33 KiB

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

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

interface InitialSyncContextData {
  // Estados
  isInitialSyncComplete: boolean;
  syncProgress: number;
  syncStatus: SyncStatus;
  lastSyncTime: number | null;
  pendingChanges: number;
  
  // Métodos
  startInitialSync: () => Promise<void>;
  retryInitialSync: () => Promise<void>;
  performIncrementalSync: () => Promise<void>;
  performSelectiveSync: (ids: string[]) => Promise<void>;
  resolveConflict: (conflictId: string, resolution: ConflictResolution) => Promise<void>;
  
  // Utilitários
  getSyncStats: () => Promise<SyncStats>;
  clearSyncData: () => Promise<void>;
}

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

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<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. 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<SyncResult> {
    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<SyncResult> {
    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<void> {
    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<void> {
    const settings = await this.api.getSettings();
    for (const [key, value] of Object.entries(settings)) {
      await this.database.saveSetting(key, value);
    }
  }

  private async syncReferenceData(): Promise<void> {
    // 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

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<SyncQueueItem, 'id' | 'timestamp' | 'retryCount'>): Promise<string> {
    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<void> {
    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<void> {
    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

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<Conflict[]> {
    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<void> {
    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

-- 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

-- Í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

// Endpoints necessários no servidor
interface SyncEndpoints {
  // Sincronização inicial
  'GET /v1/sync/initial': () => Promise<InitialSyncData>;
  
  // Sincronização incremental
  'GET /v1/sync/changes': (since: number) => Promise<ChangeSet>;
  'POST /v1/sync/changes': (changes: ChangeSet) => Promise<SyncResult>;
  
  // Sincronização seletiva
  'POST /v1/sync/selective': (ids: string[]) => Promise<SelectiveSyncData>;
  
  // Resolução de conflitos
  'POST /v1/sync/conflicts/resolve': (conflicts: ConflictResolution[]) => Promise<void>;
  
  // Status de sincronização
  'GET /v1/sync/status': () => Promise<SyncStatus>;
}

2. Implementação no Cliente

// src/services/api.ts - Métodos adicionais
class ApiService {
  // Obter dados para sincronização inicial
  async getInitialSyncData(): Promise<InitialSyncData> {
    const response = await this.request('/v1/sync/initial');
    return response.data;
  }

  // Obter mudanças desde timestamp
  async getChangesSince(timestamp: number): Promise<ChangeSet> {
    const response = await this.request(`/v1/sync/changes?since=${timestamp}`);
    return response.data;
  }

  // Enviar mudanças locais
  async sendLocalChanges(changes: ChangeSet): Promise<SyncResult> {
    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<SelectiveSyncData> {
    const response = await this.request('/v1/sync/selective', {
      method: 'POST',
      body: JSON.stringify({ ids })
    });
    return response.data;
  }

  // Resolver conflitos
  async resolveConflicts(resolutions: ConflictResolution[]): Promise<void> {
    await this.request('/v1/sync/conflicts/resolve', {
      method: 'POST',
      body: JSON.stringify({ resolutions })
    });
  }

  // Obter status de sincronização
  async getSyncStatus(): Promise<SyncStatus> {
    const response = await this.request('/v1/sync/status');
    return response.data;
  }
}

Interface de Usuário para Sincronização

1. Tela de Sincronização Inicial

// 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 (
    <View style={styles.container}>
      <LinearGradient colors={[COLORS.primary, "#3B82F6"]} style={styles.header}>
        <Text style={styles.title}>Sincronização Inicial</Text>
        <Text style={styles.subtitle}>
          Baixando dados necessários para funcionamento offline
        </Text>
      </LinearGradient>

      <View style={styles.content}>
        <View style={styles.progressContainer}>
          <ProgressBar progress={syncProgress} />
          <Text style={styles.progressText}>
            {Math.round(syncProgress * 100)}% concluído
          </Text>
        </View>

        <View style={styles.statusContainer}>
          <Text style={styles.statusText}>
            Status: {getStatusText(syncStatus)}
          </Text>
        </View>

        <TouchableOpacity
          style={[styles.syncButton, isLoading && styles.syncButtonDisabled]}
          onPress={handleStartSync}
          disabled={isLoading}
        >
          <Ionicons name="cloud-download" size={24} color="white" />
          <Text style={styles.syncButtonText}>
            {isLoading ? 'Sincronizando...' : 'Iniciar Sincronização'}
          </Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

2. Componente de Status de Sincronização

// 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 (
    <TouchableOpacity
      style={styles.container}
      onPress={() => setShowDetails(!showDetails)}
    >
      <View style={styles.statusRow}>
        <Ionicons
          name={getStatusIcon(syncStatus)}
          size={16}
          color={getStatusColor(syncStatus)}
        />
        <Text style={[styles.statusText, { color: getStatusColor(syncStatus) }]}>
          {getStatusText(syncStatus)}
        </Text>
        {pendingChanges > 0 && (
          <View style={styles.badge}>
            <Text style={styles.badgeText}>{pendingChanges}</Text>
          </View>
        )}
      </View>

      {showDetails && (
        <View style={styles.detailsContainer}>
          <Text style={styles.detailText}>
            Última sincronização: {formatDate(lastSyncTime)}
          </Text>
          <Text style={styles.detailText}>
            Mudanças pendentes: {pendingChanges}
          </Text>
        </View>
      )}
    </TouchableOpacity>
  );
};

3. Tela de Resolução de Conflitos

// src/screens/sync/ConflictResolutionScreen.tsx
const ConflictResolutionScreen: React.FC = () => {
  const { conflicts, resolveConflict } = useSync();
  const [selectedConflict, setSelectedConflict] = useState<Conflict | null>(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 (
    <View style={styles.container}>
      <Text style={styles.title}>Resolução de Conflitos</Text>
      
      <FlatList
        data={conflicts}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <TouchableOpacity
            style={styles.conflictItem}
            onPress={() => setSelectedConflict(item)}
          >
            <Text style={styles.conflictTitle}>
              Conflito em {item.table} - {item.recordId}
            </Text>
            <Text style={styles.conflictFields}>
              Campos: {item.conflictFields.join(', ')}
            </Text>
          </TouchableOpacity>
        )}
      />

      {selectedConflict && (
        <ConflictResolutionModal
          conflict={selectedConflict}
          onResolve={handleResolveConflict}
          onClose={() => setSelectedConflict(null)}
        />
      )}
    </View>
  );
};

Estratégias de Otimização

1. Compressão de Dados

// 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<string> {
    const fileContent = await FileSystem.readAsStringAsync(filePath);
    return this.compress(fileContent);
  }
}

2. Cache Inteligente

// src/services/cacheService.ts
class CacheService {
  private cache = new Map<string, CacheEntry>();
  private maxSize = 100; // MB
  private currentSize = 0;

  async set(key: string, data: any, ttl: number = 3600000): Promise<void> {
    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<any | null> {
    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<void> {
    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

// 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<void> {
    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<void> {
    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

// 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<string> {
    return JSON.stringify(this.logs, null, 2);
  }

  async clearLogs(): Promise<void> {
    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

// 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<string> {
    return JSON.stringify(this.metrics, null, 2);
  }
}

export const metrics = new MetricsService();

Considerações de Segurança

1. Criptografia de Dados Sensíveis

// 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<T>(encryptedData: string): T {
    const decrypted = this.decrypt(encryptedData);
    return JSON.parse(decrypted);
  }
}

2. Validação de Dados

// 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.