Skip to main content

Order Lifecycle

PixelTech orders follow a structured workflow with distinct statuses:

Status Definitions

PENDIENTE
string
Order created but awaiting payment confirmation or fulfillment action
PAGADO
string
Payment received - URGENT priority for packing
ALISTADO
string
Items picked and packed, ready for shipment
DESPACHADO
string
Order handed to carrier with tracking number
ENTREGADO
string
Successfully delivered to customer
CANCELADO
string
Order cancelled (payment failed, out of stock, customer request)
DEVUELTO
string
Full return - refund issued
DEVOLUCION_PARCIAL
string
Partial return - some items refunded

Order List and Filtering

Real-Time Order Updates

The order view implements smart caching with live updates:
const qRecentOrders = query(
    collection(db, "orders"),
    where("status", "in", ["PAGADO", "PENDIENTE", "ALISTADO"]),
    orderBy("createdAt", "desc"),
    limit(100)
);

unsubscribeOrders = onSnapshot(qRecentOrders, (snap) => {
    snap.docChanges().forEach(change => {
        if (['added', 'modified', 'removed'].includes(change.type)) {
            updateOrderTable();
        }
    });
});

Priority Grouping

Orders are automatically grouped by shipping urgency:
const cutoffTime = "14:00"; // 2:30 PM
const now = new Date();
const cutoffDateToday = new Date(now);
cutoffDateToday.setHours(14, 30, 0, 0);

ordersSnap.forEach(d => {
    const orderDate = d.data().createdAt.toDate();
    const isToday = orderDate.toDateString() === now.toDateString();
    
    if (isToday && orderDate > cutoffDateToday) {
        tomorrowOrders.push({ id: d.id, ...d.data() });
    } else {
        todayOrders.push({ id: d.id, ...d.data() });
    }
});
Orders marked PAGADO require immediate attention - they appear with a pulsing red badge.

Manual Sales Creation

Create orders directly from the admin panel using manual-sale.js:

Customer Selection

Search existing customers or create new sale:
const filtered = manualClientsCache.filter(u => {
    const nameMatch = normalizeText(u.name || "").includes(term);
    const phoneMatch = (u.phone || "").includes(term);
    return nameMatch || phoneMatch;
});

Product Selection with Stock Validation

function addManualItemRow() {
    // Product search with real-time availability
    const filtered = manualProductsCache.filter(p => {
        const searchStr = normalizeText(`${p.name} ${p.sku || ''}`);
        return searchStr.includes(term) && p.stock > 0;
    });
}

// Variant-specific stock checking
function updateRowStock(row, product) {
    if (product.combinations && product.combinations.length > 0) {
        const combo = product.combinations.find(c => {
            return c.color === selectedColor && c.capacity === selectedCap;
        });
        currentStock = combo ? combo.stock : 0;
    }
    
    row.querySelector('.p-max-stock').value = currentStock;
}

Shipping Methods

Customer collects from store location
shippingData = { address: "📍 Recogida en Local" };

Payment Processing

Manual sales support immediate payment or credit:
const accountId = document.getElementById('m-payment-account').value;

if (accountId === 'credit') {
    // Accounts receivable
    paymentStatus = 'PENDING';
    amountPaid = 0;
} else {
    // Immediate payment - update treasury
    await runTransaction(db, async (t) => {
        const accountRef = doc(db, "accounts", accountId);
        const accountDoc = await t.get(accountRef);
        
        t.update(accountRef, { 
            balance: accountDoc.data().balance + total 
        });
    });
    
    // Record income in expenses collection
    await addDoc(collection(db, "expenses"), {
        amount: total,
        category: "Ingreso Ventas Manual",
        description: `Cobro Inmediato - Venta a ${custName}`,
        paymentMethod: accountName,
        type: 'INCOME',
        date: new Date()
    });
    
    paymentStatus = 'PAID';
    amountPaid = total;
}
Atomic Operations: Manual sales use Firestore transactions to ensure inventory, treasury, and order creation succeed or fail together.

Order Details and Status Updates

Payment Tracking

Orders track payment progress with strict validation:
const total = Number(order.total) || 0;
const paid = Number(order.amountPaid) || 0;
const refunded = Number(order.refundedAmount) || 0;

let pendingBalance = total - paid - refunded;
if (pendingBalance < 0) pendingBalance = 0;

const isFullyPaid = order.paymentStatus === 'PAID' || 
                   order.status === 'PAGADO' || 
                   pendingBalance === 0;

Partial Payments

Support installment payments:
window.openPaymentModal = (orderId, pendingBalance) => {
    // Display payment form
    document.getElementById('payment-amount-input').max = pendingBalance;
};

// Process payment
await runTransaction(db, async (t) => {
    const orderRef = doc(db, "orders", orderId);
    const orderDoc = await t.get(orderRef);
    
    const currentPaid = orderDoc.data().amountPaid || 0;
    const newPaid = currentPaid + paymentAmount;
    const total = orderDoc.data().total;
    
    t.update(orderRef, {
        amountPaid: newPaid,
        paymentStatus: newPaid >= total ? 'PAID' : 'PARTIAL',
        lastPaymentDate: new Date(),
        updatedAt: new Date()
    });
});

Invoice Generation

Electronic Invoice Flag

const invoiceBtn = order.requiresInvoice 
    ? `<span class="text-blue-500">Factura Solicitada ✓</span>`
    : `<button onclick="requestInvoice('${order.id}')">Solicitar Factura</button>`;

Billing Information

Stored in order document:
{
  requiresInvoice: true,
  billingInfo: {
    name: "Empresa ABC SAS",
    id: "901234567-8",
    email: "[email protected]",
    address: "Calle 123 #45-67",
    city: "Bogotá"
  }
}

Remissions (Shipping Documents)

Remissions are generated automatically for each order:
await setDoc(doc(db, "remissions", orderRef.id), {
    ...orderData,
    orderId: orderRef.id,
    status: 'PENDIENTE_ALISTAMIENTO',
    type: order.source === 'MANUAL' ? 'DIRECTA' : 'TIENDA_WEB',
    createdAt: new Date()
});

Remission Security Rules

match /remissions/{remissionId} {
  allow create: if isAdmin();
  allow read, update, delete: if isAdmin();
}
Only admins can create remissions to prevent accounting discrepancies.

Order Fulfillment Workflow

Step 1: Packing (Alistamiento)

Mark items as picked and ready:
window.saveAlistamiento = async (callback) => {
    // Collect serial numbers for trackable items
    const serialNumbers = collectSerialNumbers();
    
    await updateDoc(doc(db, "orders", orderId), {
        status: 'ALISTADO',
        packedAt: new Date(),
        serialNumbers: serialNumbers,
        updatedAt: new Date()
    });
    
    if (callback) callback();
};

Step 2: Dispatch

Assign carrier and tracking number:
window.confirmDispatch = async (callback) => {
    const carrier = document.getElementById('dispatch-carrier').value;
    const tracking = document.getElementById('dispatch-tracking').value;
    
    if (!carrier || !tracking) {
        alert("Se requiere transportadora y número de guía");
        return;
    }
    
    await updateDoc(doc(db, "orders", orderId), {
        status: 'DESPACHADO',
        carrier: carrier,
        trackingNumber: tracking,
        shippedAt: new Date(),
        updatedAt: new Date()
    });
    
    // Tracking notification sent automatically via Cloud Function
    if (callback) callback();
};

Available Carriers

  • Servientrega
  • Interrapidísimo
  • Envía
  • Coordinadora
Tracking numbers are displayed with monospace font and select-all class for easy copying.

Returns and Refunds

Full Return

await updateDoc(doc(db, "orders", orderId), {
    status: 'DEVUELTO',
    refundedAmount: order.total,
    refundedAt: new Date(),
    paymentStatus: 'REFUNDED'
});

// Restore inventory
for (const item of order.items) {
    await adjustStock(item.id, item.quantity, item.color, item.capacity);
}

Partial Return

const refundAmount = calculatePartialRefund(returnedItems);

await updateDoc(doc(db, "orders", orderId), {
    status: 'DEVOLUCION_PARCIAL',
    refundedAmount: (order.refundedAmount || 0) + refundAmount,
    returnedItems: returnedItems,
    updatedAt: new Date()
});

Best Practices

Priority Management

  • Process PAGADO orders first
  • Respect cutoff times for same-day shipping
  • Update status immediately after each step

Payment Verification

  • Confirm payment before shipping
  • Track partial payments accurately
  • Issue invoices when requested

Inventory Accuracy

  • Verify stock before confirming orders
  • Scan serial numbers for electronics
  • Update stock immediately on returns

Customer Communication

  • Send tracking numbers promptly
  • Notify of shipping delays
  • Confirm delivery receipt

Build docs developers (and LLMs) love