Order Lifecycle
PixelTech orders follow a structured workflow with distinct statuses:
Status Definitions
Order created but awaiting payment confirmation or fulfillment action
Payment received - URGENT priority for packing
Items picked and packed, ready for shipment
Order handed to carrier with tracking number
Successfully delivered to customer
Order cancelled (payment failed, out of stock, customer request)
Full return - refund issued
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
Pickup
Saved Address
New Address
Customer collects from store locationshippingData = { address: "📍 Recogida en Local" };
Use customer’s registered delivery addressconst address = currentUserAddresses[selectedIndex];
shippingData = {
department: address.dept,
city: address.city,
address: `${address.address} (${address.alias})`
};
Enter custom delivery locationshippingData = {
department: selectedDepartment,
city: selectedCity,
address: manualAddress
};
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>`;
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