GET /cotizador. It loads the quotes.builder Blade view, which bootstraps the frontend SPA. The page fetches the block catalogue from the API, lets the client assemble a quote interactively, and submits the result — all without requiring a login.
How it works
Page load
The browser requests
GET /cotizador. The server renders quotes.builder and passes all active QuoteBlockCategory records (ordered by order, with their active, ordered QuoteBlock children) into the view.Block catalogue fetch
The SPA calls Only blocks where
GET /api/quote-blocks to retrieve the full block catalogue in JSON. The response shape is:is_active = true are included, ordered by the order column.Block selection
The client selects blocks from categorised lists. As each block is toggled, the SPA recalculates:
- Total price — sum of
base_priceacross selected blocks - Total hours — sum of
default_hoursacross selected blocks - Subtotal, tax (16 %), and grand total
Client information form
Before submitting, the client fills in their details. The form collects the following fields:
| Field | Required | Description |
|---|---|---|
client.name | Yes | Full name |
client.email | Yes | Email address |
client.company | No | Company or organisation |
client.phone | No | Phone number |
client.additional_requirements | No | Free-text notes |
Save draft or submit
The client has two actions available:
- Save draft — calls
POST /api/quotes/save-draft. The quote is stored withstatus = draft. No PDF is generated and no email is sent. - Submit — calls
POST /api/quotes/submit. The quote is stored withstatus = sent, a PDF is generated and stored, and thesent_attimestamp is recorded.
reference on success.API endpoints
GET /api/quote-blocks
GET /api/quote-blocks
Returns all active categories and their active, ordered blocks. No authentication required.Response
POST /api/quotes/save-draft
POST /api/quotes/save-draft
Saves the current builder state as a draft. The quote is created with
Success responseValidation error response (422)
status = draft.Required fields| Field | Type | Validation |
|---|---|---|
client.name | string | required, max 255 |
client.email | string | required, valid email |
blocks | array | required |
summary.total | numeric | required, min 0 |
POST /api/quotes/submit
POST /api/quotes/submit
Submits the quote. The quote is stored with
Success responseServer error response (500)
status = sent, a PDF is generated and written to storage/app/public/quotes/{reference}.pdf, and sent_at is set to the current timestamp.Required fields| Field | Type | Validation |
|---|---|---|
client.name | string | required, max 255 |
client.email | string | required, valid email |
blocks | array | required, min 1 item |
summary.total | numeric | required |
Request payload structure
Bothsave-draft and submit accept the same top-level payload:
Quote items
After theQuote record is created, each entry in the blocks array is stored as a QuoteItem. The fields persisted are:
| Column | Source field |
|---|---|
quote_block_id | blocks[].id |
name | blocks[].name |
description | blocks[].description |
type | blocks[].type |
quantity | blocks[].quantity (default 1) |
hours | blocks[].hours |
unit_price | blocks[].base_price or blocks[].unit_price |
total_price | blocks[].total_price or blocks[].totalPrice |
data | blocks[].data or blocks[].config |
Quote reference format
References are generated automatically in theQuote model’s boot() method when a record is created:
COT-63F9A1B2C3D4E. References are unique per server microsecond and require no database sequence.
The draft save and final submit flows are identical in terms of the data they accept. The only differences are that
submit enforces at least one block (min:1) and sets status = sent with a sent_at timestamp and a generated PDF.