events collection, with a mirrored copy maintained in PostgreSQL for high-throughput querying during sales and access control.
Ticket Anatomy
Every ticket in the system is scoped to an event, a zone within that event, and a specific seat within that zone. The ticket is the atomic unit of admission — it carries its own identity, state, and a full audit trail via theledger.
| Field | Description |
|---|---|
ticket_id | Composite ID: {event_id}-{firestore_doc_id} |
seat_id | Composite seat reference: {zone_id}-{seat_number} |
zone | Display name of the seating zone |
color | Hex color assigned to the zone |
status | true = available, false = sold |
status_offline | true when the ticket is assigned to an offline office |
access_status | true once the ticket has been scanned for entry |
access_entry | true while the holder is inside the venue |
ledger | Array of time-stamped action entries (see below) |
date_start | Event start timestamp (copied from the event document) |
date_end | Event end timestamp (copied from the event document) |
event_name | Display name of the parent event |
event_id | Firestore document ID of the parent event |
seat_row | Row label, set to "por asignar" at generation |
Ledger Entries
Theledger array records every significant action against a ticket. Each entry has the same shape:
action values:
| Action | When it is written |
|---|---|
generated | Ticket is first created by tickets_generate |
accessed | Ticket is scanned for entry (access_control_in) |
came-out | Ticket holder exits the venue (access_control_out) |
updated | Ticket fields are manually updated |
offline | Ticket is assigned to an offline office |
unassign | Ticket is returned from an offline office |
Firestore Data Model
Tickets live as sub-documents under their parent event:The
events/{id}/setup/zones document must have status: true before tickets_generate will proceed. This acts as an activation gate to prevent accidental generation.Subsystems
Ticket Generation
Generate one ticket per seat from zone configuration. Writes to both Firestore and PostgreSQL in a single atomic batch.
Ticket Management
List all tickets for an event, fetch an individual ticket, or update ticket status and ledger entries.
Access Control
Checkpoint-based entry and exit scanning for both ticket QR codes and staff credentials. Supports cold (offline) sync.
Virtual Office
Online sales point: list available seats, lock tickets during checkout, view live sales status, and release held tickets.
Offline Office
Physical box offices that operate without a live connection. Tickets are assigned, sold locally, and synchronised back to the central platform.
Dual-Write Architecture
All ticket mutations are written to both Firestore and PostgreSQL. Firestore is the source of truth for real-time UI and access control apps. PostgreSQL backs all reporting, sales, and bulk queries via thedbpostgres.sqltmt helper.
The
tickets_blocked table in PostgreSQL acts as a short-lived reservation lock. Records are inserted with a locked_up expiry timestamp and deleted either explicitly or by the tickets_unlock sweep.