Base de datos

Este inventario se genera desde la base de datos real (information_schema / pg_catalog de Supabase staging, PostgreSQL 17.6), no de migraciones ni de memoria. Fuente: docs/DATABASE_ERD.md.

iFondita — ERD / Inventario del schema Supabase (generado del schema VIVO)

Fecha: 2026-07-13 · Regenerado desde el schema desplegado (staging Supabase pdfkobkatghthmjgdkov, PostgreSQL 17.6). Reemplaza el ERD anterior (obsoleto, pre-Bloque B). Este documento se genera leyendo information_schema / pg_catalog de la DB real, no de migraciones ni de memoria.

  • 60 tablas · 70 claves foráneas (FK) · 41 CHECK constraints · 19 UNIQUE constraints.
  • Todas las PK son id uuid DEFAULT gen_random_uuid() salvo que se indique. Timestamps *_at son timestamptz DEFAULT now().
  • El pos-backend se conecta como service_role (bypassa RLS) y aísla por restaurant_id en middleware (requireRestaurant). La DB tiene RLS habilitado (Modelo-1: authenticated filtrado por auth.uid()), relevante para el acceso directo del dashboard-owner vía Supabase Auth.
  • Contrato clave del dinero: orders.payment_details (jsonb) con lines[] de métodos-hoja (cash|card|transfer|online) es la fuente de verdad del cobro. Ver api/openapi.yaml.

Anotación de estado (post-Bloque B, 8/8): el núcleo orders/order_items/tables/cash_sessions fue reconciliado en A0-A2; el auth de owner (restaurants.auth_user_id) migró a Supabase Auth en pieza D. Tablas retiradas del linaje viejo (restaurant_staff, restaurant_sessions, staff, cash_register, daily_closings, tickets, tablecloth_*) ya no existen en este schema.


Diagrama de relaciones (FKs)

Notación: A ||--o{ B : "col" = B.col referencia a A.id. La anotación (cascade|set null|restrict) indica la regla ON DELETE cuando no es NO ACTION.


Inventario de tablas por dominio

Plataforma, Auth y Onboarding

restaurants

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
auth_user_iduuidNO“
owner_nametextNO“
restaurant_nametextNO“
slugtextNO“
phonetextNO“
emailtext“
addresstextNO“
coloniatext“
geogeographyNO“
zone_iduuid“→ zones.id
photo_urltext“
logo_urltext“
food_typestext[]'{}'::text[]
service_modeltext'a_la_carta'::text
business_hoursjsonbNO“
tiertext'bronze'::text
avg_ticketnumeric(10,2)“
is_activebooleantrue
contract_statustext'pending'::text
onboarding_stepinteger0
onboarding_completed_attimestamptz“
referral_codetext“
force_logoutbooleanfalse
deletion_requested_attimestamptz“
is_deletedbooleanfalse
created_attimestamptznow()
updated_attimestamptznow()
owner_emailtext“
last_logintimestamptz“
plantext'free'::text
totp_secrettext“
totp_activobooleanfalse
totp_recovery_codesjsonb'[]'::jsonb
cerrado_hoybooleanfalse
cerrado_hoy_razontext“
cerrado_hoy_hastatimestamptz“
logo_thumb_urltext“
carta_plantilla_idinteger1
carta_coloresjsonb“
mantel_styletext“
social_linksjsonb'{}'::jsonb
fidelidad_activobooleantrue
fidelidad_configjsonb'{"recompensa": "Un guisado gratis", "como_…
propinas_activobooleanfalse
propinas_opcionesjsonb“
propinas_modotext“
metodos_pago_configjsonb“
spei_clabetext“
spei_bancotext“
spei_beneficiariotext“
fiscal_rfctext“
fiscal_razon_socialtext“
fiscal_regimentext“
fiscal_cptext“
pac_providertext“
pac_api_keytext“
pac_api_secrettext“
cfdi_activobooleanfalse
jwt_secrettext“
rappi_hmac_secrettext“
rappi_store_idtext“
uber_eats_hmac_secrettext“
uber_eats_store_idtext“
whatsapp_creditos_usadosinteger0
mesa_margen_capacidadinteger“
calletext“
numero_exttext“
ciudadtext“
cptext“
descripciontext“
foto_portada_urltext“

operators

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
nametextNO“
pin_hashtextNO“
roletextNO'mesero'::text
nivelintegerNO4
activebooleanNOtrue
emailtext“
password_hashtext“
twilio_phonetext“
otp_codetext“
otp_expires_attimestamptz“
created_attimestamptzNOnow()

admin_users

PK: id · filas (staging): 1

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
auth_user_iduuidNO“
roletextNO“
display_nametextNO“
emailtextNO“
phonetext“
assigned_zonesuuid[]'{}'::uuid[]
is_activebooleantrue
two_factor_enabledbooleanfalse
created_attimestamptznow()

fogon_users

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
emailtextNO“
password_hashtextNO“
nombretext“
roltextNO'admin'::text
modulosjsonbNO'["all"]'::jsonb
totp_secrettext“
totp_activobooleanNOfalse
totp_recovery_codesjsonbNO'[]'::jsonb
activobooleanNOtrue
created_attimestamptzNOnow()

active_sessions

PK: id · filas (staging): 0

Nota: DORMANT — tabla de auditoría de sesiones; 0 filas, sin escritura activa desde el pos-backend tras retirar restaurant_sessions.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
user_iduuidNO“
user_typetextNO“
device_idtext“
device_infojsonb“
is_revokedbooleanfalse
last_active_attimestamptznow()
created_attimestamptznow()

rate_limits

PK: id · filas (staging): 1

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
action_typetextNO“
identifiertextNO“
attemptsinteger1
first_attempt_attimestamptznow()
blocked_untiltimestamptz“

app_versions

PK: id · filas (staging): 0

Nota: Config de versiones de app móvil (force-update). Sin superficie POS activa.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
platformtextNO“
min_versiontextNO“
recommended_versiontextNO“
force_updatebooleanfalse
store_urltext“
updated_attimestamptznow()

onboarding_progress

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
step_accountbooleanNOtrue
step_profilebooleanNOfalse
step_menubooleanNOfalse
step_tablesbooleanNOfalse
step_tableclothbooleanNOfalse
step_devicebooleanNOfalse
step_first_orderbooleanNOfalse
dismissedbooleanNOfalse
completed_attimestamptz“
created_attimestamptzNOnow()
updated_attimestamptzNOnow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
tipotextNO“
versiontextNO“
titletext“
contenidotextNO“
resumen_cambiostext“
activobooleanNOfalse
es_cambio_materialbooleanNOfalse
created_byuuid“→ fogon_users.id (set null)
created_attimestamptzNOnow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
document_iduuidNO“→ legal_documents.id (cascade)
usuario_tipotextNO'anonimo'::text
usuario_iduuid“
iptext“
device_fingerprinttext“
accepted_attimestamptzNOnow()

deploy_log

PK: id · filas (staging): 0

Nota: Bitácora de deploys (panel Fogón system-health). Operacional, no de negocio.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
ambientetext“
resultadotext“
deploado_portext“
created_attimestamptzNOnow()

POS — Núcleo (órdenes, mesas, caja)

orders

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
table_iduuid“→ tables.id
order_numberintegerNOnextval('orders_order_number_seq'::regclass)
order_typetextNO'dine_in'::text
statustextNO'open'::text
guest_countinteger1
subtotalnumeric(10,2)0
tax_amountnumeric(10,2)0
discount_amountnumeric(10,2)0
discount_reasontext“
tip_amountnumeric(10,2)0
tip_percentagenumeric(5,2)“
totalnumeric(10,2)0
payment_methodtext“
payment_detailsjsonb“
notestext“
is_splitbooleanfalse
parent_order_iduuid“→ orders.id
cc_pickup_timetimestamptz“
cc_statustext“
paid_attimestamptz“
cancelled_attimestamptz“
cancellation_reasontext“
created_attimestamptznow()
updated_attimestamptznow()

order_items

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
order_iduuidNO“→ orders.id (cascade)
menu_item_iduuidNO“→ menu_items.id
quantityintegerNO1
unit_pricenumeric(10,2)NO“
modifiersjsonb'[]'::jsonb
notestext“
combo_iduuid“→ combos.id
combo_slottext“
kds_statustext'pending'::text
kds_started_attimestamptz“
kds_ready_attimestamptz“
sent_to_kds_attimestamptz“
created_attimestamptznow()

tables

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
nametextNO“
sectiontext“
capacityjsonbNO'{"chairs": 4}'::jsonb
positionjsonb“
statustext'free'::text
current_order_iduuid“
status_changed_attimestamptznow()
is_activebooleantrue
created_attimestamptznow()

cash_sessions

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
opened_byuuidNO“
closed_byuuid“
opening_amountnumeric(10,2)NO“
expected_cashnumeric(10,2)“
actual_cashnumeric(10,2)“
differencenumeric(10,2)“
total_salesnumeric(10,2)“
total_ordersinteger“
total_cancellationsinteger0
total_tipsnumeric(10,2)0
by_payment_methodjsonb“
top_itemsjsonb“
qr_scans_todayinteger0
reservations_summaryjsonb“
opened_attimestamptznow()
closed_attimestamptz“

restaurant_floors

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
numerointegerNO1
nombretextNO'Planta baja'::text
anchointegerNO800
altointegerNO600
activobooleanNOtrue
created_attimestamptzNOnow()

restaurant_services

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
click_collect_enabledbooleanfalse
cc_prep_time_minutesinteger20
cc_max_concurrent_ordersinteger5
cc_custom_hoursjsonb“
cc_allows_buffetbooleanfalse
reservations_enabledbooleanfalse
res_max_advance_daysinteger7
res_tolerance_minutesinteger15
res_min_party_sizeinteger1
res_max_party_sizeinteger8
res_confirmation_modetext'auto'::text
res_require_phonebooleantrue
loyalty_enabledbooleantrue
loyalty_rewardtext'dessert_free'::text
loyalty_reward_customtext“
updated_attimestamptznow()

restaurant_blocked_dates

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
blocked_datedateNO“
reasontext“

Menú, Modificadores y Precios

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
category_iduuid“→ menu_categories.id
nametextNO“
descriptiontext“
pricenumeric(10,2)NO“
photo_urltext“
is_availablebooleantrue
is_daily_specialbooleanfalse
tagstext[]'{}'::text[]
allergenstext[]'{}'::text[]
prep_time_minutesinteger15
display_orderinteger0
is_activebooleantrue
created_attimestamptznow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
nametextNO“
display_orderinteger0
is_activebooleantrue

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
nametextNO“
created_attimestamptzNOnow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
nombretextNO“
tipotextNO'opcional'::text
selecciontextNO'unico'::text
min_seleccionintegerNO0
max_seleccionintegerNO1
activobooleanNOtrue
created_attimestamptzNOnow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
group_iduuidNO“→ menu_item_modifier_groups.id (cascade)
nombretextNO“
precio_extranumericNO0
activobooleanNOtrue
created_attimestamptzNOnow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
menu_item_iduuidNO“→ menu_items.id (cascade)
group_iduuidNO“→ menu_item_modifier_groups.id (cascade)
created_attimestamptzNOnow()

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
menu_item_iduuid“→ menu_items.id (set null)
operator_iduuid“→ operators.id (set null)
actiontextNO“
porciones_restantesinteger“
motivotext“
created_attimestamptzNOnow()

combos

PK: id · filas (staging): 0

Nota: Combos / comida corrida. Schema listo.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
nametextNO“
pricenumeric(10,2)NO“
combo_typetext'comida_corrida'::text
is_activebooleantrue
created_attimestamptznow()

combo_slots

PK: id · filas (staging): 0

Nota: Slots de combo. Schema listo.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
combo_iduuidNO“→ combos.id
slot_nametextNO“
display_orderinteger0
min_selectionsinteger1
max_selectionsinteger1

combo_slot_options

PK: id · filas (staging): 0

Nota: Opciones por slot de combo. Schema listo.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
combo_slot_iduuidNO“→ combo_slots.id
menu_item_iduuidNO“→ menu_items.id
extra_pricenumeric(10,2)0
is_availablebooleantrue

daily_menus

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
datedateNO“
pricenumeric“
is_publishedbooleanNOfalse
created_attimestamptzNOnow()

daily_menu_items

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
daily_menu_iduuidNO“→ daily_menus.id (cascade)
menu_item_iduuidNO“→ menu_items.id (cascade)
group_iduuid“→ menu_groups.id (set null)
sort_orderintegerNO0
is_activebooleanNOtrue
created_attimestamptzNOnow()

price_schemes

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
nombretext“
tipotextNO'porcentaje'::text
descuento_porcentajenumericNO0
horario_iniciotime“
horario_fintime“
activobooleanNOtrue
created_attimestamptzNOnow()

price_scheme_items

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
price_scheme_iduuidNO“→ price_schemes.id (cascade)
menu_item_iduuidNO“→ menu_items.id (cascade)
precio_especialnumericNO“

buffet_configs

PK: id · filas (staging): 0

Nota: Buffet: modelo de servicio soportado en schema; superficie de cobro por buffet aún no cableada al POS.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
nametext'Buffet'::text
price_adultnumeric(10,2)NO“
price_childnumeric(10,2)“
price_seniornumeric(10,2)“
includestext“
start_timetime“
end_timetime“
is_activebooleantrue

import_logs

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
operator_iduuid“→ operators.id (set null)
filenametext“
total_rowsinteger“
importedintegerNO0
errorsintegerNO0
duplicatesintegerNO0
statustextNO'processing'::text
error_detailsjsonb“
created_attimestamptzNOnow()
completed_attimestamptz“

Inventario y Recetas

inventory_items

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
nametextNO“
unittextNO“
current_stocknumeric(10,3)0
min_stocknumeric(10,3)0
cost_per_unitnumeric(10,2)“
last_restocked_attimestamptz“
created_attimestamptznow()

inventory_movements

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
inventory_item_iduuidNO“→ inventory_items.id
movement_typetextNO“
quantitynumeric(10,3)NO“
notestext“
created_byuuid“
created_attimestamptznow()

recipes

PK: id · filas (staging): 0

Nota: Recetas → escandallo de inventario. Best-effort (no bloquea cobros).

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
menu_item_iduuidNO“→ menu_items.id
restaurant_iduuidNO“→ restaurants.id

recipe_ingredients

PK: id · filas (staging): 0

Nota: Ingredientes por receta. Best-effort.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
recipe_iduuidNO“→ recipes.id
ingredient_iduuidNO“→ inventory_items.id
quantitynumeric(10,3)NO“
unittextNO“

Fiscal, Facturación y Gastos

fiscal_invoices

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
folio_seqbigintNO“
restaurant_iduuidNO“→ restaurants.id (restrict)
tipotextNO“
conceptotext“
subtotalnumeric(10,2)NO“
ivanumeric(10,2)NO“
totalnumeric(10,2)NO“
estadotextNO'pendiente'::text
fecha_emisiontimestamptzNOnow()
fecha_vencimientotimestamptz“
foliotext“
created_attimestamptzNOnow()

invoice_items

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
invoice_iduuidNO“→ fiscal_invoices.id (cascade)
descripciontextNO“
cantidadintegerNO1
precio_unitarionumeric(10,2)NO“
subtotalnumeric(10,2)NO“
created_attimestamptzNOnow()

fiscal_closes

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id
folio_numberintegerNO“
close_datedateNO“
total_salesnumeric(12,2)NO“
taxable_salesnumeric(12,2)NO“
tax_amountnumeric(12,2)NO“
cash_totalnumeric(12,2)0
card_totalnumeric(12,2)0
transfer_totalnumeric(12,2)0
total_ordersintegerNO“
total_cancellationsinteger0
total_tipsnumeric(12,2)0
closed_byuuidNO“
pdf_urltext“
created_attimestamptznow()

expenses

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
amountnumeric(10,2)NO“
categorytextNO'otro'::text
descriptiontext“
suppliertext“
notetext“
datedateNOCURRENT_DATE
created_byuuid“→ operators.id (set null)
created_attimestamptzNOnow()

Fidelidad, CRM y Recepción

loyalty_customers

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
phonetextNO“
nametext“
stampsintegerNO0
total_redeemedintegerNO0
aprovecho_user_iduuid“
created_attimestamptzNOnow()
updated_attimestamptzNOnow()

loyalty_history

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
customer_iduuidNO“→ loyalty_customers.id (cascade)
order_iduuid“
actiontextNO“
stamps_changeintegerNO“
created_byuuid“→ operators.id (set null)
created_attimestamptzNOnow()

crm_interactions

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuid“→ restaurants.id (cascade)
prospecto_iduuid“
tipotextNO“
direcciontextNO'saliente'::text
resumentextNO“
resultadotext“
siguiente_acciontext“
siguiente_fechadate“
created_byuuid“→ fogon_users.id (set null)
created_attimestamptzNOnow()

waitlist

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
nombretextNO“
personasintegerNO“
telefonotext“
fuentetextNO'manual'::text
estadotextNO'esperando'::text
hora_llegadatimestamptzNOnow()
hora_asignaciontimestamptz“
mesa_iduuid“→ tables.id (set null)

cupones

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
codigotextNO“
tipotextNO“
valornumericNO“
usos_maximosinteger“
usos_actualesintegerNO0
fecha_findate“
descripciontext“
activobooleanNOtrue
created_attimestamptzNOnow()

Publicidad, Manteles y Crecimiento (superficie E)

advertisers

PK: id · filas (staging): 1

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
emailtextNO“
nametextNO“
companytextNO“
phonetext“
statustextNO'active'::text
created_attimestamptznow()
auth_user_iduuid“
logo_urltext“

campaigns

PK: id · filas (staging): 4

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
advertiser_iduuidNO“→ advertisers.id
nametextNO“
packagetextNO“
total_costnumericNO“
statustextNO'draft'::text
qr_code_urltext“
qr_scansintegerNO0
created_attimestamptznow()
expires_attimestamptz“

invoices

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
advertiser_iduuidNO“→ advertisers.id
campaign_iduuidNO“→ campaigns.id
amountnumericNO“
statustextNO'pending'::text
stripe_payment_intent_idtext“
created_attimestamptznow()

qr_scans

PK: id · filas (staging): 1

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
campaign_iduuidNO“→ campaigns.id
locationtextNO“
restaurant_iduuid“
scanned_attimestamptznow()
user_iduuid“

napkins

PK: id · filas (staging): 0

Nota: Ads Fase 2 (servilletas impresas). Schema listo; flujo diferido.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
created_by_admin_iduuidNO“
nametextNO“
anuncio_idsuuid[]'{}'::uuid[]
quantityintegerNO0
statustextNO'draft'::text
pdf_urltext“
created_attimestamptznow()

napkin_distributions

PK: id · filas (staging): 0

Nota: Ads Fase 2 (distribución de servilletas). Diferido.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
napkin_iduuidNO“→ napkins.id
restaurant_iduuidNO“→ restaurants.id
statustextNO'pending'::text
distributed_attimestamptz“
received_attimestamptz“
created_attimestamptznow()

promoters

PK: id · filas (staging): 0

Nota: Crecimiento: promotores de zona. Schema listo; sin flujo activo.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
nametextNO“
phonetextNO“
referral_codetextNO“
commission_per_activationnumeric(10,2)300.00
assigned_zonestext[]'{}'::text[]
is_activebooleantrue
created_attimestamptznow()

referrals

PK: id · filas (staging): 0

Nota: Crecimiento: referidos multi-tipo. Schema listo; sin flujo activo.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
referrer_typetextNO“
referrer_iduuidNO“
referred_typetextNO“
referred_iduuid“
referral_codetextNO“
statustext'pending'::text
created_attimestamptznow()

mantel_designs

PK: id · filas (staging): 0

Nota: Editor de manteles (Fogón). Activo en panel Fogón.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
nombretextNO“
configjsonbNO'{}'::jsonb
preview_urltext“
estadotextNO'borrador'::text
created_attimestamptzNOnow()

mantel_stock

PK: id · filas (staging): 0

Nota: Inventario de manteles físicos. Fase 1 manteles.

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
stock_actualintegerNO0
stock_minimointegerNO200
ultimo_reabastecimientotimestamptz“
updated_attimestamptzNOnow()

solicitudes_manteles

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
plantextNO'basico'::text
cantidadintegerNO50
estadotextNO'solicitado'::text
archivo_arte_urltext“
brieftext“
notas_clientetext“
tracking_numbertext“
fecha_estimada_entregadate“
revision_comentariotext“
revision_attimestamptz“
created_attimestamptzNOnow()

Notificaciones y Zonas

push_subscriptions

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
restaurant_iduuidNO“→ restaurants.id (cascade)
endpointtextNO“
p256dhtextNO“
authtextNO“
user_agenttext“
activobooleanNOtrue
created_attimestamptzNOnow()

zones

PK: id · filas (staging): 0

ColumnaTipoNullDefaultReferencia (FK)
id 🔑uuidNOgen_random_uuid()
nametextNO“
slugtextNO“
polygongeography“
statustext'planned'::text
min_restaurantsinteger5
created_attimestamptznow()

CHECK constraints (enumeraciones y validaciones)

El schema no usa tipos ENUM nativos; los dominios cerrados se modelan con CHECK (col = ANY(ARRAY[...])). Valores permitidos reales:

TablaColumna(s)Valores / regla
active_sessionsuser_typerestaurant, advertiser, comensal, admin
admin_usersrolesuper_admin, admin_ops, admin_commercial, admin_content, support, promoter, viewer
advertisersstatusactive, inactive
app_versionsplatformios, android, web
campaignspackagebasic, zone, premium
campaignsstatusdraft, active, paused, completed
comboscombo_typecomida_corrida, desayuno, cena, promo
cupones—CHECK (((tipo <> 'porcentaje') OR (valor <= (100))))
cuponestipoporcentaje, monto_fijo
cupones—CHECK ((valor > (0)))
expenses—CHECK ((amount > (0)))
expensescategorymercado, gas, limpieza, servicios, nomina, otro
fiscal_invoicesestadopendiente, pagada, vencida, cancelada
fogon_usersroladmin, superadmin, soporte, finanzas, TI
import_logsstatusprocessing, completed, failed
inventory_movementsmovement_typepurchase, consumption, adjustment, waste
invoicesstatuspending, paid, failed
loyalty_historyactionstamp, redeem
menu_86_logaction86, restore
menu_item_modifier_groupsseleccionunico, multiple
menu_item_modifier_groupstipoopcional, obligatorio
napkin_distributionsstatuspending, received, in_use, exhausted
napkinsstatusdraft, pending_print, printed, distributed
operators—CHECK (((nivel >= 1) AND (nivel <= 5)))
operatorsroleduena, mesero, cocina, caja, repartidor
order_itemskds_statuspending, preparing, ready, served, cancelled
orderscc_statuspending, accepted, preparing, ready, picked_up, no_show, cancelled
ordersorder_typedine_in, takeout, click_collect, delivery
orderspayment_methodcash, card, transfer, mixed, online
ordersstatusopen, preparing, ready, served, paid, cancelled
referralsreferred_typerestaurant, advertiser, comensal
referralsreferrer_typepromoter, restaurant, advertiser, comensal
referralsstatuspending, registered, activated, paid
restaurant_servicesres_confirmation_modeauto, manual
restaurantscontract_statuspending, active, suspended, terminated
restaurantsplanfree, basic, pro
restaurantsservice_modela_la_carta, comida_corrida, buffet, mixed
restaurantstierbronze, silver, gold
tablesstatusfree, occupied, served, check_requested, dirty, reserved
waitlistestadoesperando, asignado, expirado, no_show, cancelada
zonesstatusplanned, prospecting, active, paused

UNIQUE constraints e índices parciales relevantes

TablaConstraint / índiceDefinición
admin_usersadmin_users_auth_user_id_keyUNIQUE (auth_user_id)
advertisersadvertisers_email_keyUNIQUE (email)
advertisersadvertisers_auth_user_id_keyUNIQUE (auth_user_id)
cuponescupones_restaurant_id_codigo_keyUNIQUE (restaurant_id, codigo)
daily_menusdaily_menus_restaurant_id_date_keyUNIQUE (restaurant_id, date)
fiscal_closesfiscal_closes_restaurant_id_folio_number_keyUNIQUE (restaurant_id, folio_number)
fiscal_invoicesfiscal_invoices_folio_keyUNIQUE (folio)
loyalty_customersloyalty_customers_restaurant_id_phone_keyUNIQUE (restaurant_id, phone)
mantel_stockmantel_stock_restaurant_id_keyUNIQUE (restaurant_id)
menu_item_modifier_group_linksmenu_item_modifier_group_links_menu_item_id_group_id_keyUNIQUE (menu_item_id, group_id)
onboarding_progressonboarding_progress_restaurant_id_keyUNIQUE (restaurant_id)
promoterspromoters_referral_code_keyUNIQUE (referral_code)
push_subscriptionspush_subscriptions_restaurant_id_endpoint_keyUNIQUE (restaurant_id, endpoint)
restaurant_blocked_datesrestaurant_blocked_dates_restaurant_id_blocked_date_keyUNIQUE (restaurant_id, blocked_date)
restaurant_servicesrestaurant_services_restaurant_id_keyUNIQUE (restaurant_id)
restaurantsrestaurants_slug_keyUNIQUE (slug)
restaurantsrestaurants_referral_code_keyUNIQUE (referral_code)
restaurantsrestaurants_auth_user_id_keyUNIQUE (auth_user_id)
zoneszones_slug_keyUNIQUE (slug)
cash_sessionscash_sessions_one_open_per_restaurant (UNIQUE parcial)... USING btree (restaurant_id) WHERE (closed_at IS NULL)
operatorsidx_operators_email (UNIQUE parcial)... USING btree (restaurant_id, lower(email)) WHERE (email IS NOT NULL)
ordersidx_orders_table (parcial)... USING btree (table_id) WHERE (status = 'open'::text)
restaurantsidx_restaurants_zone (parcial)... USING btree (zone_id) WHERE (is_active = true)

cash_sessions_one_open_per_restaurant (UNIQUE parcial WHERE closed_at IS NULL) es el candado de A0-A2 T5: garantiza una sola caja abierta por fonda. Dos POST /api/cash/open concurrentes → el segundo recibe 23505 traducido a 400 Ya hay una caja abierta.


Notas de estado: DORMANT, diferidas y deuda técnica

  • active_sessions — DORMANT. 0 filas; la auditoría vía restaurant_sessions fue retirada (arregla issue #101). Se conserva como superficie futura de gestión de sesiones/dispositivos.
  • Sin tabla reservations — el router reception.ts expone /reservas, pero las reservas no tienen tabla propia en este schema (superficie de reservas parcialmente diferida). waitlist sí es real.
  • Sin tablas de comensal/Aprovecho en public — los usuarios Aprovecho viven en Supabase Auth / otra capa; loyalty_customers.aprovecho_user_id es el puente. qr_scans.user_id es nullable por lo mismo.
  • CFDI (fiscal_invoices/invoice_items) e inventario (recipes/recipe_ingredients) — schema presente; los flujos son best-effort o diferidos (F1 CFDI sin demanda real; inventario no bloquea cobros).
  • Ads Fase 2 (napkins, napkin_distributions, promoters, referrals) — schema listo, flujo diferido. La superficie E activa hoy es QR/paquete (campaigns + qr_scans).
  • restaurants es amplia (72 columnas) — concentra config de fonda, fiscal, delivery (rappi/uber HMAC), 2FA (totp_*), fidelidad y auth (auth_user_id → Supabase). Deuda conocida: la entidad restaurants estuvo triplicada históricamente (PG UUID + Supabase UUID + migraciones SERIAL); este schema es el UUID canónico.
  • operators.role (duena|mesero|cocina|caja|repartidor) alimenta requireRole. operatorId sufre un type-lie conocido (middleware lo tipa number, en runtime es el uuid de operators.id); cash.ts lo coacciona a string localmente.

Generado automáticamente desde information_schema/pg_catalog de la DB desplegada. Para regenerar: extraer los TSV de schema y correr el generador. 60 tablas verificadas.