Skip to main content
All tables are defined in src/db/schema.ts using Drizzle ORM’s pgTable builder and migrated with drizzle-kit. The Drizzle config reads the connection string from the DATABASE_URL environment variable:
drizzle.config.ts
export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: { url: process.env.DATABASE_URL },
});

Soft-delete pattern

Most tables include a borrado_en column of type timestamp with time zone. A NULL value means the record is active. When you “delete” a record the application sets borrado_en to the current timestamp instead of issuing a DELETE statement. All queries filter by borrado_en IS NULL to return only active rows.

Tables

usuarios

Stores staff accounts. The primary key is a text UUID generated by the application.
ColumnTypeNotes
idtextPrimary key (app-generated UUID)
nombrevarchar(60)Full name
nombre_usuariovarchar(30)Login username
contrasenavarchar(255)Hashed password
rolvarchar(20)Default 'cajero'
creado_entimestamptzAuto-set on insert
actualizado_entimestamptzAuto-set on insert
borrado_entimestamptzSoft-delete timestamp

caja_turno

Represents a single cash-register shift. Tracks denomination counts, sales by payment method, and total cash-outs.
ColumnTypeNotes
idserialPrimary key
fechadateShift date
hora_aperturatimestamptzOpening time, default now
hora_cierretimestamptzClosing time (nullable)
usuario_idtextFK → usuarios.id
monto_inicialnumeric(10,2)Opening cash amount
b200m010integerBill/coin denomination counts
ventas_efectivonumeric(10,2)Cash sales total
ventas_qrnumeric(10,2)QR sales total
total_salidasnumeric(10,2)Total cash-outs
cerradabooleanfalse while shift is open
cierre_obstextClosing observations (nullable)

gastos_caja

Expenses recorded against an open shift.
ColumnTypeNotes
idserialPrimary key
caja_idintegerFK → caja_turno.id (cascade delete)
usuario_idtextFK → usuarios.id
descripciontextExpense description
metodo_pagovarchar(20)Payment method
montonumeric(10,2)Amount
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

productos

Retail products (non-dish items) sold by unit.
ColumnTypeNotes
idtextPrimary key
nombrevarchar(60)Product name
precionumeric(10,2)Unit price
stockintegerCurrent stock, default 0
unidadvarchar(20)Unit of measure
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

ingredientes

Kitchen ingredients with minimum-stock alerts.
ColumnTypeNotes
idtextPrimary key
nombrevarchar(100)Ingredient name
unidadvarchar(20)Unit of measure
cantidaddouble precisionCurrent quantity, default 0
cantidad_minimadouble precisionMinimum alert threshold, default 0
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

platos

Menu dishes.
ColumnTypeNotes
idtextPrimary key
nombrevarchar(60)Dish name
precionumeric(10,2)Price
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

plato_ingredientes

Join table linking dishes to their ingredient requirements. Uses a composite primary key.
ColumnTypeNotes
plato_idtextPK + FK → platos.id (cascade delete)
ingrediente_idtextPK + FK → ingredientes.id
cantidaddouble precisionQuantity needed per serving
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

transacciones

The central sales table. Each row is one order.
ColumnTypeNotes
idserialPrimary key
nro_regintegerHuman-readable order number
fechadateOrder date
horatimestamptzExact order time
tipovarchar(30)Default 'venta'
conceptotextOrder description
monto_totalnumeric(10,2)Total amount
monto_pagadonumeric(10,2)Amount paid so far
mesavarchar(50)e.g. 'Mesa 5', 'Para llevar', 'Delivery'
clientevarchar(100)Customer name (nullable)
estadovarchar(20)pendiente | abierto | cerrado
estado_cocinavarchar(20)pendiente | terminado
caja_idintegerFK → caja_turno.id
usuario_idtextFK → usuarios.id
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp
monto_pendiente is not stored. Compute it as monto_total - monto_pagado.
Below is the full Drizzle definition for this table:
src/db/schema.ts
export const transacciones = pgTable('transacciones', {
  id: serial('id').primaryKey(),
  nro_reg: integer('nro_reg').notNull(),
  fecha: date('fecha').defaultNow(),
  hora: timestamp('hora', { withTimezone: true }).defaultNow(),
  tipo: varchar('tipo', { length: 30 }).default('venta'),
  concepto: text('concepto').notNull(),

  monto_total: numeric('monto_total', { precision: 10, scale: 2 })
    .notNull()
    .default('0'),
  monto_pagado: numeric('monto_pagado', { precision: 10, scale: 2 })
    .notNull()
    .default('0'),

  mesa: varchar('mesa', { length: 50 }),
  cliente: varchar('cliente', { length: 100 }),

  estado: varchar('estado', { length: 20 }).default('pendiente'),
  estado_cocina: varchar('estado_cocina', { length: 20 }).default('pendiente'),

  caja_id: integer('caja_id').references(() => caja_turno.id),
  usuario_id: text('usuario_id').references(() => usuarios.id),

  creado_en: timestamp('creado_en', { withTimezone: true })
    .defaultNow()
    .notNull(),
  actualizado_en: timestamp('actualizado_en', { withTimezone: true })
    .defaultNow()
    .notNull(),
  borrado_en: timestamp('borrado_en', { withTimezone: true }),
});

detalle_items

Line items for a transaction. Each row is either a producto or a plato (the two foreign keys are mutually exclusive).
ColumnTypeNotes
idserialPrimary key
transaccion_idintegerFK → transacciones.id (cascade delete)
producto_idtextFK → productos.id (nullable)
plato_idtextFK → platos.id (nullable)
cantidadnumeric(10,2)Quantity ordered
precio_unitarionumeric(10,2)Unit price at time of sale
subtotalnumeric(10,2)cantidad × precio_unitario
notastextPer-item customer notes, e.g. 'Sin cebolla'
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

detalle_item_extras

Optional extras attached to a line item. The extra can reference a known ingredient or use a free-text description.
ColumnTypeNotes
idserialPrimary key
detalle_item_idintegerFK → detalle_items.id (cascade delete)
ingrediente_idtextFK → ingredientes.id (nullable)
descripciontextFree-text extra, e.g. 'Extra queso'
precionumeric(10,2)Extra price
cantidadnumeric(10,2)Default 1
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp

pagos

Payment records for a transaction. Multiple payments are allowed per transaction (split payments).
ColumnTypeNotes
idserialPrimary key
transaccion_idintegerFK → transacciones.id (cascade delete)
metodo_pagovarchar(20)'efectivo' or 'qr'
montonumeric(10,2)Amount to pay
monto_recibidonumeric(10,2)Cash received (cash payments only)
referencia_qrvarchar(100)QR transaction reference (nullable)
usuario_idtextFK → usuarios.id
creado_entimestamptz
actualizado_entimestamptz
borrado_entimestamptzSoft-delete timestamp
cambio (change due) is not stored. Compute it as monto_recibido - monto for cash payments.

Relationships

usuarios
  ├── caja_turno.usuario_id
  ├── gastos_caja.usuario_id
  ├── transacciones.usuario_id
  └── pagos.usuario_id

caja_turno
  ├── gastos_caja.caja_id  (cascade delete)
  └── transacciones.caja_id

transacciones
  ├── detalle_items.transaccion_id  (cascade delete)
  └── pagos.transaccion_id          (cascade delete)

detalle_items
  ├── detalle_item_extras.detalle_item_id  (cascade delete)
  ├── productos.producto_id
  └── platos.plato_id

detalle_item_extras
  └── ingredientes.ingrediente_id

platos
  └── plato_ingredientes.plato_id  (cascade delete)

ingredientes
  └── plato_ingredientes.ingrediente_id

Build docs developers (and LLMs) love