Skip to main content

Product Catalog Management

The inventory system uses a Smart Real-Time Cache architecture that synchronizes products to localStorage, enabling instant searches and minimal Firebase reads.

Smart Sync Architecture

const SmartAdminInventorySync = {
    STORAGE_KEY: 'pixeltech_admin_master_inventory',
    runtimeMap: {},
    lastSyncTime: 0,

    async init() {
        // 1. Instant load from cache
        const cachedRaw = localStorage.getItem(this.STORAGE_KEY);
        if (cachedRaw) {
            const parsed = JSON.parse(cachedRaw);
            this.runtimeMap = parsed.map;
            this.updateGlobalArray();
            renderViewFromMemory(); // Instant UI
        }

        // 2. Listen for real-time updates (deltas only)
        this.listenForUpdates();
    }
};
Performance: First load is instantaneous from cache. Only changed products trigger Firebase reads.

Stock Tracking and Adjustments

Product Variations

Products can have combinations of colors and capacities. Stock is tracked per combination:
// Product structure with combinations
{
  id: "prod123",
  name: "iPhone 15 Pro",
  price: 4500000,
  stock: 45, // Total stock
  combinations: [
    {
      color: "Negro",
      capacity: "128GB",
      price: 4500000,
      stock: 15,
      sku: "IP15P-BLK-128"
    },
    {
      color: "Negro",
      capacity: "256GB",
      price: 5200000,
      stock: 10,
      sku: "IP15P-BLK-256"
    }
  ]
}

Stock Adjustment Function

From inventory-core.js:
export async function adjustStock(productId, delta, color = null, capacity = null) {
    const productRef = doc(db, "products", productId);
    
    await runTransaction(db, async (transaction) => {
        const productDoc = await transaction.get(productRef);
        if (!productDoc.exists()) throw "Product not found";
        
        const data = productDoc.data();
        let newStock = (data.stock || 0) + delta;
        
        // If product has combinations, adjust specific variant
        if (data.combinations && (color || capacity)) {
            const combos = data.combinations.map(c => {
                if (c.color === color && c.capacity === capacity) {
                    return { ...c, stock: (c.stock || 0) + delta };
                }
                return c;
            });
            
            // Recalculate total stock
            newStock = combos.reduce((sum, c) => sum + (c.stock || 0), 0);
            
            transaction.update(productRef, {
                combinations: combos,
                stock: newStock,
                updatedAt: new Date()
            });
        } else {
            transaction.update(productRef, {
                stock: newStock,
                updatedAt: new Date()
            });
        }
    });
}
Atomic Operations: Stock adjustments use Firestore transactions to prevent race conditions during concurrent sales.

Product Creation and Editing

Required Fields

  • Name: Product title (searchable)
  • SKU: Unique identifier
  • Price: Base selling price
  • Category: Product classification
  • Status: active or draft

Optional Fields

  • Brand: Manufacturer name
  • Description: Rich HTML content
  • Images: Main image + gallery
  • Warranty: Time period and unit (days/months/years)
  • Variations: Colors and capacities

Low Stock Alerts

Products with ≤5 units are automatically flagged:
if (currentFilterType === 'lowstock') {
    filtered = adminProductsCache.filter(p => (p.stock || 0) <= 5);
    filtered.sort((a,b) => (a.stock || 0) - (b.stock || 0));
}

Discount and Promotions

Apply temporary price reductions with automatic expiration:

Single Product Discount

await updateDoc(doc(db, "products", productId), {
    originalPrice: 500000,      // Store original
    price: 399000,              // New discounted price
    promoEndsAt: endDate,       // Auto-restore on this date
    updatedAt: new Date()
});

Multi-Variant Discount

For products with combinations:
const updatedCombinations = product.combinations.map(c => {
    if (!c.originalPrice) c.originalPrice = c.price;
    
    if (newPriceRaw > 0 && newPriceRaw < c.originalPrice) {
        c.price = newPriceRaw;
    } else {
        c.price = c.originalPrice; // Restore original
    }
    return c;
});
Visual Indicators: Discounted products automatically show percentage badges and strike-through pricing in the catalog.

Suppliers and Purchases

Supplier Management

Suppliers are stored in the suppliers collection:
{
  name: "Importadora XYZ SAS",
  contact: "Carlos Mendoza",
  phone: "3001234567",
  email: "[email protected]",
  category: "Electrónica"
}

Purchase Orders

Track inventory purchases and payables:
// Create purchase
await addDoc(collection(db, "purchases"), {
    supplierId: "supplier123",
    items: [
        { productId: "prod456", quantity: 50, unitCost: 200000 }
    ],
    total: 10000000,
    status: "PENDING",
    createdAt: new Date()
});

// Create payable (debt to supplier)
await addDoc(collection(db, "payables"), {
    provider: "Importadora XYZ SAS",
    description: "Compra electrónica junio",
    total: 10000000,
    amountPaid: 0,
    balance: 10000000,
    status: "PENDING",
    dueDate: new Date("2024-07-15")
});

Warranty Inventory

Manage products under warranty separately:
// Warranty claim structure
{
  orderId: "order123",
  userId: "user456",
  productId: "prod789",
  issue: "Pantalla no enciende",
  status: "PENDING",        // PENDING | APPROVED | REJECTED | REPLACED
  createdAt: Timestamp,
  resolvedAt: null,
  replacementProductId: null
}

Warranty Rules

From firestore.rules:
match /warranties/{warrantyId} {
  allow create: if isAuthenticated() && request.resource.data.userId == request.auth.uid;
  allow read: if isAuthenticated() && (resource.data.userId == request.auth.uid || isAdmin());
  allow update, delete: if isAdmin();
}

Search and Filters

Real-Time Search (Zero Reads)

Search operates entirely in-memory:
function renderViewFromMemory() {
    const term = normalizeText(searchInput.value.trim());
    
    if (term.length > 0) {
        filtered = adminProductsCache.filter(p => 
            p.searchStr && p.searchStr.includes(term)
        );
    }
    
    // Pagination
    const pageProducts = filtered.slice(startIdx, endIdx);
    renderTable(pageProducts);
}

Available Filters

Show entire catalog (active + draft)

Product Status Management

Toggle visibility without deleting:
window.toggleProductStatus = async (id, currentStatus) => {
    const newStatus = currentStatus === 'active' ? 'draft' : 'active';
    
    await updateDoc(doc(db, "products", id), {
        status: newStatus,
        updatedAt: new Date()
    });
    
    // Real-time listener automatically updates UI
};
Security: Product price changes require admin role. Stock adjustments can be performed by authenticated system processes.

Best Practices

  • Always use adjustStock() function for inventory changes
  • Never manually modify stock values to prevent race conditions
  • Review low-stock alerts daily
  • Use combinations for color/capacity variants
  • Each combination tracks independent stock
  • Set unique SKUs for each variant
  • Use originalPrice field for promotions
  • Set promoEndsAt for automatic restoration
  • Discount percentage is calculated automatically
  • Link purchases to suppliers for reporting
  • Track payment terms in payables
  • Monitor supplier performance

Build docs developers (and LLMs) love