Ir para o conteúdo

Como sincronizar o Google Sheets com o Calendário do iPhone usando Apps Script

Aprenda a sincronizar uma planilha do Google Sheets com o Calendário do iPhone usando Google Apps Script e receba alertas dos seus vencimentos.

MacBook com planilha de gastos no Google Sheets e iPhone mostrando os mesmos vencimentos no widget do Calendário

Você já usa uma planilha para controlar suas contas, mas continua precisando abrir o arquivo para descobrir o que vence nos próximos dias?

Foi exatamente esse problema que me levou a criar esta automação.

Eu tinha uma planilha com minhas despesas, vencimentos, valores e status de pagamento. Ela funcionava bem para registrar e atualizar as informações, mas não era o melhor lugar para consultar o que precisava ser pago.

A solução foi transformar o Google Sheets na fonte dos dados e o Google Calendar na interface para acompanhar os vencimentos.

O resultado é que hoje consigo simplesmente olhar o widget do Calendário no iPhone — ou perguntar à Siri — quais são os próximos vencimentos.

Nota

As imagens e valores utilizados neste artigo representam um projeto real testado. Informações pessoais podem ser ocultadas ou alteradas antes da publicação.

O problema: a planilha funciona, mas não é onde eu quero consultar minhas contas

Minha planilha possui basicamente quatro informações para cada despesa:

InformaçãoExemplo
ItemFaculdade
Vencimento12/08/2026
ValorR$ 260,00
StatusA VENCER

Ela é excelente para gerenciar as contas.

Posso alterar o status de uma despesa quando ela é paga, adicionar uma nova conta ou alterar um vencimento.

O problema é outro: eu não queria precisar abrir a planilha toda vez que quisesse saber o que estava próximo de vencer.

Detalhe da planilha financeira usada como fonte de dados da sincronização

A ideia: deixar o calendário cuidar dos lembretes

Calendário é uma ferramenta que eu já consulto naturalmente durante o dia.

Então, em vez de criar mais um aplicativo ou uma interface para acompanhar as contas, a ideia foi simples:

Planilha → Apps Script → Google Calendar → Calendário do iPhone

A planilha continua sendo responsável pelos dados.

O calendário passa a ser responsável pela visualização dos próximos vencimentos e pelos alertas.

E o melhor: o Google Calendar sincroniza com o aplicativo Calendário do iPhone.

Como funciona a automação

A automação foi feita utilizando o Google Apps Script, que permite executar JavaScript integrado aos serviços do Google.

O fluxo ficou assim:

Google Sheets
      ↓
Apps Script
      ↓
Google Calendar
      ↓
Calendário do iPhone
      ↓
Widget / Siri / notificações

O script lê cada linha da planilha, verifica se já existe um evento correspondente e:

  • cria o evento se ele ainda não existir;
  • atualiza o evento se ele já existir;
  • evita eventos duplicados;
  • mantém um identificador único para cada despesa;
  • coloca o valor e o status dentro do evento;
  • configura um lembrete para um dia antes do vencimento.

1. Prepare sua planilha

No projeto utilizado neste artigo:

E → ITEM
F → VENCIMENTO
G → VALOR
H → STATUS
I → ID_CALENDAR

Exemplo:

ITEMVENCIMENTOVALORSTATUS
Faculdade12/08/2026R$ 260,00A VENCER
Seguro13/08/2026R$ 235,00A VENCER
Condomínio15/08/2026R$ 150,00A VENCER

A coluna ID_CALENDAR é utilizada para identificar permanentemente cada despesa.

Detalhe da planilha financeira usada como fonte de dados da sincronização

2. Crie o Apps Script

Na planilha, acesse:

Extensões → Apps Script

Crie o projeto e cole o código completo abaixo.

O script cria automaticamente um menu chamado 📅 Calendário Financeiro, com opções como:

  • 🔄 Sincronizar agora
  • 🧹 Limpar duplicados
  • 🔎 Diagnosticar planilha
  • 📋 Ver calendário configurado
Editor do Google Apps Script com o projeto Sincronização Financeira

Código completo (Apps Script)

/*******************************************************
 * SINCRONIZAÇÃO DE DESPESAS COM GOOGLE CALENDAR
 *
 * E = ITEM
 * F = VENCIMENTO
 * G = VALOR
 * H = STATUS
 * I = ID_CALENDAR
 *******************************************************/

const CONFIG = {
  CALENDAR_NAME: '💰 Vencimentos Financeiros',

  ITEM_COL: 5,
  VENCIMENTO_COL: 6,
  VALOR_COL: 7,
  STATUS_COL: 8,
  ID_COL: 9,

  FIRST_DATA_ROW: 2,

  REMINDER_DAYS_BEFORE: 1,

  EVENT_PREFIX: '💰 Vencimento — ',

  EVENT_ID_PREFIX: 'ID_CALENDAR: ',

  OLD_EVENT_ID_PREFIX: 'ID_DESPESA: '
};

function onOpen() {
  SpreadsheetApp
    .getUi()
    .createMenu('📅 Calendário Financeiro')
    .addItem('🔄 Sincronizar agora', 'sincronizarDespesas')
    .addItem('🧹 Limpar duplicados', 'limparEventosDuplicados')
    .addItem('🔎 Diagnosticar planilha', 'diagnosticarPlanilha')
    .addItem('📋 Ver calendário configurado', 'mostrarCalendario')
    .addToUi();
}

function sincronizarDespesas() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const calendar = obterOuCriarCalendario();
  const sheets = ss.getSheets();

  let totalCriados = 0;
  let totalAtualizados = 0;
  let totalIgnorados = 0;
  let totalEncontrados = 0;
  let totalDuplicadosRemovidos = 0;
  let totalIdsCriados = 0;

  sheets.forEach(function(sheet) {
    const nomeAba = sheet.getName();
    const ultimaLinha = sheet.getLastRow();

    if (ultimaLinha < CONFIG.FIRST_DATA_ROW) {
      return;
    }

    const quantidadeLinhas =
      ultimaLinha - CONFIG.FIRST_DATA_ROW + 1;

    const dados =
      sheet
        .getRange(
          CONFIG.FIRST_DATA_ROW,
          CONFIG.ITEM_COL,
          quantidadeLinhas,
          5
        )
        .getValues();

    dados.forEach(function(linha, index) {
      const linhaPlanilha =
        CONFIG.FIRST_DATA_ROW + index;

      const item = linha[0];
      const vencimento = linha[1];
      const valor = linha[2];
      const status = linha[3];
      let idCalendar = linha[4];

      if (
        item === null ||
        item === undefined ||
        String(item).trim() === ''
      ) {
        totalIgnorados++;
        return;
      }

      if (
        String(item)
          .trim()
          .toUpperCase() === 'TOTAL'
      ) {
        totalIgnorados++;
        return;
      }

      if (
        !vencimento ||
        !(vencimento instanceof Date) ||
        isNaN(vencimento.getTime())
      ) {
        totalIgnorados++;
        return;
      }

      totalEncontrados++;

      const idAntigo =
        criarIdDespesaAntigo(
          nomeAba,
          linhaPlanilha,
          item
        );

      if (
        idCalendar === null ||
        idCalendar === undefined ||
        String(idCalendar).trim() === ''
      ) {
        idCalendar = Utilities.getUuid();

        sheet
          .getRange(
            linhaPlanilha,
            CONFIG.ID_COL
          )
          .setValue(idCalendar);

        totalIdsCriados++;
      } else {
        idCalendar = String(idCalendar).trim();
      }

      const eventos =
        buscarEventosNaData(
          calendar,
          vencimento
        );

      let eventosEncontrados =
        eventos.filter(function(evento) {
          const descricao =
            evento.getDescription() || '';

          const id =
            extrairId(
              descricao,
              CONFIG.EVENT_ID_PREFIX
            );

          return id === idCalendar;
        });

      if (eventosEncontrados.length === 0) {
        eventosEncontrados =
          eventos.filter(function(evento) {
            const descricao =
              evento.getDescription() || '';

            const id =
              extrairId(
                descricao,
                CONFIG.OLD_EVENT_ID_PREFIX
              );

            return id === idAntigo;
          });
      }

      if (eventosEncontrados.length > 0) {
        const eventoPrincipal =
          eventosEncontrados[0];

        if (eventosEncontrados.length > 1) {
          for (
            let i = 1;
            i < eventosEncontrados.length;
            i++
          ) {
            try {
              eventosEncontrados[i]
                .deleteEvent();

              totalDuplicadosRemovidos++;
            } catch (erro) {
              Logger.log(
                'Erro ao remover duplicado: ' +
                erro
              );
            }
          }
        }

        atualizarEvento(
          eventoPrincipal,
          item,
          vencimento,
          valor,
          status,
          nomeAba,
          linhaPlanilha,
          idCalendar
        );

        totalAtualizados++;
      } else {
        criarEvento(
          calendar,
          item,
          vencimento,
          valor,
          status,
          nomeAba,
          linhaPlanilha,
          idCalendar
        );

        totalCriados++;
      }
    });
  });

  SpreadsheetApp
    .getUi()
    .alert(
      '✅ Sincronização concluída!\n\n' +
      '📊 Despesas encontradas: ' +
      totalEncontrados +
      '\n\n🟢 Eventos criados: ' +
      totalCriados +
      '\n🔄 Eventos atualizados: ' +
      totalAtualizados +
      '\n🧹 Duplicados removidos: ' +
      totalDuplicadosRemovidos +
      '\n🆔 IDs criados: ' +
      totalIdsCriados +
      '\n⚪ Linhas ignoradas: ' +
      totalIgnorados
    );
}

function buscarEventosNaData(calendar, data) {
  const inicio = new Date(data);
  inicio.setHours(0, 0, 0, 0);

  const fim = new Date(data);
  fim.setHours(23, 59, 59, 999);

  return calendar.getEvents(
    inicio,
    fim
  );
}

function obterOuCriarCalendario() {
  const calendars =
    CalendarApp.getCalendarsByName(
      CONFIG.CALENDAR_NAME
    );

  if (calendars.length > 0) {
    return calendars[0];
  }

  return CalendarApp.createCalendar(
    CONFIG.CALENDAR_NAME
  );
}

function criarIdDespesaAntigo(
  nomeAba,
  linha,
  item
) {
  return (
    nomeAba +
    '|' +
    linha +
    '|' +
    String(item).trim()
  );
}

function extrairId(
  descricao,
  prefixo
) {
  const linhas =
    String(descricao || '')
      .split('\n');

  for (
    let i = 0;
    i < linhas.length;
    i++
  ) {
    const linha =
      linhas[i].trim();

    if (
      linha.indexOf(prefixo) === 0
    ) {
      return linha
        .substring(prefixo.length)
        .trim();
    }
  }

  return null;
}

function criarEvento(
  calendar,
  item,
  vencimento,
  valor,
  status,
  nomeAba,
  linhaPlanilha,
  idCalendar
) {
  const titulo =
    CONFIG.EVENT_PREFIX +
    String(item).trim();

  const descricao =
    criarDescricao(
      item,
      valor,
      status,
      nomeAba,
      linhaPlanilha,
      idCalendar
    );

  const evento =
    calendar.createAllDayEvent(
      titulo,
      vencimento,
      {
        description: descricao
      }
    );

  evento.addPopupReminder(
    CONFIG.REMINDER_DAYS_BEFORE *
    24 *
    60
  );

  return evento;
}

function atualizarEvento(
  evento,
  item,
  vencimento,
  valor,
  status,
  nomeAba,
  linhaPlanilha,
  idCalendar
) {
  evento.setTitle(
    CONFIG.EVENT_PREFIX +
    String(item).trim()
  );

  evento.setDescription(
    criarDescricao(
      item,
      valor,
      status,
      nomeAba,
      linhaPlanilha,
      idCalendar
    )
  );

  evento.setAllDayDate(
    vencimento
  );

  evento.removeAllReminders();

  evento.addPopupReminder(
    CONFIG.REMINDER_DAYS_BEFORE *
    24 *
    60
  );
}

function criarDescricao(
  item,
  valor,
  status,
  nomeAba,
  linhaPlanilha,
  idCalendar
) {
  let valorFormatado;

  if (
    typeof valor === 'number'
  ) {
    valorFormatado =
      valor.toLocaleString(
        'pt-BR',
        {
          style: 'currency',
          currency: 'BRL'
        }
      );
  } else {
    valorFormatado =
      String(valor || '');
  }

  return (
    '💰 DESPESA FINANCEIRA\n\n' +
    'Item: ' +
    item +
    '\nValor: ' +
    valorFormatado +
    '\nStatus: ' +
    (status || 'A VENCER') +
    '\n\nOrigem:\n' +
    'Aba: ' +
    nomeAba +
    '\nLinha: ' +
    linhaPlanilha +
    '\n\n' +
    CONFIG.EVENT_ID_PREFIX +
    idCalendar
  );
}

function limparEventosDuplicados() {
  const calendar =
    obterOuCriarCalendario();

  const hoje =
    new Date();

  const inicio =
    new Date(hoje);

  inicio.setMonth(
    inicio.getMonth() - 12
  );

  const fim =
    new Date(hoje);

  fim.setMonth(
    fim.getMonth() + 24
  );

  const eventos =
    calendar.getEvents(
      inicio,
      fim
    );

  const grupos = {};

  eventos.forEach(function(evento) {
    const descricao =
      evento.getDescription() || '';

    const id =
      extrairId(
        descricao,
        CONFIG.EVENT_ID_PREFIX
      );

    if (!id) {
      return;
    }

    if (!grupos[id]) {
      grupos[id] = [];
    }

    grupos[id].push(evento);
  });

  let removidos = 0;

  Object.keys(grupos).forEach(function(id) {
    const grupo =
      grupos[id];

    if (grupo.length <= 1) {
      return;
    }

    for (
      let i = 1;
      i < grupo.length;
      i++
    ) {
      try {
        grupo[i].deleteEvent();
        removidos++;
      } catch (erro) {
        Logger.log(erro);
      }
    }
  });

  SpreadsheetApp
    .getUi()
    .alert(
      '🧹 Limpeza concluída!\n\n' +
      'Duplicados removidos: ' +
      removidos
    );
}

function diagnosticarPlanilha() {
  const ss =
    SpreadsheetApp
      .getActiveSpreadsheet();

  const sheets =
    ss.getSheets();

  let resultado =
    'DIAGNÓSTICO DA PLANILHA\n\n';

  sheets.forEach(function(sheet) {
    const nome =
      sheet.getName();

    const ultimaLinha =
      sheet.getLastRow();

    resultado +=
      'ABA: ' +
      nome +
      '\n';

    resultado +=
      'Última linha: ' +
      ultimaLinha +
      '\n';

    if (
      ultimaLinha >=
      CONFIG.FIRST_DATA_ROW
    ) {
      const quantidade =
        Math.min(
          ultimaLinha,
          10
        ) -
        CONFIG.FIRST_DATA_ROW +
        1;

      const dados =
        sheet
          .getRange(
            CONFIG.FIRST_DATA_ROW,
            CONFIG.ITEM_COL,
            quantidade,
            5
          )
          .getValues();

      dados.forEach(function(linha, i) {
        resultado +=
          'Linha ' +
          (
            i +
            CONFIG.FIRST_DATA_ROW
          ) +
          ': ITEM=' +
          linha[0] +
          ' | DATA=' +
          linha[1] +
          ' | VALOR=' +
          linha[2] +
          ' | STATUS=' +
          linha[3] +
          ' | ID=' +
          linha[4] +
          '\n';
      });
    }

    resultado +=
      '\n----------------------\n\n';
  });

  Logger.log(resultado);

  SpreadsheetApp
    .getUi()
    .alert(
      resultado.substring(
        0,
        5000
      )
    );
}

function mostrarCalendario() {
  const calendars =
    CalendarApp.getCalendarsByName(
      CONFIG.CALENDAR_NAME
    );

  if (
    calendars.length === 0
  ) {
    SpreadsheetApp
      .getUi()
      .alert(
        'Calendário não encontrado.'
      );

    return;
  }

  SpreadsheetApp
    .getUi()
    .alert(
      'Calendário encontrado:\n\n' +
      calendars[0].getName()
    );
}

3. O calendário é criado automaticamente

Uma das partes da implementação é a criação automática do calendário.

O script procura por:

💰 Vencimentos Financeiros

Se o calendário já existir, utiliza o existente. Caso contrário, cria um novo.

4. A primeira sincronização

Depois de autorizar o Apps Script a acessar o Google Sheets e o Google Calendar, basta utilizar:

📅 Calendário Financeiro → 🔄 Sincronizar agora

O script percorre as abas e verifica os registros.

Linhas vazias e a linha TOTAL são ignoradas.

Para cada despesa válida:

  • se não existir evento correspondente, cria;
  • se existir, atualiza.

5. Como fica o evento

O título segue este padrão:

💰 Vencimento — FACULDADE

O evento é criado como evento de dia inteiro.

A descrição contém o item, valor, status, origem na planilha e o identificador ID_CALENDAR.

Além disso, o script configura um lembrete para um dia antes do vencimento.

Google Calendar em visão semanal com eventos de vencimento sincronizados da planilha

6. E finalmente: o iPhone

Depois que o Google Calendar está sincronizado com o Calendário do iPhone, os vencimentos aparecem junto com os outros compromissos.

Widget do Calendário no iPhone listando vencimentos como Faculdade, Seguro e Condomínio
App Calendário do iPhone em visão mensal com os vencimentos sincronizados

Na prática, posso olhar o widget e já saber o que está próximo.

Também posso perguntar à Siri quais eventos tenho nos próximos dias.

Isso muda a experiência: não preciso abrir a planilha para descobrir o que vence nesta semana.

O que mudou no meu uso da planilha

A planilha não deixou de ser necessária. Ela simplesmente deixou de ser minha interface principal para consultar vencimentos.

  • Para cadastrar ou alterar algo: Google Sheets.
  • Para saber o que está vencendo: Calendário.
  • Para receber lembretes: iPhone.
  • Para consultar os próximos compromissos por voz: Siri.

E quando uma conta é paga?

A planilha continua sendo a fonte de verdade.

Quando uma conta é paga, altero:

STATUS: PAGO

e sincronizo novamente.

O evento correspondente é atualizado e passa a mostrar o novo status.

Por que usar um ID?

Cada linha recebe um identificador único com Utilities.getUuid().

Isso evita depender apenas do nome da despesa. Assim, mesmo que existam despesas com nomes semelhantes ou que um nome seja alterado, o script consegue relacionar o evento correto à linha correta.

E se aparecerem eventos duplicados?

O script possui a opção 🧹 Limpar duplicados.

Ela procura eventos que possuem o mesmo ID_CALENDAR e mantém apenas um.

O resultado

A arquitetura final é:

Google Sheets
      ↓
Apps Script
      ↓
Google Calendar
      ↓
iPhone
 ├── Widget
 ├── Alertas
 └── Siri

O interessante é que não foi necessário criar um aplicativo financeiro.

A planilha continua fazendo aquilo que faz bem. O Google Calendar cuida da agenda e o iPhone vira a camada de consulta e notificações.

No meu caso, isso resolveu exatamente o problema: parei de depender da planilha para lembrar das contas.

Agora uso uma ferramenta que já faz parte da minha rotina todos os dias: o calendário.

O que você precisa

  • Uma conta Google
  • Google Sheets
  • Google Calendar
  • iPhone, caso queira usar Calendário/Siri
  • Google Apps Script
  • A planilha que você já utiliza

Para continuar

Se você está montando outras automações no ecossistema Google, explore também a categoria Google Workspace e a área de Tecnologia. Dúvidas ou correções: envie feedback.

Achou um erro, uma lacuna ou quer sugerir o próximo artigo?

Enviar feedback