The new-sale screen is a split-panel point-of-sale interface designed for fast checkout. The left panel handles receipt type selection and client data entry, along with a live product-search box. The right panel is the shopping cart, which updates in real time as products are added or removed. When the cashier is satisfied with the cart contents, a single button finalizes the transaction, runs server-side validation, decrements stock, and generates the voucher number — all inside a database transaction to guarantee consistency.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/interezante456-pixel/nuevo-proyecto-viernes/llms.txt
Use this file to discover all available pages before exploring further.
Accessing the Screen
Navigate to/Venta/Nueva. Access is restricted to users with one of the following roles:
| Role | Can open New Sale? |
|---|---|
| Administrador | ✅ Yes |
| Gerente | ✅ Yes |
| Cajero | ✅ Yes |
| Other authenticated users | ❌ No (HTTP 403) |
/Venta/Lista) whenever the current user holds one of these three roles.
Step-by-Step: Registering a Sale
Choose receipt type
The first control in the client panel is the Tipo de Comprobante dropdown. Select either Boleta de Venta (default) or Factura. Your selection immediately adapts the client-data form below it: Boleta shows a single optional name field, while Factura reveals the Razón Social, RUC, and Celular fields and marks them as required.
Enter client info — Boleta
For a Boleta, the Nombre del Cliente field is entirely optional. Type the customer’s name if you want to associate the sale with a named client. If the field is left blank, the system will automatically link the sale to the built-in Público General record — no further action is needed.
Enter client info — Factura
For a Factura all three fields are mandatory. Fill them in before adding products to avoid a validation error at submission time.
If a client with the same RUC already exists, the system updates their Razón Social and Celular to whatever you entered, then links the sale to that existing record.
| Field | Validation |
|---|---|
| Razón Social | Non-empty. Represents the business name of the client. |
| RUC | Exactly 11 digits, must start with 10 (natural-person RUC) or 20 (legal-entity RUC). Only digits are accepted — the field auto-strips any non-numeric input. |
| Celular | Exactly 9 digits, must start with 9, must not be all the same digit (e.g. 999999999 is rejected). Only digits are accepted. |
Search for products
Use the Buscar Producto por Nombre search box at the bottom of the left panel. Start typing the product name — suggestions appear automatically after a 300 ms debounce once at least 2 characters have been entered. Each suggestion card shows:
- Product name (bold)
- Available stock (
Stock disp: N) - Sale price badge (
S/ #.##)
Estado = true (active) and Stock > 0 are returned, so out-of-stock or deactivated products never appear in search results. A maximum of 20 results is returned per query.Clicking anywhere outside the suggestion dropdown closes it without selecting a product.Add products to the cart
Click any suggestion to add it to the cart on the right panel. The suggestion box closes and focus returns to the search field so you can immediately search for the next product.
- If the product is not yet in the cart, a new row is created with a quantity of
1. - If the product is already in the cart, its quantity is incremented by
1automatically. - The quantity input accepts manual edits. The
maxattribute is set to the product’s available stock, and any attempt to enter a higher value is silently capped to that maximum.
Review the cart
The right-panel cart table shows the following columns for each line item:
The TOTAL row in the table footer sums all subtotals in real time and displays the running grand total as
| Column | Description |
|---|---|
| Producto | Product name and remaining stock hint |
| Cant. | Editable quantity input (1 – available stock) |
| P. Unit. | Unit price read-only — always the product’s PrecioVenta at the time it was added to the cart |
| Subtotal | Cantidad × P. Unit., recalculated live on every quantity change |
| (remove) | Trash icon button — removes the row from the cart and recalculates the grand total |
S/ #,##0.00.Finalize the sale
Click Registrar Venta. The following sequence of events occurs:
- Client-side validation runs first (
validarFinalizacion()). If any Factura field is missing or fails its regex, an error message is shown inline and the form does not submit. - Server-side validation checks that at least one product was submitted, that array lengths are consistent, and re-validates all Factura fields and RUC/phone formats.
- Transaction scope is opened. For each line item the server verifies the product exists and that the requested quantity does not exceed current stock.
- Stock is decremented (
producto.Stock -= cant) for every product in the cart. - Voucher number is auto-generated as the next correlative in the series (
B001-XXXXXXXXorF001-XXXXXXXX). - The
Ventaand allDetalleVentarecords are persisted and the transaction is committed. - The user is redirected to the sales list with a success notification showing the new voucher number.
Product Search API
The product search box is powered by a dedicated endpoint onVentaController:
q — the search string typed by the user.
Filter criteria applied server-side:
NombreProducto.Contains(q)— case-insensitive substring matchEstado == true— only active productsStock > 0— only products with available inventory
| Property | Type | Description |
|---|---|---|
id | int | Producto.ID_Producto |
nombre | string | Producto.NombreProducto |
precio | decimal | Producto.PrecioVenta — current sale price |
stock | int | Producto.Stock — current available quantity |
q is empty or whitespace, the endpoint returns an empty array immediately without querying the database.
The frontend applies a 300 ms debounce before firing the request, and requires at least 2 characters before any request is sent, preventing unnecessary load from single-keypress searches.
Validation Rules
Both the browser and the server validate a sale independently. The table below lists every rule enforced and where it fires.| Rule | Client-side | Server-side |
|---|---|---|
| At least one product in the cart | ✅ (button stays disabled) | ✅ (productoIds.Length == 0) |
Array lengths consistent (productoIds, cantidades, precios) | ✅ (arrays built by JS) | ✅ |
| Factura: Razón Social non-empty | ✅ | ✅ |
| Factura: RUC non-empty | ✅ | ✅ |
| Factura: Celular non-empty | ✅ | ✅ |
RUC format: ^(10|20)\d{9}$ | ✅ | ✅ |
Phone format: ^9\d{8}$ | ✅ | ✅ |
| Phone: no all-same digits | ✅ (/^(\d)\1{8}$/ check) | ✅ (regex ^(?!(\d)\1{8})9\d{8}$) |
| Quantity > 0 for each line item | ✅ (min="1" on input) | ✅ |
| Quantity ≤ available stock | ✅ (JS max attribute cap) | ✅ (per-product re-check) |
| Product must exist in database | — | ✅ |
Cart Restoration
If the server rejects a submission for any reason (validation failure, stock conflict, unexpected exception), the current state of the cart and form fields is preserved so the cashier does not have to start over. TheRestaurarEstadoVenta helper re-populates ViewBag with the following data:
| ViewBag key | Contents |
|---|---|
TipoComprobante | Previously selected receipt type |
ClienteNombres | Boleta client name or Factura Razón Social |
NumeroDocumento | RUC entered (Factura) |
Telefono | Phone number entered (Factura) |
RestoredCart | JSON array of cart items serialized as { id, nombre, precio, stockMaximo, cantidad } |
ViewBag.RestoredCart and replays each item through agregarFila(), restoring the exact quantities the cashier had entered before the error occurred. This means the cashier only needs to review the error message, correct the problem (e.g. reduce a quantity), and resubmit.