Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JFKoryy/autopart-pro/llms.txt

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

When a client is ready to purchase, AutoPart Pro carries their cart through a three-stage flow: items are held in React state (and localStorage) via CartContext, the Checkout page collects shipping details and submits a single POST /api/sales request, and the server atomically records the order and deducts stock for every line item inside a MySQL transaction. The entire operation either commits fully or rolls back — there is no partial state.

Cart State (Frontend)

CartContext is a React context provider that lives at frontend/src/context/CartContext.jsx. It manages the in-memory cart and persists it to localStorage so items survive page refreshes.
// CartContext.jsx — key helpers exposed through useCart()
function addItem(product, qty = 1) {
  setItems((prev) => {
    const existing = prev.find((i) => i.id === product.id)
    if (existing) {
      return prev.map((i) => (i.id === product.id ? { ...i, qty: i.qty + qty } : i))
    }
    return [...prev, { ...product, qty }]
  })
}

const count = items.reduce((sum, i) => sum + i.qty, 0)
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0)
The context exposes items, addItem, updateQty, removeItem, clearCart, count, and total to any component wrapped inside <CartProvider>.

Checkout Flow

1

Browse the catalog and add items

On the Catalog page (/catalogo) the client clicks Add to Cart on a ProductCard. The card calls addItem(product) from useCart(), which merges the product into the cart state and increments its quantity if it already exists.
2

Review the cart

Navigating to /carrito opens the Cart.jsx page. The client can adjust quantities with the + / controls (which call updateQty) or remove individual lines with the trash icon (removeItem). A summary panel shows the running subtotal.
3

Proceed to checkout

Clicking Proceder al pago navigates to /checkout. The Checkout.jsx page collects shipping details and renders a payment-gateway placeholder. The grand total includes a flat shipping fee of 10 000 on top of the cart subtotal.
4

Submit the order

On form submission, Checkout.jsx calls checkout(items) from api.js, which maps the cart into the wire format and sends it to the backend:
// frontend/src/services/api.js
export async function checkout(cart) {
  const items = cart.map(item => ({
    product_id: item.id,
    quantity:   item.qty,
    unit_price: item.price
  }))

  const response = await fetch(`${import.meta.env.VITE_API_URL}/sales`, {
    method: 'POST',
    headers: getAuthHeader(),
    body: JSON.stringify({ items })
  })
  // ...
}
5

Server records the sale and deducts stock

The createSale controller passes the authenticated user’s ID and the items array to SaleModel.create(), which runs everything inside a single MySQL transaction (see below).
6

Confirmation screen

On a 201 response the frontend calls clearCart() (empties state and localStorage) and renders a success screen showing the new saleId order number.

Server-Side Transaction

SaleModel.create() in backend/src/models/saleModel.js uses an explicit MySQL connection to guarantee atomicity:
create: async (userId, items) => {
  const connection = await db.getConnection();
  await connection.beginTransaction();

  try {
    // 1. Compute total server-side
    const total = items.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0);

    // 2. Insert the sale header
    const [saleResult] = await connection.execute(
      'INSERT INTO sales (user_id, total) VALUES (?, ?)',
      [userId, total]
    );
    const saleId = saleResult.insertId;

    // 3. Insert each line item and deduct stock
    for (const item of items) {
      await connection.execute(
        'INSERT INTO sale_items (sale_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)',
        [saleId, item.product_id, item.quantity, item.unit_price]
      );

      await connection.execute(
        'UPDATE products SET stock = stock - ? WHERE id = ?',
        [item.quantity, item.product_id]
      );
    }

    await connection.commit();
    return saleId;

  } catch (error) {
    await connection.rollback();
    throw error;
  } finally {
    connection.release();
  }
}
The stock = stock - ? deduction is not guarded by a check for negative stock at the SQL level. If you expose the storefront publicly, consider adding a WHERE stock >= ? clause or a check constraint to prevent overselling.
The sale total is always computed server-side as sum(quantity × unit_price) across all items, so a client cannot manipulate the total by altering the request payload after the unit prices are set.

Checkout Request & Response

Request — POST /api/sales Requires a valid JWT (Authorization: Bearer <TOKEN>).
curl -X POST http://localhost:5000/api/sales \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '{
    "items": [
      { "product_id": 12, "quantity": 2, "unit_price": 45.99 },
      { "product_id": 7,  "quantity": 1, "unit_price": 18.50 }
    ]
  }'
Response — 201 Created
{
  "success": true,
  "message": "Compra realizada con éxito.",
  "saleId": 101
}
If the cart is empty, the server returns 400:
{
  "success": false,
  "message": "El carrito está vacío."
}

Viewing Sales History

GET /api/sales returns order history scoped to the caller’s role:
  • Admin — calls SaleModel.getAll(), which returns every sale in the system joined with user details.
  • Client / Employee — calls SaleModel.getByUser(req.user.id), which filters by user_id.
curl http://localhost:5000/api/sales \
  -H "Authorization: Bearer <TOKEN>"
Each sale object in the response includes a nested items array assembled via MySQL’s JSON_ARRAYAGG:
{
  "id": 101,
  "total": 110.48,
  "status": "completed",
  "created_at": "2024-11-15T14:32:00.000Z",
  "user_name": "María López",
  "user_email": "maria@example.com",
  "items": [
    {
      "product_id": 12,
      "product_name": "Pastilla de freno delantera",
      "quantity": 2,
      "unit_price": 45.99
    },
    {
      "product_id": 7,
      "product_name": "Filtro de aceite",
      "quantity": 1,
      "unit_price": 18.50
    }
  ]
}
The user_name and user_email fields only appear in the admin view (SaleModel.getAll()). The client-scoped query (getByUser) omits those fields since the buyer is already known from the JWT.

Inventory Management

Understand the product data model that backs each sale line item.

User Roles

See how POST /api/sales is protected and how admin vs. client sales visibility is enforced.

Build docs developers (and LLMs) love