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.
GastroMóvil replaces polling with persistent WebSocket connections for every time-sensitive interaction. Restaurants receive incoming orders the moment they are placed, customers watch their order status change in real time, and delivery drivers stream GPS coordinates directly to the customer’s map view — all without a single HTTP refresh. This page documents each channel’s URL pattern, message contract, and lifecycle.
Infrastructure
GastroMóvil runs under Daphne, an ASGI server that supports both HTTP and WebSocket protocols from a single process. Django Channels sits on top of Daphne and provides the consumer framework, while the channel layer is configured as an InMemoryChannelLayer — meaning no Redis or external broker is required.
# gastromovil/settings.py
ASGI_APPLICATION = 'gastromovil.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
InMemoryChannelLayer stores groups in process memory. If you run more than one Daphne worker process, each process has its own isolated layer and cross-process broadcasts will not work. For multi-process deployments, switch the backend to channels_redis.core.RedisChannelLayer.
The ASGI entry point combines HTTP and WebSocket routes via ProtocolTypeRouter, with AuthMiddlewareStack wrapping all WebSocket connections so that scope["user"] is available inside every consumer:
# gastromovil/asgi.py
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import pedidos.routing
import repartidores.routing
application = ProtocolTypeRouter({
'http': get_asgi_application(),
'websocket': AuthMiddlewareStack(
URLRouter(
pedidos.routing.websocket_urlpatterns +
repartidores.routing.websocket_urlpatterns
)
),
})
The full list of registered WebSocket URL patterns (in match order) is:
| Pattern | Consumer | Defined in |
|---|
ws/pedidos/<tipo>/<sala_id>/ | PedidoConsumer | pedidos/routing.py |
ws/repartidor/<repartidor_id>/ | RepartidorConsumer | pedidos/routing.py |
ws/ubicacion/<repartidor_id>/ | UbicacionConsumer | repartidores/routing.py |
repartidores/routing.py also declares a ws/repartidor/<repartidor_id>/ pattern that maps to RepartidorConsumer, but because pedidos.routing.websocket_urlpatterns is prepended first in asgi.py, the pedidos/routing.py entry matches all ws/repartidor/ connections. The entry in repartidores/routing.py is never reached.
Channel 1 — Order Notifications (PedidoConsumer)
URL pattern: ws://<host>/ws/pedidos/<tipo>/<sala_id>/
PedidoConsumer is a general-purpose notification fan-out channel. Any backend signal can push a message to a named group, and every browser tab connected to that group receives it immediately. The group name is constructed by joining the two URL parameters: {tipo}_{sala_id}.
URL parameters
| Parameter | Type | Description |
|---|
tipo | string (\w+) | Category of the listener — typically restaurante or cliente |
sala_id | string (\w+) | ID of the entity — restaurant ID or client user ID |
Examples:
ws://localhost:8000/ws/pedidos/restaurante/7/ — restaurant with ID 7 listens for new orders
ws://localhost:8000/ws/pedidos/cliente/42/ — customer with user ID 42 listens for status changes on their orders
Lifecycle
# pedidos/consumers.py (simplified)
async def connect(self):
self.group_name = f"{self.tipo}_{self.sala_id}"
await self.channel_layer.group_add(self.group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.group_name, self.channel_name)
On connect the consumer joins its group and accepts the handshake. On disconnect it removes itself from the group — no messages are lost for other connected members.
Events handled by PedidoConsumer
The consumer does not receive text frames from the browser; it is receive-only from the client’s perspective. It handles three server-side channel layer events:
Channel layer type | Handler method | Forwarded to client? |
|---|
notificacion_pedido | notificacion_pedido() | ✅ forwards event['data'] |
nuevo_pedido | nuevo_pedido() | ✅ forwards entire event dict |
pedido_disponible | pedido_disponible() | ✅ forwards entire event dict |
notificacion_pedido extracts the data sub-key before sending, so the wire payload is the object inside data. The other two handlers serialize the whole channel layer event, which means the type key travels to the browser as well.
Channel 2 — Driver Location (UbicacionConsumer)
URL pattern: ws://<host>/ws/ubicacion/<repartidor_id>/
Both the delivery driver and the tracking customer connect to this same URL. The driver sends coordinate frames; the server persists each update to UbicacionRepartidor and fans it out to every member of the group (including back to the driver, and to any customer watching).
Group name: ubicacion_{repartidor_id}
Sending a location update (driver → server)
{"latitud": 2.196, "longitud": -75.637}
The server writes the coordinates to the database with update_or_create (one row per driver) and then broadcasts to the group.
Receiving a location update (server → client)
{"tipo": "ubicacion", "latitud": 2.196, "longitud": -75.637}
Signalling delivery completion (driver → server)
{"tipo": "pedido_entregado"}
When the server receives this frame it skips the database write and broadcasts a delivery-complete notification to the group:
{"tipo": "pedido_entregado", "latitud": null, "longitud": null}
Clients receiving "tipo": "pedido_entregado" should hide the live map and show a delivery confirmation UI. The latitud and longitud values will be null for this event type and should be ignored.
Database persistence
Coordinates are written asynchronously using database_sync_to_async so the async consumer is never blocked by a synchronous ORM call:
# repartidores/consumers.py
@database_sync_to_async
def guardar_ubicacion(self, latitud, longitud):
from usuarios.models import Usuario
try:
usuario = Usuario.objects.get(id=self.repartidor_id)
UbicacionRepartidor.objects.update_or_create(
repartidor=usuario,
defaults={'latitud': latitud, 'longitud': longitud}
)
except Usuario.DoesNotExist:
pass
Channel 3 — Driver Notifications (RepartidorConsumer)
URL pattern: ws://<host>/ws/repartidor/<repartidor_id>/
RepartidorConsumer is a private push channel for each delivery driver. On connect it joins two groups: its own private group repartidor_{repartidor_id} and, if the driver’s Repartidor.estado is 'disponible', the shared repartidores_disponibles broadcast group.
# repartidores/consumers.py (simplified)
async def connect(self):
self.group_name = f'repartidor_{self.repartidor_id}'
await self.channel_layer.group_add(self.group_name, self.channel_name)
if await self.esta_disponible():
await self.channel_layer.group_add("repartidores_disponibles", self.channel_name)
await self.accept()
Events handled by RepartidorConsumer
Channel layer type | Handler method | Notes |
|---|
notificacion_pedido | notificacion_pedido() | Forwards event['data'] — used for direct assignment notifications |
pedido_disponible | pedido_disponible() | Forwards entire event — broadcast to all available drivers when a restaurant confirms an order |
A driver who is not in 'disponible' state at connection time will not receive pedido_disponible broadcasts because it never joins repartidores_disponibles. The availability check runs once at connect time; reconnecting is required if the driver’s status changes.
JavaScript Client Examples
Connect a restaurant dashboard to receive live order alerts and a customer view to track their order status.// Restaurant dashboard — listens for new orders
const restauranteId = 7;
const wsRestaurante = new WebSocket(
`ws://${location.host}/ws/pedidos/restaurante/${restauranteId}/`
);
wsRestaurante.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.tipo === 'nuevo_pedido') {
console.log(`New order #${data.pedido_id} from ${data.cliente}`);
showOrderAlert(data);
}
if (data.tipo === 'notificacion_pedido') {
console.log(data.mensaje);
}
};
wsRestaurante.onclose = () => {
console.warn('Restaurant WS closed — reconnecting...');
};
// Customer order tracking
const clienteId = 42;
const wsCliente = new WebSocket(
`ws://${location.host}/ws/pedidos/cliente/${clienteId}/`
);
wsCliente.onmessage = (event) => {
const data = JSON.parse(event.data);
updateOrderStatusBadge(data.estado);
};
Subscribe to a driver’s location stream from the customer’s tracking page.const repartidorId = 15;
const wsUbicacion = new WebSocket(
`ws://${location.host}/ws/ubicacion/${repartidorId}/`
);
wsUbicacion.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.tipo === 'ubicacion') {
// Update the map marker
map.setCenter({ lat: data.latitud, lng: data.longitud });
driverMarker.setPosition({ lat: data.latitud, lng: data.longitud });
}
if (data.tipo === 'pedido_entregado') {
wsUbicacion.close();
showDeliveryConfirmation();
}
};
Send GPS coordinates from the driver’s mobile browser and signal when delivery is complete.const repartidorId = 15;
const wsDriver = new WebSocket(
`ws://${location.host}/ws/ubicacion/${repartidorId}/`
);
wsDriver.onopen = () => {
// Start watching GPS position
navigator.geolocation.watchPosition((position) => {
wsDriver.send(JSON.stringify({
latitud: position.coords.latitude,
longitud: position.coords.longitude,
}));
}, null, { enableHighAccuracy: true, maximumAge: 3000 });
};
function confirmDelivery() {
wsDriver.send(JSON.stringify({ tipo: 'pedido_entregado' }));
}
A driver connects to receive new delivery assignments pushed by the server.const repartidorId = 15;
const wsRepartidor = new WebSocket(
`ws://${location.host}/ws/repartidor/${repartidorId}/`
);
wsRepartidor.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.tipo === 'pedido_disponible') {
showAvailableOrderBanner(data);
}
if (data.tipo === 'pedido_listo') {
showAssignmentNotification(data);
}
};
Event Reference
All payloads are JSON objects sent over the WebSocket text frame.
Events received by restaurant (restaurante_{id} group)
nuevo_pedido
notificacion_pedido
Sent when a customer places a new order. This message is delivered via the notificacion_pedido channel layer event, so the wire payload is the contents of event['data'] — it contains "tipo" (not "type").{
"tipo": "nuevo_pedido",
"mensaje": "🍽️ ¡Nuevo pedido #101!",
"pedido_id": 101,
"cliente": "Ana García",
"total": "25.50",
"notas": "Sin cebolla, por favor"
}
| Field | Type | Description |
|---|
tipo | string | Event category — always "nuevo_pedido" |
pedido_id | integer | Primary key of the new order |
cliente | string | Display name of the customer |
total | string | Order total as a decimal string |
notas | string | Customer delivery notes |
Generic notification forwarded from event['data'].{
"tipo": "notificacion_pedido",
"mensaje": "El pedido #101 ha sido actualizado",
"pedido_id": 101
}
| Field | Type | Description |
|---|
tipo | string | Event category |
mensaje | string | Human-readable notification text |
pedido_id | integer | Related order primary key |
Events received by customer (cliente_{id} group)
Sent whenever the order’s status is updated. The estado value is always one of the valid Pedido.ESTADOS codes: pendiente, aceptado, preparando, enviado, entregado, or cancelado.{
"tipo": "cambio_estado",
"mensaje": "🛵 ¡Tu pedido está en camino!",
"pedido_id": 101,
"estado": "enviado"
}
| Field | Type | Description |
|---|
pedido_id | integer | Order primary key |
estado | string | New order status — one of pendiente, aceptado, preparando, enviado, entregado, cancelado |
mensaje | string | Human-readable description of the change |
Events received by drivers (repartidor_{id} group)
pedido_disponible
pedido_listo
Sent to a specific available driver’s private group (repartidor_{repartidor.usuario.id}) when the restaurant moves an order to enviado via the API endpoint. The pedido_disponible channel layer type is forwarded as the entire event, so type and tipo both appear in the wire payload.{
"type": "pedido_disponible",
"tipo": "pedido_disponible",
"pedido_id": 101,
"restaurante": "Pizzería Roma",
"direccion": "Carrera 8 #20-10, Neiva"
}
| Field | Type | Description |
|---|
pedido_id | integer | Order to be claimed |
restaurante | string | Origin restaurant name |
direccion | string | Customer delivery address |
Sent to a driver’s private group when the restaurant marks an order as ready for pickup via the web UI. Delivered via the notificacion_pedido channel layer type, so the wire payload is the contents of event['data'].{
"tipo": "pedido_listo",
"mensaje": "🛵 ¡Pedido #101 listo para recoger!",
"pedido_id": 101,
"restaurante": "Pizzería Roma",
"direccion_restaurante": "Calle 5 #12-34, Neiva",
"direccion_entrega": "Carrera 8 #20-10, Neiva"
}
| Field | Type | Description |
|---|
restaurante | string | Restaurant display name |
direccion_restaurante | string | Pickup address at the restaurant |
direccion_entrega | string | Customer delivery address |
Events on the location channel (ubicacion_{repartidor_id} group)
Event tipo | Direction | latitud | longitud | Notes |
|---|
ubicacion | server → client | decimal | decimal | Broadcast after every driver coordinate update |
pedido_entregado | server → client | null | null | Driver has confirmed delivery; close the map |