entregas_app/docs/ESTRATEGIA_SINCRONIZACAO_OF...

1134 lines
33 KiB
Markdown

# 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<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`
```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<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`
```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<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`
```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<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
```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<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
```typescript
// 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
```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 (
<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
```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 (
<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
```typescript
// 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
```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<string> {
const fileContent = await FileSystem.readAsStringAsync(filePath);
return this.compress(fileContent);
}
}
```
### 2. Cache Inteligente
```typescript
// 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
```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<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
```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<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
```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<string> {
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<T>(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.