Sincronizar Transacciones
Exportar contenido
Importa transacciones desde un banco conectado vía Open Banking.
Sincronización automática
Sección titulada «Sincronización automática»Si configuraste frecuencia automática (every_6h, daily, weekly):
- Se ejecuta en background vía cron job
- No requiere intervención manual
- Notifica si falla (email o en-app notification)
Sincronización manual
Sección titulada «Sincronización manual»- Haz clic en icono sync (⟳) junto a conexión bancaria
- Opcional: Selecciona rango de fechas personalizado
- Se ejecuta sincronización (puede tardar 10-60 segundos)
- Toast confirma número de transacciones importadas
Qué se sincroniza
Sección titulada «Qué se sincroniza»- Saldos actuales de todas las cuentas
- Transacciones nuevas en rango de fechas
- Actualización metadatos (merchant_name, transaction_category)
Preservación de datos
Sección titulada «Preservación de datos»✅ Transacciones reconciliadas no se sobrescriben ✅ Matches manuales se preservan ✅ Notas del usuario se mantienen
Errores comunes
Sección titulada «Errores comunes»| Error | Causa | Solución |
|---|---|---|
| ”Token expired and could not be refreshed” | Refresh token inválido | Reconectar banco (botón “Reconectar”) |
| “Sync failed: consecutive_failures=5” | 5 fallos consecutivos, conexión inactiva | Verificar estado del banco, reconectar |
| ”Rate limit exceeded” | Demasiadas peticiones a TrueLayer API | Esperar 1 hora y reintentar |
Notas técnicas
Sección titulada «Notas técnicas»Detalles de implementación
Edge Function: truelayer-sync-transactions
Flujo de sincronización:
- Desencripta tokens con
get_bank_connection_tokensRPC - Verifica expiración: si expira en menos de 5 min, refresca proactivamente
- Fetch cuentas:
GET /data/v1/accounts - Fetch saldos:
GET /data/v1/accounts/{id}/balance - Fetch transacciones:
GET /data/v1/accounts/{id}/transactions?from={from_date}&to={to_date} - Upsert inteligente:
// Transacción YA reconciliada → solo actualizar datos, NO estadoif (existingTx && existingTx.reconciliation_status !== 'pending') {await supabase.from('bank_transactions').update({description: tx.description,amount: Math.abs(tx.amount),merchant_name: tx.merchant_name,// ❌ NO tocar: reconciliation_status, matched_expense_id}).eq('id', existingTx.id);} else {// Nueva transacción → upsert completoawait supabase.from('bank_transactions').upsert({ ... });}
- Actualiza
bank_sync_historycon resultado - Resetea
consecutive_failuresa 0 si éxito
Manejo de tokens expirados/revocados:
// Detección de token inválidoif (response.error === 'invalid_token') { // Intento de refresh reactivo const refreshResult = await handleInvalidToken( refreshToken, connection_id, supabase );
if (refreshResult.success) { // Retry con nuevo token currentAccessToken = refreshResult.access_token; // Continuar sincronización... } else { // Marcar conexión como 'revoked' o 'expired' await supabase .from('bank_connections') .update({ status: refreshResult.reason === 'revoked' ? 'revoked' : 'expired', requires_reauth: true }) .eq('id', connection_id);
throw new Error('Token revoked, user must reconnect'); }}Retry con backoff exponencial:
import { retryWithBackoff } from '../_shared/retry-helper.ts';
const accountResponse = await retryWithBackoff( () => fetch( `${TRUELAYER_API_BASE}/data/v1/accounts/${accountId}`, { headers: { 'Authorization': `Bearer ${accessToken}` } } ), { maxRetries: 2, baseDelay: 1000, shouldRetry: (error) => { // No reintentar si es invalid_token return !error?.message?.includes('invalid_token'); } });