;(function() { window.createMeasureObserver = (measureName) => { var markPrefix = `_uol-measure-${measureName}-${new Date().getTime()}`; performance.mark(`${markPrefix}-start`); return { end: function() { performance.mark(`${markPrefix}-end`); performance.measure(`uol-measure-${measureName}`, `${markPrefix}-start`, `${markPrefix}-end`); performance.clearMarks(`${markPrefix}-start`); performance.clearMarks(`${markPrefix}-end`); } } }; /** * Gerenciador de eventos */ window.gevent = { stack: [], RUN_ONCE: true, on: function(name, callback, once) { this.stack.push([name, callback, !!once]); }, emit: function(name, args) { for (var i = this.stack.length, item; i--;) { item = this.stack[i]; if (item[0] === name) { item[1](args); if (item[2]) { this.stack.splice(i, 1); } } } } }; var runningSearch = false; var hadAnEvent = true; var elementsToWatch = window.elementsToWatch = new Map(); var innerHeight = window.innerHeight; // timestamp da última rodada do requestAnimationFrame // É usado para limitar a procura por elementos visíveis. var lastAnimationTS = 0; // verifica se elemento está no viewport do usuário var isElementInViewport = function(el) { var rect = el.getBoundingClientRect(); var clientHeight = window.innerHeight || document.documentElement.clientHeight; // renderizando antes, evitando troca de conteúdo visível no chartbeat-related-content if(el.className.includes('related-content-front')) return true; // garante que usa ao mínimo 280px de margem para fazer o lazyload var margin = clientHeight + Math.max(280, clientHeight * 0.2); // se a base do componente está acima da altura da tela do usuário, está oculto if(rect.bottom < 0 && rect.bottom > margin * -1) { return false; } // se o topo do elemento está abaixo da altura da tela do usuário, está oculto if(rect.top > margin) { return false; } // se a posição do topo é negativa, verifica se a altura dele ainda // compensa o que já foi scrollado if(rect.top < 0 && rect.height + rect.top < 0) { return false; } return true; }; var asynxNextFreeTime = () => { return new Promise((resolve) => { if(window.requestIdleCallback) { window.requestIdleCallback(resolve, { timeout: 5000, }); } else { window.requestAnimationFrame(resolve); } }); }; var asyncValidateIfElIsInViewPort = function(promise, el) { return promise.then(() => { if(el) { if(isElementInViewport(el) == true) { const cb = elementsToWatch.get(el); // remove da lista para não ser disparado novamente elementsToWatch.delete(el); cb(); } } }).then(asynxNextFreeTime); }; // inicia o fluxo de procura de elementos procurados var look = function() { if(window.requestIdleCallback) { window.requestIdleCallback(findByVisibleElements, { timeout: 5000, }); } else { window.requestAnimationFrame(findByVisibleElements); } }; var findByVisibleElements = function(ts) { var elapsedSinceLast = ts - lastAnimationTS; // se não teve nenhum evento que possa alterar a página if(hadAnEvent == false) { return look(); } if(elementsToWatch.size == 0) { return look(); } if(runningSearch == true) { return look(); } // procura por elementos visíveis apenas 5x/seg if(elapsedSinceLast < 1000/5) { return look(); } // atualiza o último ts lastAnimationTS = ts; // reseta status de scroll para não entrar novamente aqui hadAnEvent = false; // indica que está rodando a procura por elementos no viewport runningSearch = true; const done = Array.from(elementsToWatch.keys()).reduce(asyncValidateIfElIsInViewPort, Promise.resolve()); // obtém todos os elementos que podem ter view contabilizados //elementsToWatch.forEach(function(cb, el) { // if(isElementInViewport(el) == true) { // // remove da lista para não ser disparado novamente // elementsToWatch.delete(el); // cb(el); // } //}); done.then(function() { runningSearch = false; }); // reinicia o fluxo de procura look(); }; /** * Quando o elemento `el` entrar no viewport (-20%), cb será disparado. */ window.lazyload = function(el, cb) { if(el.nodeType != Node.ELEMENT_NODE) { throw new Error("element parameter should be a Element Node"); } if(typeof cb !== 'function') { throw new Error("callback parameter should be a Function"); } elementsToWatch.set(el, cb); } var setEvent = function() { hadAnEvent = true; }; window.addEventListener('scroll', setEvent, { capture: true, ive: true }); window.addEventListener('click', setEvent, { ive: true }); window.addEventListener('resize', setEvent, { ive: true }); window.addEventListener('load', setEvent, { once: true, ive: true }); window.addEventListener('DOMContentLoaded', setEvent, { once: true, ive: true }); window.gevent.on('allJSLoadedAndCreated', setEvent, window.gevent.RUN_ONCE); // inicia a validação look(); })();
  • AssineUOL
Topo

Chama que a comida vem: a tecnologia por trás dos apps de delivery

Rodrigo Lara

Colaboração para Tilt

08/10/2020 04h00

Sexta-feira à noite, bate aquela vontade de pedir uma pizza. Há alguns anos, o roteiro seria ligar para sua pizzaria favorita, fazer o pedido e esperar a entrega. Mas, essa rotina mudou. Hoje, o mais comum é abrirmos um app de delivery no smartphone para comprar um prato ou lanche. Mas você sabe como esses aplicativos funcionam?

O processo é realmente muito simples com a tecnologia que temos hoje. Em termos técnicos, tudo que o app de delivery faz é agir como um intermediário entre o cliente e o restaurante.

Cada aplicativo tem suas nuances, mas de forma geral, os restaurantes podem usar os serviços de duas formas diferentes. Uma delas, mais usada por estabelecimentos pequenos, consiste em um app no celular que emite notificações sempre que há algum novo pedido.

Já restaurantes maiores usam sistemas próprios de gestão de fila de pedidos, e os serviços de delivery podem ser integrados a eles.

Na hora da entrega, há pequenas variações. Em alguns aplicativos, os restaurantes podem optar por planos mais básicos. Neles, a entrega fica a cargo do próprio estabelecimento. Em outros casos, o app de delivery também intermedia o contato com entregadores cadastrados, que ficam responsáveis por arem nos restaurantes, retirarem os pedidos e levarem até os clientes.

Quem define as formas de pagamento aceitas?

Aqui, há variações. Tomando como exemplo os dois principais aplicativos de delivery disponíveis no país, enquanto no iFood tudo depende do plano que os restaurantes "assinam", no Rappi a escolha é unicamente do restaurante.

Ainda no caso do iFood, quando o restaurante é o responsável por fazer a entrega, a escolha das formas de pagamento ficam a cargo do estabelecimento, enquanto no plano no qual a entrega fica a cargo do aplicativo, é o iFood que gerencia essas opções.

Quem atualiza os cardápios dos restaurantes?

A definição sobre quais produtos serão vendidos e o gerenciamento de cardápio fica totalmente a cargo dos estabelecimentos.

Qual é o processo para que restaurantes possam vender seus produtos nesses aplicativos?

Aqui, há duas vias. Os restaurantes podem procurar diretamente os aplicativos e solicitarem a inscrição, assim como os aplicativos também têm equipes para a captação de novos estabelecimentos. De qualquer maneira, os locais novos am por uma análise para saber se estão regularizados antes de serem aceitos nas plataformas.

O que faz o preço da entrega variar?

Das empresas consultadas, o Rappi afirma que o frete varia "de acordo com o clima, dia da semana, horário, zona da entrega, distância percorrida e complexidade do pedido". Os restaurantes têm a liberdade de fazer promoções com frete grátis, mas o valor é abatido pelo estabelecimento. No caso do iFood, a oferta de frete grátis leva em conta "a negociação de cada um dos restaurantes" com a plataforma.

Quem define quando há promoções?

Aqui também é algo que varia de acordo com a plataforma. No caso do iFood, isso também depende da negociação feita com o restaurante, mas a plataforma afirma que faz ações de subsídio de vouchers. Já no caso do Rappi, esses descontos são definidos pelos próprios restaurantes.

Fontes:
Felipe Crull, diretor comercial do iFood
Diego Bonna, diretor de produto do Rappi

Toda quinta, Tilt mostra que há tecnologia por trás de (quase) tudo que nos rodeia. Tem dúvida de algum objeto? Mande para a gente que vamos investigar.