Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ProcesosAgilesUMSS/sansistore/llms.txt

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

Once an order is packaged and marked LISTO, the seller’s next responsibility is to assign a courier (mensajero) and monitor the delivery to completion. SansiStore models each delivery as a separate Firestore document in the deliveries collection, linked to the originating order. The seller portal provides dedicated pages for incidents, failure reasons, and decisions on non-delivered orders.

Delivery Assignment Flow

1

Order Reaches LISTO Status

After markOrderReady updates the order to LISTO, it appears on /seller/ready-orders. The ReadyOrdersPanel component lists all such orders filtered by sellerId.
2

Seller Opens the Assign Messenger Modal

Clicking an order card opens AssignMessengerModal. The modal calls useGetMessengers to load all registered couriers from Firestore and shows their availability status (isAvailable: boolean).
// src/features/seller/types.ts
export interface Messenger {
  uid: string;
  displayName: string;
  institutionalId: string;
  isAvailable: boolean;
}
If all couriers are busy (isAvailable: false), the modal displays a warning: “Todos los mensajeros están ocupados en este momento.” The confirm button remains disabled until an available courier is selected.
3

Confirm Courier Assignment

The seller selects an available courier and clicks Confirmar asignación. This triggers useAssignOrdersToDelivery.assingToDelivery(orderId, courierId), which calls assignCourierToDelivery in a single Firestore transaction.
// src/features/seller/services/assignCourierToDelivery.ts
export const assignCourierToDelivery = async (
  db: Firestore,
  orderId: string,
  courierId: string,
): Promise<void> => {
  await runTransaction(db, async (tx) => {
    const orderRef  = doc(db, 'orders', orderId);
    const deliveryRef = doc(collection(db, 'deliveries'));
    const deliveryCode = `DEL-${Date.now().toString(36).toUpperCase()}`;

    // Validate order exists
    const orderSnap = await tx.get(orderRef);
    if (!orderSnap.exists()) throw new Error('Order no existe.');

    // Advance order status
    tx.update(orderRef, {
      status: 'ASIGNADO',
      deliveryId: deliveryRef.id,
      deliveryStatus: 'ASIGNADO',
      updatedAt: serverTimestamp(),
    });

    // Create delivery document
    tx.set(deliveryRef, {
      orderId,
      courierId,
      status: 'assigned',
      deliveryCode,
      attemptNumber: 1,
      incidentReason: null,
      evidenceUrl: null,
      failureReason: null,
      amountCollected: null,
      customerConfirmed: false,
      customerConfirmedAt: null,
      pickedUpAt: null,
      inTransitAt: null,
      deliveredAt: null,
      failedAt: null,
      reprogrammedAt: null,
      createdAt: serverTimestamp(),
      assignedAt: serverTimestamp(),
      updatedAt: serverTimestamp(),
    });
  });
};
The transaction is atomic — if the order document does not exist or the write fails, neither document is modified.
4

Courier Picks Up and Delivers

The courier app moves the delivery through picked_up → in_transit → delivered. On a successful delivery the order reaches ENTREGADO or PAGADO. On failure or non-delivery it lands in NO ENTREGADO.
5

Courier Reassignment (if needed)

If a delivery stalls in PENDIENTE-ASIGNACION, the seller can reassign using reassignCourierToDelivery. This function updates the existing delivery document’s courierId and resets its assignedAt timestamp, while updating the order back to ASIGNADO.
// src/features/seller/services/assignCourierToDelivery.ts
export const reassignCourierToDelivery = async (
  db: Firestore,
  deliveryId: string,
  orderId: string,
  courierId: string,
): Promise<void> => {
  // Validates orderData.status === 'PENDIENTE-ASIGNACION'
  tx.update(deliveryRef, {
    courierId,
    status: 'assigned',
    assignedAt: serverTimestamp(),
    updatedAt: serverTimestamp(),
  });
  tx.update(orderRef, {
    status: 'ASIGNADO',
    deliveryStatus: 'ASIGNADO',
    updatedAt: serverTimestamp(),
  });
};
The useRessignOrdersToDelivery hook wraps this service and is used by ReassignModal.

Deliveries Firestore Schema

Each document in the deliveries collection contains:
FieldTypeDescription
orderIdstringReference to the parent order
courierIdstringUID of the assigned courier
statusstringassignedpicked_upin_transitdelivered / failed / reprogrammed
deliveryCodestringHuman-readable code, e.g. DEL-LX3K9F
attemptNumbernumberIncrements on reassignment or reprogramming
incidentReasonstring | nullFree-text incident logged by courier
evidenceUrlstring | nullURL to photographic evidence
failureReasonstring | nullStructured failure category
amountCollectednumber | nullCash collected on delivery (BOB)
customerConfirmedbooleanWhether the buyer confirmed reception
customerConfirmedAtTimestamp | nullWhen the buyer confirmed
pickedUpAtTimestamp | nullWhen courier picked up the package
inTransitAtTimestamp | nullWhen courier departed
deliveredAtTimestamp | nullSuccessful delivery timestamp
failedAtTimestamp | nullFailure timestamp
reprogrammedAtTimestamp | nullWhen delivery was rescheduled
assignedAtTimestampWhen courier was assigned
createdAtTimestampDocument creation time
updatedAtTimestampLast modification time
The deliveryCode field is generated as DEL- followed by the base-36 representation of Date.now(), providing a short, unique, human-readable identifier couriers can quote to buyers.

Incidents Page

The /seller/incidents page (IncidentsPanel) lets the seller log and review delivery problems in real time. An incident is a note attached to a delivery explaining why it could not be completed as expected — for example, a blocked road or an inaccessible building.
When an incident is recorded, the order document is updated with:
  • incidentReason (string) — the human-readable incident description
  • incidentNotes (string | null) — optional extended notes
Both fields are visible in OrderDetailsModal and on the individual order detail page at /seller/orders/[id].

Failure Reasons Page

The /seller/failure-reasons page is powered by the useDeliveryFailureReasons hook. It combines:
  • ordersAwaitingReason — rejected orders (RECHAZADO) that have no seller-registered failure reason yet and no customer return reason
  • registeredReasons — seller-submitted records from the deliveryFailures collection
  • customerReturnReasons — buyer-submitted return requests from the returns collection

Registering a Failure Reason

The seller picks a structured reason from the DELIVERY_FAILURE_REASON_OPTIONS list and optionally adds a description:
// src/features/seller/services/deliveryFailureReasonsService.ts
export const DELIVERY_FAILURE_REASON_OPTIONS = [
  'Cliente ausente',
  'Direccion incorrecta',
  'Producto rechazado por el cliente',
  'Producto dañado o incompleto',
  'Error en el pedido',
  'Otro',
] as const;
registerDeliveryFailureReason validates that:
  1. The order exists and belongs to the authenticated seller
  2. The order is in RECHAZADO status
  3. No reason has already been registered for this order
  4. No customer return has been filed for this order
On success it atomically writes to deliveryFailures/{orderId} and updates the order with deliveryFailureReason, deliveryFailureDescription, deliveryFailureRegisteredAt, and deliveryFailureRegisteredBy.
Calling registerDeliveryFailureReason on an order that already has a customer return in the returns collection will throw 'El cliente ya registro el motivo de este pedido.' The seller should consult the customer’s return request instead.

Failed Orders — Seller Decision

Orders in NO ENTREGADO status surface on /seller/failed-orders. The FailedOrdersPanel uses useFailedOrderDecision (which calls restartFailedOrder or cancelFailedOrder) to resolve each case.
Restarting returns the order to RESERVADO so the seller can re-package and re-assign a courier. If stockRestored is true (inventory was already freed), the service re-reserves the items before updating the order:
// src/features/seller/services/failedOrderDecisionService.ts
tx.update(orderRef, {
  status: 'RESERVADO',
  deliveryId: null,
  deliveryStatus: null,
  incidentReason: null,
  incidentNotes: null,
  failedAt: null,
  cancelledAt: null,
  stockRestored: false,
  stockRestoredAt: null,
  stockRestoredBy: null,
  reservedAt: serverTimestamp(),
  updatedAt: serverTimestamp(),
});

Undelivered Orders — Seller Decision

/seller/undelivered-orders shows the same NO ENTREGADO queue but is managed by useUndeliveredOrderDecision, which calls restartUndeliveredOrder or cancelUndeliveredOrder from undeliveredOrderDecisionService.ts. The logic is identical to the failed-order flow — restart re-reserves stock when needed, and cancel releases it. Both hooks expose loadingAction, successAction, and error for optimistic UI feedback.
// useUndeliveredOrderDecision return shape
{
  loadingAction: { orderId: string; action: 'restart' | 'cancel' } | null;
  successAction: { orderId: string; action: 'restart' | 'cancel' } | null;
  error: string | null;
  restartOrder: (orderId: string) => Promise<void>;
  cancelOrder:  (orderId: string) => Promise<void>;
}
Both FailedOrdersPanel and UndeliveredOrdersPanel display an incidentReason badge when the field is populated, giving the seller immediate context before making the restart or cancel decision.

Build docs developers (and LLMs) love