Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lffiesco-svg/gastromovil/llms.txt

Use this file to discover all available pages before exploring further.

Once a restaurant marks an order as enviado, GastroMóvil’s delivery system takes over. An available driver receives an instant WebSocket notification, accepts the pickup, and then begins sharing their GPS location in real time. Customers watch the driver’s position update live on a Google Maps embed at the tracking page — all without polling.

Data Models

Repartidor

Extends the core Usuario model with delivery-specific fields through a OneToOneField:
class Repartidor(models.Model):
    ESTADOS = [
        ('disponible', 'Disponible'),
        ('ocupado',    'Ocupado'),
        ('inactivo',   'Inactivo'),
    ]
    usuario               = models.OneToOneField(Usuario, on_delete=models.CASCADE,
                                                  related_name='repartidor')
    estado                = models.CharField(max_length=20, choices=ESTADOS,
                                              default='inactivo')
    calificacion_promedio = models.DecimalField(max_digits=3, decimal_places=2, default=0.0)
    activo                = models.BooleanField(default=True)

UbicacionRepartidor

A single-row record per driver that stores the most recent GPS coordinates. Every time the driver’s browser sends a location update the row is overwritten via update_or_create:
class UbicacionRepartidor(models.Model):
    repartidor = models.OneToOneField(Usuario, on_delete=models.CASCADE,
                                      related_name='ubicacion')
    latitud    = models.DecimalField(max_digits=9, decimal_places=6)
    longitud   = models.DecimalField(max_digits=9, decimal_places=6)
    actualizado = models.DateTimeField(auto_now=True)
auto_now=True on actualizado means the timestamp is refreshed on every save, giving a reliable “last seen” marker for each driver.

Driver Panel

The driver dashboard at /panel-repartidor/ is divided into three sections:

Pending Deliveries

Orders with estado='enviado' that are waiting for a driver to accept.

In Transit

Orders with estado='en_camino' assigned to the logged-in driver.

Completed Today

Orders with estado='entregado' for the current date assigned to this driver.

How Location Sharing Works

The live tracking system uses two separate WebSocket consumers that share the same channel group.
1

Driver Connects

The driver’s browser opens a WebSocket connection to:
ws://<host>/ws/ubicacion/<repartidor_id>/
UbicacionConsumer.connect() adds the connection to the group ubicacion_{repartidor_id}.
2

Driver Sends Coordinates

As the driver moves, the browser sends JSON messages at regular intervals:
{ "latitud": 2.196541, "longitud": -75.629834 }
The consumer’s receive() method calls guardar_ubicacion() (an async database write using database_sync_to_async) and then broadcasts the update to the group.
3

Customer Receives Updates

The customer connects to the same WebSocket group from the tracking page. Whenever the driver’s browser sends a new location, ubicacion_actualizada fires on all group members:
async def ubicacion_actualizada(self, event):
    await self.send(text_data=json.dumps({
        'tipo':     event.get('tipo', 'ubicacion'),
        'latitud':  event.get('latitud'),
        'longitud': event.get('longitud'),
    }))
The tracking page at /repartidores/seguimiento/<repartidor_id>/ uses the Google Maps JavaScript API to move a marker on the map each time a location message arrives.
4

Driver Marks Order Delivered

When the driver completes the delivery, the browser sends a special message type:
{ "tipo": "pedido_entregado" }
The consumer rebroadcasts this to the group so the customer’s page can react (e.g. show a completion screen) without needing to poll the server.

UbicacionConsumer — Full Flow

class UbicacionConsumer(AsyncWebsocketConsumer):

    async def connect(self):
        self.repartidor_id = self.scope['url_route']['kwargs']['repartidor_id']
        self.group_name    = f'ubicacion_{self.repartidor_id}'
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()

    async def receive(self, text_data):
        data = json.loads(text_data)

        if data.get('tipo') == 'pedido_entregado':
            await self.channel_layer.group_send(
                self.group_name,
                {'type': 'ubicacion_actualizada', 'tipo': 'pedido_entregado'}
            )
            return

        latitud  = data.get('latitud')
        longitud = data.get('longitud')
        await self.guardar_ubicacion(latitud, longitud)
        await self.channel_layer.group_send(
            self.group_name,
            {'type': 'ubicacion_actualizada', 'latitud': latitud, 'longitud': longitud}
        )

    @database_sync_to_async
    def guardar_ubicacion(self, latitud, longitud):
        usuario = Usuario.objects.get(id=self.repartidor_id)
        UbicacionRepartidor.objects.update_or_create(
            repartidor=usuario,
            defaults={'latitud': latitud, 'longitud': longitud}
        )

RepartidorConsumer — Order Assignment Channel

Each driver also maintains a second, private WebSocket connection for receiving order assignment events:
class RepartidorConsumer(AsyncWebsocketConsumer):

    async def connect(self):
        self.repartidor_id = self.scope['url_route']['kwargs']['repartidor_id']
        self.group_name    = f'repartidor_{self.repartidor_id}'

        await self.channel_layer.group_add(self.group_name, self.channel_name)

        # Join the shared pool only if currently available
        if await self.esta_disponible():
            await self.channel_layer.group_add("repartidores_disponibles", self.channel_name)

        await self.accept()
A driver joins the repartidores_disponibles group automatically at connection time if their Repartidor.estado is 'disponible'. This group is also used by the post_save signal on Pedido when the restaurant accepts an order.

Automatic Order Assignment

When the restaurant accepts an order (sets estado to 'aceptado'), a post_save signal on Pedido broadcasts a pedido_disponible event to the shared repartidores_disponibles channel group. Every driver whose RepartidorConsumer joined that group at connection time receives the notification:
@receiver(post_save, sender=Pedido)
def notificar_cambio_pedido(sender, instance, created, **kwargs):
    ...
    elif instance.estado == 'aceptado':
        async_to_sync(channel_layer.group_send)(
            "repartidores_disponibles",
            {
                "type":        "pedido_disponible",
                "pedido_id":   instance.id,
                "restaurante": instance.restaurante.nombre,
                "direccion":   str(instance.direccion_entrega),
            }
        )

Driver Action Endpoints

MethodEndpointDescription
POST/pedidos/<pk>/aceptar-entrega/Accept a delivery — sets order estado to en_camino and assigns repartidor to the logged-in user
POST/pedidos/<pk>/marcar-entregado/Mark the order delivered — sets estado to entregado
Both endpoints validate that the order is in the correct state and that the requesting user matches the assigned driver before making any changes.

Customer Tracking Page

GET /repartidores/seguimiento/<repartidor_id>/ renders the tracking view. The page template receives the repartidor_id as context and uses it to open the ubicacion_{repartidor_id} WebSocket channel. Incoming latitude/longitude messages are fed into the Google Maps JavaScript API to animate a marker representing the driver’s current position.

Build docs developers (and LLMs) love