Ir al contenido

Sincronizar Transacciones

Importa transacciones desde un banco conectado vía Open Banking.

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)
  1. Haz clic en icono sync (⟳) junto a conexión bancaria
  2. Opcional: Selecciona rango de fechas personalizado
  3. Se ejecuta sincronización (puede tardar 10-60 segundos)
  4. Toast confirma número de transacciones importadas
  1. Saldos actuales de todas las cuentas
  2. Transacciones nuevas en rango de fechas
  3. Actualización metadatos (merchant_name, transaction_category)

Transacciones reconciliadas no se sobrescriben ✅ Matches manuales se preservan ✅ Notas del usuario se mantienen

ErrorCausaSolución
”Token expired and could not be refreshed”Refresh token inválidoReconectar banco (botón “Reconectar”)
“Sync failed: consecutive_failures=5”5 fallos consecutivos, conexión inactivaVerificar estado del banco, reconectar
”Rate limit exceeded”Demasiadas peticiones a TrueLayer APIEsperar 1 hora y reintentar
Detalles de implementación

Edge Function: truelayer-sync-transactions

Flujo de sincronización:

  1. Desencripta tokens con get_bank_connection_tokens RPC
  2. Verifica expiración: si expira en menos de 5 min, refresca proactivamente
  3. Fetch cuentas: GET /data/v1/accounts
  4. Fetch saldos: GET /data/v1/accounts/{id}/balance
  5. Fetch transacciones: GET /data/v1/accounts/{id}/transactions?from={from_date}&to={to_date}
  6. Upsert inteligente:
    // Transacción YA reconciliada → solo actualizar datos, NO estado
    if (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 completo
    await supabase.from('bank_transactions').upsert({ ... });
    }
  7. Actualiza bank_sync_history con resultado
  8. Resetea consecutive_failures a 0 si éxito

Manejo de tokens expirados/revocados:

// Detección de token inválido
if (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');
}
}
);