Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CRISTIANCAMACH34/Zippi/llms.txt

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

The order is the operational heart of Zippi. Its lifecycle is modeled as an explicit finite state machine: every transition has a defined origin state, a set of roles authorized to execute it, and a scope check confirming the resource belongs to the actor. Any transition not in the allowed list is rejected with a 409 Conflict. Every transition that does occur is audited atomically in the same database transaction.

Happy Path: 13 States

The happy path is the successful delivery flow from order creation to closure.
Nuevo
  → Pendiente aceptación
  → Aceptado
  → Esperando preparación
  → Preparando
  → Empacado
  → Esperando domiciliario
  → Domiciliario asignado
  → Recogido
  → En camino
  → Llegó
  → Entregado
  → Cerrado
#StateMeaning
1NuevoOrder just created via app or WhatsApp bot
2Pendiente aceptaciónAwaiting acceptance by the business
3AceptadoBusiness confirmed the order
4Esperando preparaciónOrder queued for kitchen preparation
5PreparandoKitchen is actively preparing the order
6EmpacadoOrder packed and ready for pickup
7Esperando domiciliarioAwaiting courier assignment
8Domiciliario asignadoCourier assigned; waiting for pickup
9RecogidoCourier has picked up the order
10En caminoCourier is en route to the customer
11LlegóCourier arrived at the delivery address
12EntregadoOrder delivered to the customer
13CerradoOrder closed by the system or business

Exception States

Exception states handle everything that can go wrong. Some are terminal (no further transitions to operational states); some allow re-entry into the flow via Reprogramado.
StateMeaningReachable from
CanceladoCancelled before deliveryAlmost any state prior to Entregado
DevueltoCustomer returned the orderEntregado
FallidoDelivery could not be completedEn camino, Llegó
Cliente ausenteCustomer not present at addressLlegó
No localizadoAddress or customer not foundEn camino
ReprogramadoDelivery rescheduled for retryCliente ausente, No localizado
ReembolsadoPayment refundedEntregado, Cancelado
Terminal states (no further operational transitions): Cerrado, Reembolsado, Devuelto, Fallido.

Complete Transition Table

This table is the implementation source of truth. Any transition not listed here must be rejected with 409 Conflict.
From stateTo stateAuthorized role(s)Permission
NuevoPendiente aceptaciónsistema / canal de entrada
NuevoCanceladonegocio, soporte, city/opsorders.cancel
Pendiente aceptaciónAceptadobusiness_admin, business_branch_admin, business_ownerorders.accept
Pendiente aceptaciónCanceladonegocio, soporte, city/opsorders.cancel
AceptadoEsperando preparaciónsistema / negocio
Esperando preparaciónPreparandokitchen_stafforders.prepare
PreparandoEmpacadokitchen_stafforders.pack
PreparandoCanceladonegocio, soporte, city/opsorders.cancel
EmpacadoEsperando domiciliariosistema / negocio
Esperando domiciliarioDomiciliario asignadooperations_admin, city_admin, dispatchorders.assign_courier
Domiciliario asignadoRecogidodelivery_driverorders.pickup
RecogidoEn caminodelivery_driverorders.transit
En caminoLlegódelivery_driverorders.transit
En caminoFallidodelivery_driver, soporteorders.mark_failed
En caminoNo localizadodelivery_driverorders.mark_failed
LlegóEntregadodelivery_driverorders.deliver
LlegóFallidodelivery_driver, soporteorders.mark_failed
LlegóCliente ausentedelivery_driverorders.mark_absent
Cliente ausenteReprogramadosoporte, operations_adminorders.reschedule
No localizadoReprogramadosoporte, operations_adminorders.reschedule
EntregadoCerradosistema / negocioorders.close
EntregadoDevueltosoporte, operations_adminorders.return
EntregadoReembolsadofinance_admin, cashier, business_owner (with payment permission)orders.refund
CanceladoReembolsadofinance_admin, cashier, business_owner (with payment permission)orders.refund
(most states before Entregado)Canceladonegocio, soporte, city/opsorders.cancel

State Machine Rules

The following rules apply to every transition without exception:
  1. Origin state validation — the order must be in the expected from_state. Transitions to an already-occupied state or from a terminal state are always rejected.
  2. Role authorization — the authenticated user’s role must appear in the authorized_roles for that (from_state, to_state) pair.
  3. Scope membership — the order must belong to the user’s scope (business_id, branch_id, or user_id for drivers). A valid order ID in another tenant’s data returns 404, not 403.
  4. Atomic audit — the state change and its audit record (actor, timestamp, previous state, new state, reason) are written in the same database transaction. No transition occurs without an audit trail.
  5. Idempotency — retrying an already-applied transition is a no-op, not a duplicate error.
  6. Concurrency — if two actors attempt to transition the same order simultaneously, optimistic concurrency control ensures the second actor receives a 409 if the state has already changed.
Every single state transition is audited — including automatic system transitions triggered by timeouts or webhooks. For system-driven transitions, the audit actor is set to "sistema". There are no silent state changes.

Reference Implementation

The state machine is implemented as two dictionaries: a whitelist of valid (origin, destination) pairs, and a mapping of those pairs to the set of authorized roles.
VALID_TRANSITIONS: dict[EstadoPedido, set[EstadoPedido]] = {
    EstadoPedido.NUEVO: {EstadoPedido.PENDIENTE_ACEPTACION, EstadoPedido.CANCELADO},
    EstadoPedido.PENDIENTE_ACEPTACION: {EstadoPedido.ACEPTADO, EstadoPedido.CANCELADO},
    EstadoPedido.PREPARANDO: {EstadoPedido.EMPACADO, EstadoPedido.CANCELADO},
    EstadoPedido.LLEGO: {EstadoPedido.ENTREGADO, EstadoPedido.CLIENTE_AUSENTE},
    EstadoPedido.ENTREGADO: {
        EstadoPedido.CERRADO,
        EstadoPedido.DEVUELTO,
        EstadoPedido.REEMBOLSADO,
    },
    # ... all transitions
}

ROLE_TRANSITIONS: dict[tuple[EstadoPedido, EstadoPedido], set[str]] = {
    (EstadoPedido.PREPARANDO, EstadoPedido.EMPACADO): {
        "kitchen_staff", "business_branch_admin", "business_admin", "business_owner"
    },
    (EstadoPedido.LLEGO, EstadoPedido.ENTREGADO): {"delivery_driver"},
    # ...
}

def transition(order, destino: EstadoPedido, scope):
    if destino not in VALID_TRANSITIONS.get(order.estado, set()):
        raise ConflictError(f"Transición inválida: {order.estado}{destino}")
    allowed = ROLE_TRANSITIONS.get((order.estado, destino), set())
    if scope.role not in allowed:
        raise PermissionDeniedError("Tu rol no puede ejecutar esta transición")
    if not scope.owns(order):           # scope membership check
        raise PermissionDeniedError("Pedido fuera de tu alcance")
    # state change + audit in the same Unit of Work
Prohibited transition examples: Nuevo → Entregado, Preparando → Entregado, Cerrado → any operational state, Entregado → Preparando. The state machine uses a whitelist — everything not explicitly listed is rejected with 409. Never implement a fallthrough or default “allow” path.

State Diagram

Nuevo ─── Pendiente ─── Aceptado ─── Esperando prep ─── Preparando ─── Empacado

                    Esperando domi ─── Domi asignado ─── Recogido ──────────┘

                       En camino ──────── Llegó ──────── Entregado ──── Cerrado
                           │                 │                │
                    No localizado      Cliente ausente    Devuelto / Reembolsado
                           │                 │
                       Reprogramado ─────────┘

(Cancelado: reachable from most states before Entregado)
(Fallido: reachable from En camino and Llegó)

SLA and Timeout Behavior

Some states have operational time expectations that the system monitors:
StateSLA concernSystem behavior on breach
Pendiente aceptaciónBusiness not acceptingAlert sent to ops; auto-cancel if configured
PreparandoKitchen exceeding timerAlert on kitchen screen
Esperando domiciliarioNo assignment after X minutesEscalation to dispatch
En camino → EntregadoLogistics SLATracking alert opened
Timeouts trigger events and alerts — they do not silently change order state. Any automatic state change caused by a timeout is a valid transition that must be audited with actor = "sistema" and a reason code.

Required Test Cases

- Happy path transition by the correct role → 200, state changed, audited
- Prohibited transition (Nuevo → Entregado) → 409
- Transition by unauthorized role (customer attempts advance) → 403
- Transition on order belonging to another tenant → 403 / 404
- Retry of already-applied transition → idempotent (no duplicate effect)
- Refund amount exceeding order total → 422
- Concurrent actors on same order → second actor receives 409

Build docs developers (and LLMs) love