Desarrollando un Asistente de Rutas con IA: De la Idea al MVP

Ayer tocaba entrega de paquete de Amazon en casa (para variar 🤪), pero no estábamos, y lo primero que pensé fue: «vaya putada para el repartidor».

Y con esto me acordé de mi época de instalador, donde teníamos que hacer una ruta por Andalucía instalando ordenadores, tabletas … organizando la ruta el día anterior, llamando a clientes …

Esto hoy en día se puede mejorar 💯, ahora es cuando alguien con dos dedos de luces haría research y se encontraría mil soluciones en el mercado, pero qué sería de la vida si no nos pusiésemos a picar código del tirón !!

Vamos a atacar el problema en principio desde 2 puntos:

  • Mejora de la ruta física. Es decir, que si tengo que hacer 10 entregas, que se me ordenen de la forma más eficiente posible.
  • Minimizar las posibles entregas fallidas, preavisando a los clientes, para que nos puedan confirmar o rechazar si van a estar en casa.

Optimizando la ruta

Empezamos con el paso de optimizar ruta, y como no, vamos a usar la aplicación por excelencia de mapas, Google Maps, y su API.

Las 2 APIs que vamos a tener que activar son:

  • Maps JavaScript API
  • Directions API

Desde Google Cloud Console podrás crear tu proyecto, y habilitarle ambas APIs

Teniendo nuestra herramienta de optimización de rutas preparadas, es hora de empezar a picar código.

Vamos a empezar a simplificar, y la forma más sencilla de poder gestionar ubicaciones o paradas, es usando un custom post type en WordPress, al que llamaremos ‘delivery_address’, con atributos como el título y la dirección en formato texto en principio tenemos para esta versión.

Vamos a atacar la llamada a la API https://maps.googleapis.com/maps/api/js, lo que nos permitirá obtener el mapa con las ubicaciones ya ordenadas y optimizadas (¿sencillo no?, porque recuerdo la que tuve que liar en una práctica de la facultad a few years ago 👴🏻 para optimizar rutas en un espacio 2D)

<div id="map" style="height: 500px; width: 100%;"></div>
        <script>
            function initMap() {
                var map = new google.maps.Map(document.getElementById('map'), {
                    zoom: 6,
                    center: { lat: 40.4168, lng: -3.7038 } // Centro en Madrid por defecto
                });

                var directionsService = new google.maps.DirectionsService();
                var directionsRenderer = new google.maps.DirectionsRenderer();
                directionsRenderer.setMap(map);

                directionsService.route({
                    origin: "<?= urldecode($origin) ?>",
                    destination: "<?= urldecode($destination) ?>",
                    waypoints: [
                        <?php foreach ($addresses as $address) : ?>
                            { location: "<?= $address ?>", stopover: true },
                        <?php endforeach; ?>
                    ],
                    travelMode: google.maps.TravelMode.DRIVING,
                    optimizeWaypoints: true
                }, function(response, status) {
                    if (status === "OK") {
                        directionsRenderer.setDirections(response);

                        // mostramos más información
                        var route = response.routes[0];
                        var summaryPanel = document.getElementById('directions-panel');
                        summaryPanel.innerHTML = '';
                        for (var i = 0; i < route.legs.length; i++) {
                            var routeSegment = i + 1;
                            summaryPanel.innerHTML += '<b>Ruta ' + routeSegment + '</b><br>';
                            summaryPanel.innerHTML += route.legs[i].start_address + ' a ';
                            summaryPanel.innerHTML += route.legs[i].end_address + '<br>';
                            summaryPanel.innerHTML += route.legs[i].distance.text + '<br><br>';

                            // tiempos
                            var time = route.legs[i].duration.text;
                            summaryPanel.innerHTML += 'Tiempo estimado: ' + time + '<br><br>';
                            
                        }
                    } else {
                        alert("Error al calcular la ruta: " + status);
                    }
                });
            }
        </script>
        <script async defer src="https://maps.googleapis.com/maps/api/js?key=<?= $apiKey ?>&callback=initMap"></script>
        
        <div id="directions-panel"></div>
        

Obtendremos un mapita como este, además de más info como distancias y tiempos de recorridos.

Parece que una primera versión de la parte de ruta la tenemos ya 🥳 🥳 🥳

Preavisando a los clientes

Entre la información que nos da la API de Google al crear la ruta, están los tiempos de trayectos, por lo que si consideramos que vamos a salir ya (o quizás podamos indicar una hora y día de salida, así los clientes tendrán más tiempo para confirmar su precencia), y teniendo en cuenta que el tiempo de parada va a ser de 15min por ejemplo, tendremos las distintas horas de llegada estimadas.

// ...
for (var i = 0; i < route.legs.length; i++) {
                            // tiempos
                            var time = route.legs[i].duration.text;
                            times.push(time);  
                        }

                        var now = new Date();
                        var totalSeconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
                        var arrivalTimes = [];

                        times.forEach((time, index) => {

                            // Procesar el formato "Xh Y min"
                            const timeStr = time.replace(' min', '').split('h ');
                            const hours = parseInt(timeStr[0]) || 0;
                            const minutes = parseInt(timeStr[1]) || 0;
                            
                            // Añadir el tiempo de viaje más 15 minutos de parada
                            totalSeconds += (hours * 3600) + (minutes * 60) + (15 * 60);
                            
                            // Calcular la hora de llegada
                            const arrivalHours = Math.floor(totalSeconds / 3600) % 24; // Usamos módulo 24 para formato 24h
                            const arrivalMinutes = Math.floor((totalSeconds % 3600) / 60);
                            
                            // Formatear la hora de llegada
                            arrivalTimes.push(
                                `${arrivalHours.toString().padStart(2, '0')}:${arrivalMinutes.toString().padStart(2, '0')}`
                            );
                            
                            console.log(`Parada ${index + 1}: ${time} -> Llegada: ${arrivalTimes[arrivalTimes.length - 1]}`);
                        });
// ...

Parece que ya tenemos la data necesaria para esta segunda funcionalidad, por lo que podemos empezar a notificar a los clientes.

Atacar a la API de Whatsapp directamente quedará para futuros episodios, ya que Meta se ha lucido con ella, no como mi querido Telegram que es una maravilla a nivel de potencial de desarrollo … pero no todo el mundo tiene Telegram 😓

Una alternativa que parece bastante potente es Twilio, ya que no sólo es una capa por encima de Whatsapp facilitando las integraciones, sino que tiene SMS y otros sistemas, además de tener integraciones con Zapier, lo cual siempre es un aliciente a usarlo.

Y ya puestos a hacernos los modernos, podríamos tirar de los agentes de IA de ElevenLabs, que podrían llamar a los usuarios y confirmar o no su asistencia., aunque en este caso quizás sea venirnos muy arriba.

Pero en esta primera fase en la que estamos en exploración de mercado y validación de la idea, y dado que la cuesta de Enero llega casi hasta Junio, es hora de reducir costes y empezar a escuchar a los usuarios lo antes posible, así que atacaremos a los clientes con el medio de comunicación más tradicional y efectivo … el email 📩

Para ellocrearemos un endpoint donde enviar la data ( /wp-json/deliveria/v1/notify ) y que el servidor pueda enviar los emails

Con esa notificación podremos jugar, por ejemplo haciendo que el botón de «No voy a estar en casa» notifique en un endpoint, y así se pueda reajustar la ruta, o que sea un formulario indicando cuando voy a estar y así reajustar ruta, o un simple tel: y que nos llame directamente (ya que a veces aunque no esté alguien hay opciones de entrega a vecinos y demás). Aquí la imaginación al poder 🚀

Y con esto tendríamos una primera versión MVP base sobre la que ir iterando, validando con clientes, tanto los repartidores, como los usuarios finales. Permitiéndonos ahorrar tiempo ⏱️, dinero 💰 y frustraciones 😓