Skip to main content
ElCoco uses a single relational database (MySQL in production, SQLite for local development). All tables follow Laravel conventions: auto-incrementing id primary keys and created_at / updated_at timestamps unless noted otherwise.

Entity relationship overview

quote_block_categories

        │ hasMany

   quote_blocks

        │ (referenced by quote_items)

   ─────┴──────────────────────────────────

              quotes ◄───────────── quote_items
                │           belongsTo quote_block (nullable)

                │ hasMany

          quote_replies
Relationships at a glance:
ModelRelationshipTarget
QuotehasManyQuoteItem
QuotehasManyQuoteReply
QuoteItembelongsToQuote
QuoteItembelongsToQuoteBlock (nullable FK)
QuoteBlockbelongsToQuoteBlockCategory
QuoteBlockCategoryhasManyQuoteBlock
QuoteReplybelongsToQuote

Application tables

users

Standard Laravel authentication table created by Breeze.
ColumnTypeNotes
idbigint unsignedPrimary key
namevarchar(255)Display name
emailvarchar(255)Unique
email_verified_attimestampNullable
passwordvarchar(255)Bcrypt hash
remember_tokenvarchar(100)Nullable
created_attimestamp
updated_attimestamp

quote_block_categories

Top-level groupings for service blocks displayed in the builder sidebar.
ColumnTypeNotes
idbigint unsignedPrimary key
namevarchar(255)Display name
descriptiontextNullable
iconvarchar(255)Nullable. Icon identifier for the category
colorvarchar(255)Nullable. Colour used in the UI for this category
is_activetinyint(1)Default 1. Hidden from builder when 0
orderintDefault 0. Ascending sort position
created_attimestamp
updated_attimestamp
The icon and color columns are defined in the QuoteBlockCategory model’s $fillable array and are returned by QuoteApiController::getBlocks(), but they are not present in the initial migration. Add them with a follow-up migration if needed.

quote_blocks

Individual service units selectable in the quote builder. Each block belongs to one category.
ColumnTypeNotes
idbigint unsignedPrimary key
namevarchar(255)Block display name
descriptiontextNullable
category_idbigint unsignedFK → quote_block_categories.id (cascade delete)
base_pricedecimal(10,2)Default 0.00
default_hoursintDefault 0. Estimated work hours
configjsonNullable. Array of [{key: value}] extras
formulatextNullable. Reserved for future pricing formulas
validation_rulesjsonNullable. Reserved for future input validation
is_activetinyint(1)Default 1. Only active blocks appear in builder
orderintDefault 0. Ascending sort within category
created_attimestamp
updated_attimestamp
Indexes: category_id, is_active, order

quotes

Top-level record for a client quote submission. Created when a client submits the builder form or saves a draft.
ColumnTypeNotes
idbigint unsignedPrimary key
referencevarchar(255)Unique. Auto-generated on create: COT- + strtoupper(uniqid())
client_namevarchar(255)
client_emailvarchar(255)Used as the reply-to address
client_companyvarchar(255)Nullable
client_phonevarchar(255)Nullable
project_descriptiontextNullable. Free-text from the builder form
additional_requirementstextNullable
datajsonFull raw payload from the builder (cast to array)
subtotaldecimal(12,2)Pre-tax total
taxdecimal(12,2)Tax amount
totaldecimal(12,2)Final total (subtotal + tax)
total_hoursintSum of all block hours
statusenumdraft, sent, accepted, rejected, expired. Default draft
sent_attimestampNullable. Set when status becomes sent
pdf_pathvarchar(255)Nullable. Relative path in the public disk
created_attimestamp
updated_attimestamp
Indexes: reference, client_email, status, created_at
The reference field is not generated by a migration default — it is set in the Quote model’s boot() method using a creating hook:
static::creating(function ($quote) {
    $quote->reference = 'COT-' . strtoupper(uniqid());
});

Quote status lifecycle

draft ──► sent ──► accepted
               └──► rejected
               └──► expired
StatusSet byMeaning
draftClient (save-draft)Saved but not yet submitted; no PDF generated
sentClient (submit)Submitted; sent_at recorded; PDF stored
acceptedAdmin (PATCH status)DMI accepted the quote
rejectedAdmin (PATCH status)DMI declined the quote
expiredAdmin (PATCH status)Quote validity window has passed

quote_items

One row per service block added to a quote. A quote has one or more items.
ColumnTypeNotes
idbigint unsignedPrimary key
quote_idbigint unsignedFK → quotes.id (cascade delete)
quote_block_idbigint unsignedFK → quote_blocks.id (cascade delete). Not nullable per migration
namevarchar(255)Snapshot of the block name at submission time
descriptiontextNullable. Snapshot of block description
typevarchar(255)Block type string (e.g. generic)
quantityintDefault 1
hoursintDefault 0. Hours for this line
unit_pricedecimal(10,2)Price per unit
total_pricedecimal(10,2)unit_price * quantity
datajsonNullable. Additional block config snapshot (cast to array)
created_attimestamp
updated_attimestamp
Indexes: quote_id, quote_block_id
Block name and description are snapshotted into quote_items at submission time. This ensures historical quote data remains accurate even if the original block is later edited or deleted.

quote_replies

Records each admin reply (meeting confirmation) sent for a quote.
ColumnTypeNotes
idbigint unsignedPrimary key
quote_idbigint unsignedFK → quotes.id (cascade delete)
messagetextNullable. Formatted meeting confirmation text (e.g. "Cita Virtual en GoogleMeet: 20/03/2026 14:30")
sent_to_emailvarchar(255)Nullable. Email address the reply was sent to
sent_attimestampThe scheduled meeting datetime
created_attimestamp
updated_attimestamp
Only quote_id and sent_at are in the QuoteReply model’s $fillable. The message and sent_to_email columns exist in the database but must be set via fill() or direct assignment.

Laravel infrastructure tables

These tables are created by Laravel’s built-in migrations and are not specific to ElCoco.
Stores database-backed sessions when SESSION_DRIVER=database.
ColumnType
idvarchar (primary key)
user_idbigint (nullable, indexed)
ip_addressvarchar(45) nullable
user_agenttext nullable
payloadlongtext
last_activityint (indexed)
Stores cached values when CACHE_STORE=database.
ColumnType
keyvarchar (primary key)
valuemediumtext
expirationint
Queue tables used when QUEUE_CONNECTION=database.
  • jobs — pending jobs waiting to be processed.
  • job_batches — batch metadata for grouped jobs.
  • failed_jobs — records of jobs that threw an unhandled exception.
Temporary tokens for the password-reset flow. Keyed by email address.
Sanctum API tokens. Uses a polymorphic tokenable relation so any model can issue tokens.
ColumnType
idbigint (primary key)
tokenable_typevarchar
tokenable_idbigint
nametext
tokenvarchar(64) unique
abilitiestext nullable
last_used_attimestamp nullable
expires_attimestamp nullable (indexed)

Build docs developers (and LLMs) love