Skip to main content

Overview

DADDO’s sales tracking system allows you to record transactions, automatically update inventory, track sales by product and variant, and view comprehensive sales history with confirmation workflows.

Creating Sales

Create Sale Action

The createSell action (src/Redux/actions/Sells/createSell.js) handles sale creation:
export const createSell = (products) => async (dispatch) => {
  dispatch({ type: CREATE_SELL_REQUEST });

  try {
    const response = await api.post("/sells", { products });

    dispatch({ type: CREATE_SELL_SUCCESS, payload: response.data });
    return response.data; 

  } catch (error) {
    dispatch({
      type: CREATE_SELL_FAILURE,
      payload: error.response?.data?.error || "Error al crear venta",
    });

    throw error; 
  }
};
Sales are created with an array of products, each containing product ID, variant ID (if applicable), quantity, and final price.

Create Sale Component

The Create_Sell component (src/Views/createSell.jsx) provides a comprehensive interface for recording sales:

Component State

const Create_Sell = () => {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const token = localStorage.getItem("token");

  const { categories } = useSelector((state) => state.categories);
  const { products } = useSelector((state) => state.filteredProducts);

  const [selectedCategory, setSelectedCategory] = useState("");
  const [selectedProduct, setSelectedProduct] = useState("");
  const [selectedVariant, setSelectedVariant] = useState("");
  const [quantity, setQuantity] = useState(1);
  const [sellPrice, setSellPrice] = useState("");
  const [sellProducts, setSellProducts] = useState([]);
  const [search, setSearch] = useState("");
  // ...
};

Product Selection Workflow

Step 1: Category Filter

Users start by selecting a category to narrow down product choices:
useEffect(() => {
  dispatch(getCategories(token));
}, [dispatch, token]);

useEffect(() => {
  if (selectedCategory) {
    dispatch(getFilteredProducts({ category: selectedCategory }));
  }
}, [dispatch, selectedCategory]);
<select
  value={selectedCategory}
  onChange={(e) => setSelectedCategory(e.target.value)}
  className="w-full sm:w-auto bg-gray-900 border text-white border-purple-500 rounded-md px-4 py-2"
>
  <option value="">- Selecciona una categoría -</option>
  {categories.map((cat) => (
    <option key={cat.id} value={cat.id}>
      {cat.name}
    </option>
  ))}
</select>

Step 2: Product Selection

Once a category is selected, available products are displayed:
<select
  value={selectedProduct}
  onChange={(e) => {
    setSelectedProduct(e.target.value);
    setSelectedVariant("");
  }}
  className="w-full bg-gray-900 border border-purple-500 rounded-md px-4 py-2 text-gray-200"
>
  <option value="">Selecciona un producto</option>
  {Array.isArray(products) &&
    products.map((prod) => (
      <option key={prod.id} value={prod.id}>
        {prod.name}
      </option>
    ))}
</select>
Product Preview:
{selectedProduct && (
  <div className="mt-3 flex items-center space-x-3">
    <div className="flex flex-col">
      <span className="text-gray-200 font-medium">
        {products.find((p) => p.id === selectedProduct)?.name}
      </span>
      <span className="text-gray-400 text-sm">
        Stock disponible:{" "}
        {(() => {
          const prod = products.find((p) => p.id === selectedProduct);
          return prod?.variants?.length > 0
            ? "Depende de la variante"
            : prod?.stock ?? 0;
        })()}
      </span>
    </div>
    <img
      src={
        products.find((p) => p.id === selectedProduct)?.images?.[0]?.url ||
        "https://via.placeholder.com/60x60?text=Sin+imagen"
      }
      className="w-16 h-16 object-cover rounded-md border border-gray-700"
    />
  </div>
)}

Step 3: Variant Selection (if applicable)

If the product has variants, users must select one:
{selectedProduct &&
  products.find((p) => p.id === selectedProduct)?.variants?.length > 0 && (
    <select
      value={selectedVariant}
      onChange={(e) => setSelectedVariant(e.target.value)}
      className="w-full bg-gray-900 border border-purple-500 rounded-md px-4 py-2 text-gray-200"
    >
      <option value="">Selecciona una variante</option>
      {products
        .find((p) => p.id === selectedProduct)
        ?.variants.map((v) => (
          <option key={v.id} value={v.id}>
            {v.color || v.size
              ? `${v.color || ""} ${v.size || ""}`.trim()
              : `Variante sin nombre`}{" "}
            (Stock: {v.stock})
          </option>
        ))}
    </select>
  )
}

Step 4: Quantity and Price

Users specify quantity and can override the default price:
useEffect(() => {
  if (selectedProduct) {
    const prod = products.find((p) => p.id === selectedProduct);
    if (prod) setSellPrice(prod.price);
  } else {
    setSellPrice("");
  }
}, [selectedProduct, products]);
{selectedProduct && (
  <div className="flex items-center space-x-4 mt-4">
    <input
      type="number"
      min="1"
      value={quantity}
      onChange={(e) => setQuantity(Number(e.target.value))}
      className="w-20 bg-gray-900 rounded-lg px-2 py-1 text-gray-200"
    />
    <div className="flex items-center space-x-2">
      <span className="text-gray-300 font-medium">Precio:</span>
      <input
        type="number"
        min="0"
        value={sellPrice}
        onChange={(e) => setSellPrice(e.target.value)}
        className="w-24 bg-gray-900 rounded-lg px-2 py-1 text-gray-200"
      />
    </div>
    <button
      type="button"
      onClick={handleAddProduct}
      className="bg-purple-800 text-white px-2 py-1 rounded-lg hover:bg-purple-600"
    >
      Agregar
    </button>
  </div>
)}

Adding Products to Sale

Stock Validation

The system validates stock availability before adding products:
const handleAddProduct = () => {
  if (!selectedProduct) {
    toast.error("Selecciona un producto válido");
    return;
  }

  if (quantity <= 0) {
    toast.error("Cantidad debe ser mayor a 0");
    return;
  }

  if (!sellPrice || sellPrice <= 0) {
    toast.error("Ingresa un precio válido");
    return;
  }

  const product = products.find((p) => p.id === selectedProduct);
  const variant = product.variants?.find((v) => v.id === selectedVariant) || null;

  const availableStock = variant ? variant.stock : product.stock;
  const key = variant ? `${selectedProduct}-${variant.id}` : selectedProduct;
  const finalUnitPrice = Number(sellPrice);
  const existing = sellProducts.find((p) => p.key === key);
  const totalQuantity = existing ? existing.quantity + quantity : quantity;

  if (totalQuantity > availableStock) {
    toast.error(`Stock insuficiente. Disponible: ${availableStock}`);
    return;
  }

  // Add product...
};

Building Sale Item

Each product added to the sale includes complete information:
const newItem = {
  key,
  ProductId: selectedProduct,
  variantId: variant ? variant.id : null,
  quantity,
  finalPrice: finalUnitPrice,
  name: product.name,
  variantLabel: variant
    ? `${variant.color || ""} ${variant.size || ""}`.trim()
    : null,
};

if (existing) {
  setSellProducts(
    sellProducts.map((p) =>
      p.key === key
        ? {
            ...p,
            quantity: totalQuantity,
            finalPrice: finalUnitPrice, 
          }
        : p
    )
  );
} else {
  setSellProducts([...sellProducts, newItem]);
}

Unique Key

Combines product ID and variant ID to track individual items

Quantity Update

Automatically adds quantities if same product is added multiple times

Price Override

Allows custom pricing per sale transaction

Variant Tracking

Distinguishes between different variants of the same product

Sales Cart Display

Product List

Added products are displayed in a cart-style list:
{sellProducts.length > 0 && (
  <div className="bg-gray-900 p-4 rounded-lg shadow-sm overflow-x-auto">
    <h3 className="text-lg font-semibold text-gray-200 mb-2">
      Productos seleccionados
    </h3>
    <ul className="space-y-2">
      {sellProducts.map((p) => (
        <li
          key={p.key}
          className="flex justify-between text-purple-500 items-center bg-gray-900 border border-gray-700 p-2 rounded shadow"
        >
          <span>
            {p.name}{" "}
            {p.variantLabel && (
              <span className="text-gray-400 text-sm">
                ({p.variantLabel})
              </span>
            )}{" "}
            - {p.quantity} pz - ${p.finalPrice}
          </span>
          <button
            type="button"
            onClick={() => handleRemoveProduct(p.key)}
            className="text-red-500 font-bold hover:text-red-700"
          >

          </button>
        </li>
      ))}
    </ul>
  </div>
)}

Remove Products

const handleRemoveProduct = (key) => {
  setSellProducts(sellProducts.filter((p) => p.key !== key));
};

Submitting the Sale

Sale Creation

Once all products are added, the sale can be submitted:
const handleSubmit = async (e) => {
  e.preventDefault();
  if (sellProducts.length === 0) {
    setMessage("Debes agregar al menos un producto a la venta");
    setMessageType("error");
    return;
  }

  try {
    await dispatch(createSell(sellProducts, token));
    setMessage("Venta registrada con éxito");
    setMessageType("success");

    setTimeout(() => navigate("/sells"), 1500);
  } catch (error) {
    setMessage(error?.response?.data?.error || "Error al registrar venta");
    setMessageType("error");
  }
};
Sales permanently reduce inventory stock. Ensure all quantities and products are correct before submitting.

Viewing Sales History

Get User Sales

The getUserSells action retrieves all sales for the current user:
import { getUserSells } from "../Redux/actions/Sells/getUserSells";

useEffect(() => {
  dispatch(getUserSells(token));
}, [dispatch, token]);

Sales Table Component

Sales are displayed in a comprehensive table (src/components/sales/SalesTable.jsx):
  • Sale ID and date
  • Total amount
  • Number of products
  • Confirmation status
  • Action buttons (view details, confirm, delete)

Sale Confirmation

Confirm Sale Action

Sales can be confirmed to finalize the transaction:
import { confirmSell } from "../Redux/actions/Sells/confirmSell";

const handleConfirm = (sellId) => {
  dispatch(confirmSell(sellId, token));
  toast.success("Venta confirmada");
};
Confirmed sales are typically used to mark when payment has been received or the order is complete.

Sale Details

Get Sale by ID

Detailed information about a specific sale:
import { getSellById } from "../Redux/actions/Sells/getSellById";

const viewSaleDetails = (sellId) => {
  dispatch(getSellById(sellId, token));
};
Sale details include:
  • Complete product list with quantities
  • Variant information
  • Individual product prices
  • Total sale amount
  • Creation timestamp
  • Confirmation status

Deleting Sales

Delete Sale Action

Unconfirmed sales can be deleted:
import { deleteSell } from "../Redux/actions/Sells/deleteSell";

const handleDelete = async (sellId) => {
  if (window.confirm("¿Eliminar esta venta?")) {
    await dispatch(deleteSell(sellId, token));
    dispatch(getUserSells(token)); // Refresh list
    toast.success("Venta eliminada");
  }
};
Deleting a sale does NOT restore inventory. Stock adjustments must be made manually if needed.

Search Functionality

The sales form includes product search:
const [search, setSearch] = useState("");

useEffect(() => {
  if (search.length > 2) {
    dispatch(getFilteredProducts({ search }));
  }
}, [dispatch, search]);
Search activates after entering at least 3 characters to avoid excessive API calls.

Sale Data Structure

Each sale record contains:
{
  id: "sale_id",
  userId: "user_id",
  products: [
    {
      ProductId: "product_id",
      variantId: "variant_id" || null,
      quantity: 5,
      finalPrice: 29.99,
      name: "Product Name",
      variantLabel: "Red / Large"
    }
  ],
  totalAmount: 149.95,
  confirmed: false,
  createdAt: "2024-01-15T10:30:00Z"
}

Best Practices

  • Double-check quantities before submission
  • Verify variant selection for products with options
  • Use custom pricing for discounts or special offers
  • Check stock availability before processing
  • Confirm sales after payment is received
  • Monitor stock levels regularly
  • Update product stock when receiving inventory
  • Be cautious when deleting sales (stock not restored)
  • Review sales history to identify popular products
  • Use confirmation workflow for payment tracking
  • Review sales history regularly
  • Track confirmed vs. pending sales
  • Monitor sales by product and category
  • Use sales data for inventory planning
  • Identify best-selling products and variants

Build docs developers (and LLMs) love