import { workflow, trigger, node } from 'n8n-workflow-sdk'; export default workflow('Supabase → Sheet VENDAS', { description: 'Busca fechamentos_web do Supabase e append na Sheet VENDAS (incremental por atualizado_em UTC)', nodes: [ // ── TRIGGER: 20h BRT (23h UTC) ────────────────────────────────── trigger('Schedule Daily 20h', { type: 'n8n-nodes-base.scheduleTrigger', typeVersion: 1.2, position: [100, 300], parameters: { rule: { interval: [{ field: 'cron', expression: '0 20 * * *' }] } } }), // ── READ LAST SYNC from Sheet Sincronizacao A1 ─────────────────── node('Read Last Sync', { type: 'n8n-nodes-base.googleSheets', typeVersion: 4.2, position: [340, 300], credentials: { googleSheetsOAuth2Api: { id: 'qVVlfm46BZ6tX5VX', name: 'Google Sheets account' } }, parameters: { operation: 'read', documentId: { __rl: true, mode: 'id', value: '1RGa7Qigu5kWJGCOx3kJ3Oa8wTTJeD-CzGjBYazrjsWk' }, sheetName: { __rl: true, mode: 'list', value: 'Sincronizacao' }, cellSelection: { __rl: true, mode: 'list', value: 'A1' }, options: { cellFormat: 'USER_ENTERED' } } }), // ── FETCH fechamentos_web from Supabase ────────────────────────── node('Fetch from Supabase', { type: 'n8n-nodes-base.httpRequest', typeVersion: 4.2, position: [600, 300], credentials: { supabaseApi: { id: 'Tw9r35a9Iz3P9q7x', name: 'Supabase account 2 (self-hosted)' } }, parameters: { method: 'GET', url: '=https://supabase.ofrangao.com.br/rest/v1/fechamentos_web', queryParameters: { parameters: [ { name: 'select', value: '=id_fechamento,data,loja,turno,operador,fechamento_dinheiro,fechamento_cartoes,total_sangrias,total_despesas,total_vales,total_pixcnpj,saldo_pixcnpj,total_areceber,total_cartao_credito,total_cartao_debito,total_cartao_alimentacao,total_cancelamentos,diferenca,status_diferenca,atualizado_em' }, { name: 'order', value: '=atualizado_em.asc' }, { name: 'limit', value: '=500' } ] } } }), // ── CODE: Filter by lastSync + Map to Sheet columns ───────────── node('Filter + Map', { type: 'n8n-nodes-base.code', typeVersion: 2, position: [860, 300], parameters: { jsCode: ` // 1. lastSync da Sheet const lastSyncRaw = $input.first().json.value || null; const lastSync = lastSyncRaw ? new Date(lastSyncRaw).toISOString() : null; // 2. Todas as rows do Supabase const allRows = $input.all().map(item => item.json); // 3. Filtrar por lastSync let filtered = lastSync ? allRows.filter(r => r.atualizado_em && r.atualizado_em > lastSync) : allRows; if (filtered.length === 0) { // Sem linhas novas —marker o sync point com lastSync antigo para não re-buscar return [{ json: { _action: 'noop', _lastSync: lastSync, _max_updated: lastSync, DATA:'', EMPRESA:'', SANGRIA:0, DESPESAS:0, VALES:0, DINHEIRO:0, PIX_TAXA:0, PIX_LOJA:0, PRAZO:0, P2:0, CREDITO:0, DEBITO:0, VOUCHER:0, CARTAO:0, T_VENDA:0, TX_CAIXA:0, CLIENTES:0, CANCELADOS:0, SISTEMA:0, DIFERENCA:0, CAIXA:'', PG:false } }]; } // 4. MAX(atualizado_em) do batch const maxUpdated = filtered.reduce((m, r) => r.atualizado_em > m ? r.atualizado_em : m, filtered[0].atualizado_em); // 5. Mapear para colunas Sheet const LOJA_MAP = { alianca: 'Aliança', uniao: 'União' }; return filtered.map(r => ({ json: { DATA: r.data, EMPRESA: LOJA_MAP[r.loja] || r.loja, SANGRIA: r.total_sangrias || 0, DESPESAS: r.total_despesas || 0, VALES: r.total_vales || 0, DINHEIRO: r.fechamento_dinheiro || 0, PIX_TAXA: r.total_pixcnpj || 0, PIX_LOJA: r.saldo_pixcnpj || 0, PRAZO: r.total_areceber || 0, P2: 0, CREDITO: r.total_cartao_credito || 0, DEBITO: r.total_cartao_debito || 0, VOUCHER: r.total_cartao_alimentacao || 0, CARTAO: r.fechamento_cartoes || 0, T_VENDA: 0, // fórmula na Sheet TX_CAIXA: 0, // fórmula na Sheet CLIENTES: 0, CANCELADOS: r.total_cancelamentos || 0, SISTEMA: 0, // fórmula na Sheet DIFERENCA: r.diferenca || 0, CAIXA: r.operador || '', PG: r.status_diferenca === 'ok', _action: 'append', _max_updated: maxUpdated } })); ` } }), // ── APPEND to Sheet VENDAS ────────────────────────────────────── node('Append to VENDAS', { type: 'n8n-nodes-base.googleSheets', typeVersion: 4.2, position: [1100, 200], credentials: { googleSheetsOAuth2Api: { id: 'qVVlfm46BZ6tX5VX', name: 'Google Sheets account' } }, parameters: { operation: 'append', documentId: { __rl: true, mode: 'id', value: '1RGa7Qigu5kWJGCOx3kJ3Oa8wTTJeD-CzGjBYazrjsWk' }, sheetName: { __rl: true, mode: 'list', value: 'VENDAS' }, options: { cellFormat: 'USER_ENTERED' } } }), // ── UPDATE LAST SYNC in Sincronizacao A1 ─────────────────────── // Recebe o primeiro item (com _max_updated) de Filter+Map node('Update Last Sync', { type: 'n8n-nodes-base.googleSheets', typeVersion: 4.2, position: [1100, 440], credentials: { googleSheetsOAuth2Api: { id: 'qVVlfm46BZ6tX5VX', name: 'Google Sheets account' } }, parameters: { operation: 'update', documentId: { __rl: true, mode: 'id', value: '1RGa7Qigu5kWJGCOx3kJ3Oa8wTTJeD-CzGjBYazrjsWk' }, sheetName: { __rl: true, mode: 'list', value: 'Sincronizacao' }, cellSelection: { __rl: true, mode: 'list', value: 'A1' }, valueInputOption: 'USER_ENTERED', options: { cellFormat: 'USER_ENTERED' } } }), ], connections: { 'Schedule Daily 20h': { main: [[{ node: 'Read Last Sync', type: 'main', index: 0 }]] }, 'Read Last Sync': { main: [[{ node: 'Fetch from Supabase', type: 'main', index: 0 }]] }, 'Fetch from Supabase': { main: [[{ node: 'Filter + Map', type: 'main', index: 0 }]] }, 'Filter + Map': { main: [ // Branch 0: dados para Sheet [{ node: 'Append to VENDAS', type: 'main', index: 0 }], // Branch 1: marker sync (vai directo para Update Last Sync) [{ node: 'Update Last Sync', type: 'main', index: 0 }] ] }, 'Append to VENDAS': { main: [[{ node: 'Update Last Sync', type: 'main', index: 0 }]] } }, settings: { executionOrder: 'v1', saveExecutionProgress: true, saveDataSuccessExecution: 'all', saveDataErrorExecution: 'all' } });