entregas_app/docs/BANCO_DADOS_SQLITE.md

848 lines
23 KiB
Markdown

# Banco de Dados SQLite - Estrutura e Funcionalidades
## Visão Geral
O aplicativo utiliza SQLite como banco de dados local principal, com fallback para AsyncStorage quando SQLite não está disponível (ex: plataforma web). O banco é usado para armazenamento offline, cache de dados e sincronização.
## Configuração do Banco
### Inicialização
**Arquivo**: `src/services/database.ts`
```typescript
import SQLite from 'expo-sqlite';
// Verificar disponibilidade do SQLite
let SQLite: any;
let db: any;
let usingSQLite = false;
try {
if (Platform.OS !== "web") {
SQLite = require("expo-sqlite");
if (SQLite && typeof SQLite.openDatabase === "function") {
db = SQLite.openDatabase("truckdelivery.db");
usingSQLite = true;
}
}
} catch (error) {
console.warn("SQLite não disponível, usando AsyncStorage");
}
```
### Fallback para AsyncStorage
```typescript
// Prefixos para chaves do AsyncStorage
const USERS_KEY = "@TruckDelivery:users:"
const DELIVERIES_KEY = "@TruckDelivery:deliveries:"
const ROUTES_KEY = "@TruckDelivery:routes:"
const SETTINGS_KEY = "@TruckDelivery:settings:"
```
## Estrutura das Tabelas
### 1. Tabela `users`
**Propósito**: Armazenar dados dos usuários
```sql
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT,
email TEXT,
role TEXT,
last_login INTEGER
);
```
**Campos**:
- `id`: Identificador único do usuário
- `name`: Nome completo do usuário
- `email`: Email do usuário
- `role`: Função/cargo do usuário
- `last_login`: Timestamp do último login
**Operações**:
```typescript
// Inserir usuário
await executeQuery(
"INSERT INTO users (id, name, email, role, last_login) VALUES (?, ?, ?, ?, ?)",
[id, name, email, role, lastLogin]
);
// Buscar usuário
const result = await executeQuery("SELECT * FROM users WHERE id = ?", [id]);
```
### 2. Tabela `deliveries`
**Propósito**: Armazenar dados das entregas
```sql
CREATE TABLE IF NOT EXISTS deliveries (
id TEXT PRIMARY KEY,
outId TEXT,
customerId TEXT,
customerName TEXT,
street TEXT,
streetNumber TEXT,
neighborhood TEXT,
city TEXT,
state TEXT,
zipCode TEXT,
customerPhone TEXT,
lat REAL,
lng REAL,
latFrom REAL,
lngFrom REAL,
deliverySeq INTEGER,
routing INTEGER,
sellerId TEXT,
storeId TEXT,
status TEXT,
outDate TEXT,
notes TEXT,
signature TEXT,
photos TEXT,
completedTime INTEGER,
completedBy TEXT,
version INTEGER DEFAULT 1,
lastModified INTEGER DEFAULT (strftime('%s', 'now')),
syncTimestamp INTEGER,
syncStatus TEXT DEFAULT 'pending'
);
```
**Campos**:
- `id`: Identificador único da entrega
- `outId`: ID da entrega no sistema externo
- `customerId`: ID do cliente
- `customerName`: Nome do cliente
- `street`: Rua do endereço
- `streetNumber`: Número do endereço
- `neighborhood`: Bairro
- `city`: Cidade
- `state`: Estado
- `zipCode`: CEP
- `customerPhone`: Telefone do cliente
- `lat/lng`: Coordenadas de destino
- `latFrom/lngFrom`: Coordenadas de origem
- `deliverySeq`: Sequência na rota
- `routing`: ID da rota
- `sellerId`: ID do vendedor
- `storeId`: ID da loja
- `status`: Status da entrega (pending, in_progress, delivered, failed)
- `outDate`: Data de saída
- `notes`: Observações da entrega
- `signature`: Assinatura em base64
- `photos`: URLs das fotos em JSON
- `completedTime`: Timestamp de conclusão
- `completedBy`: ID do usuário que completou
- `version`: Versão do registro
- `lastModified`: Última modificação
- `syncTimestamp`: Timestamp da sincronização
- `syncStatus`: Status de sincronização (pending, synced)
**Operações**:
```typescript
// Salvar entrega
await executeQuery(
`INSERT INTO deliveries (
id, client, address, coordinates, status,
scheduled_time, completed_time, signature, photos, notes, sync_status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[id, client, address, JSON.stringify(coordinates), status,
scheduledTime, completedTime, signature, JSON.stringify(photos),
notes, syncStatus]
);
// Buscar entregas por status
const result = await executeQuery(
"SELECT * FROM deliveries WHERE status = ? ORDER BY scheduled_time ASC",
[status]
);
```
### 3. Tabela `routes`
**Propósito**: Armazenar dados das rotas
```sql
CREATE TABLE IF NOT EXISTS routes (
id TEXT PRIMARY KEY,
name TEXT,
date TEXT,
deliveries TEXT,
status TEXT,
sync_status TEXT
);
```
**Campos**:
- `id`: Identificador único da rota
- `name`: Nome da rota
- `date`: Data da rota
- `deliveries`: IDs das entregas em JSON
- `status`: Status da rota
- `sync_status`: Status de sincronização
### 4. Tabela `settings`
**Propósito**: Armazenar configurações do aplicativo
```sql
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE,
value TEXT
);
```
**Campos**:
- `id`: Chave primária auto-incremento
- `key`: Chave da configuração
- `value`: Valor da configuração
**Configurações Padrão**:
```sql
INSERT OR IGNORE INTO settings (key, value) VALUES
('last_sync', '0'),
('offline_mode', 'false'),
('auto_sync', 'true');
```
### 5. Tabela `deliveries_offline`
**Propósito**: Armazenar entregas offline para sincronização
```sql
CREATE TABLE IF NOT EXISTS deliveries_offline (
id TEXT PRIMARY KEY,
outId INTEGER,
transactionId INTEGER,
deliveryDate TEXT,
receiverDoc TEXT,
receiverName TEXT,
lat REAL,
lng REAL,
broken INTEGER,
devolution INTEGER,
reasonDevolution TEXT,
deliveryImages TEXT,
userId INTEGER,
sync_status TEXT
);
```
**Campos**:
- `id`: Identificador único
- `outId`: ID da entrega no sistema
- `transactionId`: ID da transação
- `deliveryDate`: Data da entrega
- `receiverDoc`: Documento do receptor
- `receiverName`: Nome do receptor
- `lat`: Latitude
- `lng`: Longitude
- `broken`: Produto quebrado (0/1)
- `devolution`: Devolução (0/1)
- `reasonDevolution`: Motivo da devolução
- `deliveryImages`: Imagens em JSON
- `userId`: ID do usuário
- `sync_status`: Status de sincronização
### 6. Tabela `customer_invoices`
**Propósito**: Armazenar notas fiscais dos clientes
```sql
CREATE TABLE IF NOT EXISTS customer_invoices (
id TEXT PRIMARY KEY,
invoiceId TEXT,
transactionId INTEGER,
customerId TEXT,
customerName TEXT,
invoiceValue REAL,
status TEXT,
items TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
sync_status TEXT DEFAULT 'pending'
);
```
**Campos**:
- `id`: Identificador único
- `invoiceId`: ID da nota fiscal
- `transactionId`: ID da transação
- `customerId`: ID do cliente
- `customerName`: Nome do cliente
- `invoiceValue`: Valor da nota fiscal
- `status`: Status da nota fiscal
- `items`: Itens da nota fiscal em JSON
- `created_at`: Data de criação
- `sync_status`: Status de sincronização
### 7. Tabela `delivery_images`
**Propósito**: Gerenciar imagens das entregas
```sql
CREATE TABLE IF NOT EXISTS delivery_images (
id TEXT PRIMARY KEY,
deliveryId TEXT,
transactionId INTEGER,
imagePath TEXT,
imageUrl TEXT,
uploadStatus TEXT DEFAULT 'pending',
uploadAttempts INTEGER DEFAULT 0,
lastUploadAttempt INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (deliveryId) REFERENCES deliveries(id)
);
```
**Campos**:
- `id`: Identificador único
- `deliveryId`: ID da entrega
- `transactionId`: ID da transação
- `imagePath`: Caminho local da imagem
- `imageUrl`: URL da imagem no servidor
- `uploadStatus`: Status do upload (pending, uploaded, failed)
- `uploadAttempts`: Número de tentativas de upload
- `lastUploadAttempt`: Timestamp da última tentativa
- `created_at`: Data de criação
### 8. Tabela `sync_queue`
**Propósito**: Fila de sincronização para operações pendentes
```sql
CREATE TABLE IF NOT EXISTS sync_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT NOT NULL,
record_id TEXT NOT NULL,
action TEXT NOT NULL,
data TEXT,
priority INTEGER DEFAULT 1,
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 3,
last_attempt INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
status TEXT DEFAULT 'pending'
);
```
**Campos**:
- `id`: Identificador único
- `table_name`: Nome da tabela
- `record_id`: ID do registro
- `action`: Ação a ser executada (INSERT, UPDATE, DELETE)
- `data`: Dados em JSON
- `priority`: Prioridade da operação
- `attempts`: Número de tentativas
- `max_attempts`: Máximo de tentativas
- `last_attempt`: Timestamp da última tentativa
- `created_at`: Data de criação
- `status`: Status da operação (pending, processing, completed, failed)
### 9. Tabela `photo_uploads`
**Propósito**: Controle de upload de fotos
```sql
CREATE TABLE IF NOT EXISTS photo_uploads (
id TEXT PRIMARY KEY,
deliveryId TEXT,
transactionId INTEGER,
localPath TEXT,
serverUrl TEXT,
uploadStatus TEXT DEFAULT 'pending',
uploadProgress REAL DEFAULT 0,
uploadAttempts INTEGER DEFAULT 0,
lastUploadAttempt INTEGER,
errorMessage TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (deliveryId) REFERENCES deliveries(id)
);
```
**Campos**:
- `id`: Identificador único
- `deliveryId`: ID da entrega
- `transactionId`: ID da transação
- `localPath`: Caminho local da foto
- `serverUrl`: URL da foto no servidor
- `uploadStatus`: Status do upload (pending, uploading, completed, failed)
- `uploadProgress`: Progresso do upload (0-1)
- `uploadAttempts`: Número de tentativas
- `lastUploadAttempt`: Timestamp da última tentativa
- `errorMessage`: Mensagem de erro
- `created_at`: Data de criação
## Funções de Acesso aos Dados
### 1. Função Genérica de Query
```typescript
export const executeQuery = async (query: string, params: any[] = []): Promise<any> => {
if (usingSQLite) {
return new Promise((resolve, reject) => {
db.transaction((tx: any) => {
tx.executeSql(
query,
params,
(_: any, result: any) => resolve(result),
(_: any, error: any) => {
reject(error);
return false;
}
);
});
});
} else {
// Implementação para AsyncStorage
return executeAsyncStorageQuery(query, params);
}
};
```
### 2. Operações CRUD para Entregas
#### Buscar Entregas
```typescript
export const getDeliveries = async (status?: string): Promise<any[]> => {
try {
if (usingSQLite) {
let query = "SELECT * FROM deliveries";
const params: any[] = [];
if (status) {
query += " WHERE status = ?";
params.push(status);
}
query += " ORDER BY scheduled_time ASC";
const result = await executeQuery(query, params);
return result.rows._array;
} else {
// Fallback para AsyncStorage
const allDeliveries = await getAllDeliveriesFromAsyncStorage();
return status ? allDeliveries.filter(d => d.status === status) : allDeliveries;
}
} catch (error) {
console.error("Erro ao obter entregas:", error);
return [];
}
};
```
#### Salvar Entrega
```typescript
export const saveDelivery = async (delivery: any): Promise<boolean> => {
try {
const {
id, client, address, coordinates, status,
scheduled_time, completed_time, signature, photos, notes, sync_status
} = delivery;
if (usingSQLite) {
const existingDelivery = await getDeliveryById(id);
if (existingDelivery) {
// Atualizar entrega existente
await executeQuery(
`UPDATE deliveries SET
client = ?, address = ?, coordinates = ?, status = ?,
scheduled_time = ?, completed_time = ?, signature = ?,
photos = ?, notes = ?, sync_status = ?
WHERE id = ?`,
[client, address, JSON.stringify(coordinates), status,
scheduled_time, completed_time, signature,
JSON.stringify(photos), notes, sync_status, id]
);
} else {
// Inserir nova entrega
await executeQuery(
`INSERT INTO deliveries (
id, client, address, coordinates, status,
scheduled_time, completed_time, signature, photos, notes, sync_status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[id, client, address, JSON.stringify(coordinates), status,
scheduled_time, completed_time, signature,
JSON.stringify(photos), notes, sync_status]
);
}
} else {
// AsyncStorage fallback
await AsyncStorage.setItem(`${DELIVERIES_KEY}${id}`, JSON.stringify(delivery));
}
return true;
} catch (error) {
console.error("Erro ao salvar entrega:", error);
return false;
}
};
```
#### Atualizar Status
```typescript
export const updateDeliveryStatus = async (
id: string,
status: string,
completedTime?: number
): Promise<boolean> => {
try {
if (usingSQLite) {
let query = "UPDATE deliveries SET status = ?, sync_status = ?";
const params: any[] = [status, "pending"];
if (completedTime) {
query += ", completed_time = ?";
params.push(completedTime);
}
query += " WHERE id = ?";
params.push(id);
await executeQuery(query, params);
} else {
// AsyncStorage fallback
const deliveryJson = await AsyncStorage.getItem(`${DELIVERIES_KEY}${id}`);
if (deliveryJson) {
const delivery = JSON.parse(deliveryJson);
delivery.status = status;
delivery.sync_status = "pending";
if (completedTime) {
delivery.completed_time = completedTime;
}
await AsyncStorage.setItem(`${DELIVERIES_KEY}${id}`, JSON.stringify(delivery));
}
}
return true;
} catch (error) {
console.error("Erro ao atualizar status da entrega:", error);
return false;
}
};
```
### 3. Operações de Sincronização
#### Buscar Entregas Não Sincronizadas
```typescript
export const getUnsyncedDeliveries = async (): Promise<any[]> => {
try {
if (usingSQLite) {
const result = await executeQuery(
"SELECT * FROM deliveries WHERE sync_status = ?",
["pending"]
);
return result.rows._array;
} else {
const allDeliveries = await getAllDeliveriesFromAsyncStorage();
return allDeliveries.filter(d => d.sync_status === "pending");
}
} catch (error) {
console.error("Erro ao obter entregas não sincronizadas:", error);
return [];
}
};
```
#### Marcar como Sincronizada
```typescript
export const markDeliveryAsSynced = async (id: string): Promise<boolean> => {
try {
if (usingSQLite) {
await executeQuery(
"UPDATE deliveries SET sync_status = ? WHERE id = ?",
["synced", id]
);
} else {
const deliveryJson = await AsyncStorage.getItem(`${DELIVERIES_KEY}${id}`);
if (deliveryJson) {
const delivery = JSON.parse(deliveryJson);
delivery.sync_status = "synced";
await AsyncStorage.setItem(`${DELIVERIES_KEY}${id}`, JSON.stringify(delivery));
}
}
return true;
} catch (error) {
console.error("Erro ao marcar entrega como sincronizada:", error);
return false;
}
};
```
### 4. Operações de Configurações
#### Obter Configuração
```typescript
export const getSetting = async (key: string): Promise<string | null> => {
try {
if (usingSQLite) {
const result = await executeQuery(
"SELECT value FROM settings WHERE key = ?",
[key]
);
if (result.rows.length > 0) {
return result.rows._array[0].value;
}
return null;
} else {
return await AsyncStorage.getItem(`${SETTINGS_KEY}${key}`);
}
} catch (error) {
console.error("Erro ao obter configuração:", error);
return null;
}
};
```
#### Salvar Configuração
```typescript
export const saveSetting = async (key: string, value: string): Promise<boolean> => {
try {
if (usingSQLite) {
await executeQuery(
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
[key, value]
);
} else {
await AsyncStorage.setItem(`${SETTINGS_KEY}${key}`, value);
}
return true;
} catch (error) {
console.error("Erro ao salvar configuração:", error);
return false;
}
};
```
## Implementação AsyncStorage (Fallback)
### Funções Auxiliares
```typescript
async function getAllUsersFromAsyncStorage() {
try {
const keys = await AsyncStorage.getAllKeys();
const userKeys = keys.filter(key => key.startsWith(USERS_KEY));
const userItems = await AsyncStorage.multiGet(userKeys);
return userItems.map(([_, value]) => JSON.parse(value));
} catch (error) {
console.error("Erro ao obter usuários do AsyncStorage:", error);
return [];
}
}
async function getAllDeliveriesFromAsyncStorage() {
try {
const keys = await AsyncStorage.getAllKeys();
const deliveryKeys = keys.filter(key => key.startsWith(DELIVERIES_KEY));
const deliveryItems = await AsyncStorage.multiGet(deliveryKeys);
return deliveryItems.map(([_, value]) => JSON.parse(value));
} catch (error) {
console.error("Erro ao obter entregas do AsyncStorage:", error);
return [];
}
}
```
### Simulação de Queries SQL
```typescript
async function executeAsyncStorageQuery(query: string, params: any[]): Promise<any> {
try {
if (query.toUpperCase().startsWith("SELECT")) {
if (query.includes("FROM users")) {
const allUsers = await getAllUsersFromAsyncStorage();
return { rows: { _array: allUsers } };
} else if (query.includes("FROM deliveries")) {
const allDeliveries = await getAllDeliveriesFromAsyncStorage();
return { rows: { _array: allDeliveries } };
} else if (query.includes("FROM settings")) {
const key = params[0];
const value = await AsyncStorage.getItem(`${SETTINGS_KEY}${key}`);
return { rows: { _array: value ? [{ value }] : [] } };
}
} else if (query.toUpperCase().startsWith("INSERT") || query.toUpperCase().startsWith("UPDATE")) {
// Implementação simplificada para INSERT/UPDATE
return { rowsAffected: 1 };
}
return { rowsAffected: 1 };
} catch (error) {
console.error("Erro na operação do AsyncStorage:", error);
throw error;
}
}
```
## Operações de Entregas Offline
### Salvar Entrega Offline
```typescript
export const saveOfflineDelivery = async (delivery: any): Promise<boolean> => {
try {
if (usingSQLite) {
await executeQuery(
`INSERT OR REPLACE INTO deliveries_offline (
id, outId, transactionId, deliveryDate, receiverDoc, receiverName,
lat, lng, broken, devolution, reasonDevolution, deliveryImages, userId, sync_status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
delivery.id, delivery.outId, delivery.transactionId, delivery.deliveryDate,
delivery.receiverDoc, delivery.receiverName, delivery.lat, delivery.lng,
delivery.broken ? 1 : 0, delivery.devolution ? 1 : 0,
delivery.reasonDevolution, JSON.stringify(delivery.deliveryImages),
delivery.userId, delivery.sync_status || 'pending'
]
);
} else {
await AsyncStorage.setItem(
`@TruckDelivery:deliveries_offline:${delivery.id}`,
JSON.stringify(delivery)
);
}
return true;
} catch (error) {
console.error('Erro ao salvar entrega offline:', error);
return false;
}
};
```
### Buscar Entregas Offline
```typescript
export const getOfflineDeliveries = async (): Promise<any[]> => {
try {
if (usingSQLite) {
const result = await executeQuery(
'SELECT * FROM deliveries_offline WHERE sync_status = ?',
['pending']
);
return result.rows._array.map((row: any) => ({
...row,
deliveryImages: row.deliveryImages ? JSON.parse(row.deliveryImages) : [],
broken: !!row.broken,
devolution: !!row.devolution,
}));
} else {
const keys = await AsyncStorage.getAllKeys();
const offlineKeys = keys.filter(key =>
key.startsWith('@TruckDelivery:deliveries_offline:')
);
const items = await AsyncStorage.multiGet(offlineKeys);
return items.map(([_, value]) => JSON.parse(value))
.filter(d => d.sync_status === 'pending');
}
} catch (error) {
console.error('Erro ao buscar entregas offline:', error);
return [];
}
};
```
## Otimizações e Índices
### Índices Recomendados
```sql
-- Índices para melhorar performance
CREATE INDEX IF NOT EXISTS idx_deliveries_status ON deliveries(status);
CREATE INDEX IF NOT EXISTS idx_deliveries_sync_status ON deliveries(sync_status);
CREATE INDEX IF NOT EXISTS idx_deliveries_scheduled_time ON deliveries(scheduled_time);
CREATE INDEX IF NOT EXISTS idx_deliveries_offline_sync_status ON deliveries_offline(sync_status);
CREATE INDEX IF NOT EXISTS idx_settings_key ON settings(key);
```
### Limpeza de Dados Antigos
```typescript
export const cleanupOldData = async (daysOld: number = 30): Promise<void> => {
try {
const cutoffDate = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
if (usingSQLite) {
// Remover entregas antigas já sincronizadas
await executeQuery(
"DELETE FROM deliveries WHERE completed_time < ? AND sync_status = ?",
[cutoffDate, "synced"]
);
// Remover entregas offline antigas
await executeQuery(
"DELETE FROM deliveries_offline WHERE deliveryDate < ? AND sync_status = ?",
[cutoffDate, "synced"]
);
}
} catch (error) {
console.error("Erro na limpeza de dados:", error);
}
};
```
## Monitoramento e Logs
### Informações de Armazenamento
```typescript
export const storageInfo = {
type: usingSQLite ? "SQLite" : "AsyncStorage",
isUsingSQLite: usingSQLite,
};
// Função para obter estatísticas do banco
export const getDatabaseStats = async (): Promise<any> => {
try {
if (usingSQLite) {
const deliveriesResult = await executeQuery("SELECT COUNT(*) as count FROM deliveries");
const offlineResult = await executeQuery("SELECT COUNT(*) as count FROM deliveries_offline");
const unsyncedResult = await executeQuery(
"SELECT COUNT(*) as count FROM deliveries WHERE sync_status = ?",
["pending"]
);
return {
totalDeliveries: deliveriesResult.rows._array[0].count,
offlineDeliveries: offlineResult.rows._array[0].count,
unsyncedDeliveries: unsyncedResult.rows._array[0].count,
storageType: "SQLite"
};
} else {
const keys = await AsyncStorage.getAllKeys();
return {
totalKeys: keys.length,
storageType: "AsyncStorage"
};
}
} catch (error) {
console.error("Erro ao obter estatísticas:", error);
return null;
}
};
```
## Considerações para Sincronização Offline
### 1. Estrutura para Sincronização Incremental
```sql
-- Adicionar campos de controle de versão
ALTER TABLE deliveries ADD COLUMN version INTEGER DEFAULT 1;
ALTER TABLE deliveries ADD COLUMN last_modified INTEGER;
ALTER TABLE deliveries ADD COLUMN sync_timestamp INTEGER;
```
### 2. Tabela de Log de Sincronização
```sql
CREATE TABLE IF NOT EXISTS sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT,
record_id TEXT,
action TEXT,
timestamp INTEGER,
success INTEGER,
error_message TEXT
);
```
### 3. Controle de Conflitos
```sql
CREATE TABLE IF NOT EXISTS sync_conflicts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT,
record_id TEXT,
local_data TEXT,
server_data TEXT,
resolution TEXT,
timestamp INTEGER
);
```
Esta documentação fornece uma visão completa da estrutura do banco de dados SQLite utilizado pelo aplicativo, incluindo todas as tabelas, operações e considerações para implementação de sincronização offline eficiente.