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*_atsontimestamptz DEFAULT now(). - El pos-backend se conecta como
service_role(bypassa RLS) y aísla porrestaurant_iden middleware (requireRestaurant). La DB tiene RLS habilitado (Modelo-1:authenticatedfiltrado porauth.uid()), relevante para el acceso directo del dashboard-owner vía Supabase Auth. - Contrato clave del dinero:
orders.payment_details(jsonb) conlines[]de métodos-hoja (cash|card|transfer|online) es la fuente de verdad del cobro. Verapi/openapi.yaml.
Anotación de estado (post-Bloque B, 8/8): el núcleo
orders/order_items/tables/cash_sessionsfue 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.colreferencia aA.id. La anotación(cascade|set null|restrict)indica la regla ON DELETE cuando no esNO ACTION.
Inventario de tablas por dominio
Plataforma, Auth y Onboarding
restaurants
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
auth_user_id | uuid | NO | “ | |
owner_name | text | NO | “ | |
restaurant_name | text | NO | “ | |
slug | text | NO | “ | |
phone | text | NO | “ | |
email | text | “ | ||
address | text | NO | “ | |
colonia | text | “ | ||
geo | geography | NO | “ | |
zone_id | uuid | “ | → zones.id | |
photo_url | text | “ | ||
logo_url | text | “ | ||
food_types | text[] | '{}'::text[] | ||
service_model | text | 'a_la_carta'::text | ||
business_hours | jsonb | NO | “ | |
tier | text | 'bronze'::text | ||
avg_ticket | numeric(10,2) | “ | ||
is_active | boolean | true | ||
contract_status | text | 'pending'::text | ||
onboarding_step | integer | 0 | ||
onboarding_completed_at | timestamptz | “ | ||
referral_code | text | “ | ||
force_logout | boolean | false | ||
deletion_requested_at | timestamptz | “ | ||
is_deleted | boolean | false | ||
created_at | timestamptz | now() | ||
updated_at | timestamptz | now() | ||
owner_email | text | “ | ||
last_login | timestamptz | “ | ||
plan | text | 'free'::text | ||
totp_secret | text | “ | ||
totp_activo | boolean | false | ||
totp_recovery_codes | jsonb | '[]'::jsonb | ||
cerrado_hoy | boolean | false | ||
cerrado_hoy_razon | text | “ | ||
cerrado_hoy_hasta | timestamptz | “ | ||
logo_thumb_url | text | “ | ||
carta_plantilla_id | integer | 1 | ||
carta_colores | jsonb | “ | ||
mantel_style | text | “ | ||
social_links | jsonb | '{}'::jsonb | ||
fidelidad_activo | boolean | true | ||
fidelidad_config | jsonb | '{"recompensa": "Un guisado gratis", "como_… | ||
propinas_activo | boolean | false | ||
propinas_opciones | jsonb | “ | ||
propinas_modo | text | “ | ||
metodos_pago_config | jsonb | “ | ||
spei_clabe | text | “ | ||
spei_banco | text | “ | ||
spei_beneficiario | text | “ | ||
fiscal_rfc | text | “ | ||
fiscal_razon_social | text | “ | ||
fiscal_regimen | text | “ | ||
fiscal_cp | text | “ | ||
pac_provider | text | “ | ||
pac_api_key | text | “ | ||
pac_api_secret | text | “ | ||
cfdi_activo | boolean | false | ||
jwt_secret | text | “ | ||
rappi_hmac_secret | text | “ | ||
rappi_store_id | text | “ | ||
uber_eats_hmac_secret | text | “ | ||
uber_eats_store_id | text | “ | ||
whatsapp_creditos_usados | integer | 0 | ||
mesa_margen_capacidad | integer | “ | ||
calle | text | “ | ||
numero_ext | text | “ | ||
ciudad | text | “ | ||
cp | text | “ | ||
descripcion | text | “ | ||
foto_portada_url | text | “ |
operators
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
name | text | NO | “ | |
pin_hash | text | NO | “ | |
role | text | NO | 'mesero'::text | |
nivel | integer | NO | 4 | |
active | boolean | NO | true | |
email | text | “ | ||
password_hash | text | “ | ||
twilio_phone | text | “ | ||
otp_code | text | “ | ||
otp_expires_at | timestamptz | “ | ||
created_at | timestamptz | NO | now() |
admin_users
PK: id · filas (staging): 1
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
auth_user_id | uuid | NO | “ | |
role | text | NO | “ | |
display_name | text | NO | “ | |
email | text | NO | “ | |
phone | text | “ | ||
assigned_zones | uuid[] | '{}'::uuid[] | ||
is_active | boolean | true | ||
two_factor_enabled | boolean | false | ||
created_at | timestamptz | now() |
fogon_users
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
email | text | NO | “ | |
password_hash | text | NO | “ | |
nombre | text | “ | ||
rol | text | NO | 'admin'::text | |
modulos | jsonb | NO | '["all"]'::jsonb | |
totp_secret | text | “ | ||
totp_activo | boolean | NO | false | |
totp_recovery_codes | jsonb | NO | '[]'::jsonb | |
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
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.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
user_id | uuid | NO | “ | |
user_type | text | NO | “ | |
device_id | text | “ | ||
device_info | jsonb | “ | ||
is_revoked | boolean | false | ||
last_active_at | timestamptz | now() | ||
created_at | timestamptz | now() |
rate_limits
PK: id · filas (staging): 1
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
action_type | text | NO | “ | |
identifier | text | NO | “ | |
attempts | integer | 1 | ||
first_attempt_at | timestamptz | now() | ||
blocked_until | timestamptz | “ |
app_versions
PK: id · filas (staging): 0
Nota: Config de versiones de app móvil (force-update). Sin superficie POS activa.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
platform | text | NO | “ | |
min_version | text | NO | “ | |
recommended_version | text | NO | “ | |
force_update | boolean | false | ||
store_url | text | “ | ||
updated_at | timestamptz | now() |
onboarding_progress
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
step_account | boolean | NO | true | |
step_profile | boolean | NO | false | |
step_menu | boolean | NO | false | |
step_tables | boolean | NO | false | |
step_tablecloth | boolean | NO | false | |
step_device | boolean | NO | false | |
step_first_order | boolean | NO | false | |
dismissed | boolean | NO | false | |
completed_at | timestamptz | “ | ||
created_at | timestamptz | NO | now() | |
updated_at | timestamptz | NO | now() |
legal_documents
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
tipo | text | NO | “ | |
version | text | NO | “ | |
title | text | “ | ||
contenido | text | NO | “ | |
resumen_cambios | text | “ | ||
activo | boolean | NO | false | |
es_cambio_material | boolean | NO | false | |
created_by | uuid | “ | → fogon_users.id (set null) | |
created_at | timestamptz | NO | now() |
legal_acceptances
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
document_id | uuid | NO | “ | → legal_documents.id (cascade) |
usuario_tipo | text | NO | 'anonimo'::text | |
usuario_id | uuid | “ | ||
ip | text | “ | ||
device_fingerprint | text | “ | ||
accepted_at | timestamptz | NO | now() |
deploy_log
PK: id · filas (staging): 0
Nota: Bitácora de deploys (panel Fogón system-health). Operacional, no de negocio.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
ambiente | text | “ | ||
resultado | text | “ | ||
deploado_por | text | “ | ||
created_at | timestamptz | NO | now() |
POS — Núcleo (órdenes, mesas, caja)
orders
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
table_id | uuid | “ | → tables.id | |
order_number | integer | NO | nextval('orders_order_number_seq'::regclass) | |
order_type | text | NO | 'dine_in'::text | |
status | text | NO | 'open'::text | |
guest_count | integer | 1 | ||
subtotal | numeric(10,2) | 0 | ||
tax_amount | numeric(10,2) | 0 | ||
discount_amount | numeric(10,2) | 0 | ||
discount_reason | text | “ | ||
tip_amount | numeric(10,2) | 0 | ||
tip_percentage | numeric(5,2) | “ | ||
total | numeric(10,2) | 0 | ||
payment_method | text | “ | ||
payment_details | jsonb | “ | ||
notes | text | “ | ||
is_split | boolean | false | ||
parent_order_id | uuid | “ | → orders.id | |
cc_pickup_time | timestamptz | “ | ||
cc_status | text | “ | ||
paid_at | timestamptz | “ | ||
cancelled_at | timestamptz | “ | ||
cancellation_reason | text | “ | ||
created_at | timestamptz | now() | ||
updated_at | timestamptz | now() |
order_items
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
order_id | uuid | NO | “ | → orders.id (cascade) |
menu_item_id | uuid | NO | “ | → menu_items.id |
quantity | integer | NO | 1 | |
unit_price | numeric(10,2) | NO | “ | |
modifiers | jsonb | '[]'::jsonb | ||
notes | text | “ | ||
combo_id | uuid | “ | → combos.id | |
combo_slot | text | “ | ||
kds_status | text | 'pending'::text | ||
kds_started_at | timestamptz | “ | ||
kds_ready_at | timestamptz | “ | ||
sent_to_kds_at | timestamptz | “ | ||
created_at | timestamptz | now() |
tables
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
name | text | NO | “ | |
section | text | “ | ||
capacity | jsonb | NO | '{"chairs": 4}'::jsonb | |
position | jsonb | “ | ||
status | text | 'free'::text | ||
current_order_id | uuid | “ | ||
status_changed_at | timestamptz | now() | ||
is_active | boolean | true | ||
created_at | timestamptz | now() |
cash_sessions
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
opened_by | uuid | NO | “ | |
closed_by | uuid | “ | ||
opening_amount | numeric(10,2) | NO | “ | |
expected_cash | numeric(10,2) | “ | ||
actual_cash | numeric(10,2) | “ | ||
difference | numeric(10,2) | “ | ||
total_sales | numeric(10,2) | “ | ||
total_orders | integer | “ | ||
total_cancellations | integer | 0 | ||
total_tips | numeric(10,2) | 0 | ||
by_payment_method | jsonb | “ | ||
top_items | jsonb | “ | ||
qr_scans_today | integer | 0 | ||
reservations_summary | jsonb | “ | ||
opened_at | timestamptz | now() | ||
closed_at | timestamptz | “ |
restaurant_floors
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
numero | integer | NO | 1 | |
nombre | text | NO | 'Planta baja'::text | |
ancho | integer | NO | 800 | |
alto | integer | NO | 600 | |
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
restaurant_services
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
click_collect_enabled | boolean | false | ||
cc_prep_time_minutes | integer | 20 | ||
cc_max_concurrent_orders | integer | 5 | ||
cc_custom_hours | jsonb | “ | ||
cc_allows_buffet | boolean | false | ||
reservations_enabled | boolean | false | ||
res_max_advance_days | integer | 7 | ||
res_tolerance_minutes | integer | 15 | ||
res_min_party_size | integer | 1 | ||
res_max_party_size | integer | 8 | ||
res_confirmation_mode | text | 'auto'::text | ||
res_require_phone | boolean | true | ||
loyalty_enabled | boolean | true | ||
loyalty_reward | text | 'dessert_free'::text | ||
loyalty_reward_custom | text | “ | ||
updated_at | timestamptz | now() |
restaurant_blocked_dates
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
blocked_date | date | NO | “ | |
reason | text | “ |
Menú, Modificadores y Precios
menu_items
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
category_id | uuid | “ | → menu_categories.id | |
name | text | NO | “ | |
description | text | “ | ||
price | numeric(10,2) | NO | “ | |
photo_url | text | “ | ||
is_available | boolean | true | ||
is_daily_special | boolean | false | ||
tags | text[] | '{}'::text[] | ||
allergens | text[] | '{}'::text[] | ||
prep_time_minutes | integer | 15 | ||
display_order | integer | 0 | ||
is_active | boolean | true | ||
created_at | timestamptz | now() |
menu_categories
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
name | text | NO | “ | |
display_order | integer | 0 | ||
is_active | boolean | true |
menu_groups
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
name | text | NO | “ | |
created_at | timestamptz | NO | now() |
menu_item_modifier_groups
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
nombre | text | NO | “ | |
tipo | text | NO | 'opcional'::text | |
seleccion | text | NO | 'unico'::text | |
min_seleccion | integer | NO | 0 | |
max_seleccion | integer | NO | 1 | |
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
menu_item_modifiers
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
group_id | uuid | NO | “ | → menu_item_modifier_groups.id (cascade) |
nombre | text | NO | “ | |
precio_extra | numeric | NO | 0 | |
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
menu_item_modifier_group_links
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
menu_item_id | uuid | NO | “ | → menu_items.id (cascade) |
group_id | uuid | NO | “ | → menu_item_modifier_groups.id (cascade) |
created_at | timestamptz | NO | now() |
menu_86_log
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
menu_item_id | uuid | “ | → menu_items.id (set null) | |
operator_id | uuid | “ | → operators.id (set null) | |
action | text | NO | “ | |
porciones_restantes | integer | “ | ||
motivo | text | “ | ||
created_at | timestamptz | NO | now() |
combos
PK: id · filas (staging): 0
Nota: Combos / comida corrida. Schema listo.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
name | text | NO | “ | |
price | numeric(10,2) | NO | “ | |
combo_type | text | 'comida_corrida'::text | ||
is_active | boolean | true | ||
created_at | timestamptz | now() |
combo_slots
PK: id · filas (staging): 0
Nota: Slots de combo. Schema listo.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
combo_id | uuid | NO | “ | → combos.id |
slot_name | text | NO | “ | |
display_order | integer | 0 | ||
min_selections | integer | 1 | ||
max_selections | integer | 1 |
combo_slot_options
PK: id · filas (staging): 0
Nota: Opciones por slot de combo. Schema listo.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
combo_slot_id | uuid | NO | “ | → combo_slots.id |
menu_item_id | uuid | NO | “ | → menu_items.id |
extra_price | numeric(10,2) | 0 | ||
is_available | boolean | true |
daily_menus
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
date | date | NO | “ | |
price | numeric | “ | ||
is_published | boolean | NO | false | |
created_at | timestamptz | NO | now() |
daily_menu_items
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
daily_menu_id | uuid | NO | “ | → daily_menus.id (cascade) |
menu_item_id | uuid | NO | “ | → menu_items.id (cascade) |
group_id | uuid | “ | → menu_groups.id (set null) | |
sort_order | integer | NO | 0 | |
is_active | boolean | NO | true | |
created_at | timestamptz | NO | now() |
price_schemes
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
nombre | text | “ | ||
tipo | text | NO | 'porcentaje'::text | |
descuento_porcentaje | numeric | NO | 0 | |
horario_inicio | time | “ | ||
horario_fin | time | “ | ||
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
price_scheme_items
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
price_scheme_id | uuid | NO | “ | → price_schemes.id (cascade) |
menu_item_id | uuid | NO | “ | → menu_items.id (cascade) |
precio_especial | numeric | NO | “ |
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.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
name | text | 'Buffet'::text | ||
price_adult | numeric(10,2) | NO | “ | |
price_child | numeric(10,2) | “ | ||
price_senior | numeric(10,2) | “ | ||
includes | text | “ | ||
start_time | time | “ | ||
end_time | time | “ | ||
is_active | boolean | true |
import_logs
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
operator_id | uuid | “ | → operators.id (set null) | |
filename | text | “ | ||
total_rows | integer | “ | ||
imported | integer | NO | 0 | |
errors | integer | NO | 0 | |
duplicates | integer | NO | 0 | |
status | text | NO | 'processing'::text | |
error_details | jsonb | “ | ||
created_at | timestamptz | NO | now() | |
completed_at | timestamptz | “ |
Inventario y Recetas
inventory_items
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
name | text | NO | “ | |
unit | text | NO | “ | |
current_stock | numeric(10,3) | 0 | ||
min_stock | numeric(10,3) | 0 | ||
cost_per_unit | numeric(10,2) | “ | ||
last_restocked_at | timestamptz | “ | ||
created_at | timestamptz | now() |
inventory_movements
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
inventory_item_id | uuid | NO | “ | → inventory_items.id |
movement_type | text | NO | “ | |
quantity | numeric(10,3) | NO | “ | |
notes | text | “ | ||
created_by | uuid | “ | ||
created_at | timestamptz | now() |
recipes
PK: id · filas (staging): 0
Nota: Recetas → escandallo de inventario. Best-effort (no bloquea cobros).
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
menu_item_id | uuid | NO | “ | → menu_items.id |
restaurant_id | uuid | NO | “ | → restaurants.id |
recipe_ingredients
PK: id · filas (staging): 0
Nota: Ingredientes por receta. Best-effort.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
recipe_id | uuid | NO | “ | → recipes.id |
ingredient_id | uuid | NO | “ | → inventory_items.id |
quantity | numeric(10,3) | NO | “ | |
unit | text | NO | “ |
Fiscal, Facturación y Gastos
fiscal_invoices
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
folio_seq | bigint | NO | “ | |
restaurant_id | uuid | NO | “ | → restaurants.id (restrict) |
tipo | text | NO | “ | |
concepto | text | “ | ||
subtotal | numeric(10,2) | NO | “ | |
iva | numeric(10,2) | NO | “ | |
total | numeric(10,2) | NO | “ | |
estado | text | NO | 'pendiente'::text | |
fecha_emision | timestamptz | NO | now() | |
fecha_vencimiento | timestamptz | “ | ||
folio | text | “ | ||
created_at | timestamptz | NO | now() |
invoice_items
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
invoice_id | uuid | NO | “ | → fiscal_invoices.id (cascade) |
descripcion | text | NO | “ | |
cantidad | integer | NO | 1 | |
precio_unitario | numeric(10,2) | NO | “ | |
subtotal | numeric(10,2) | NO | “ | |
created_at | timestamptz | NO | now() |
fiscal_closes
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id |
folio_number | integer | NO | “ | |
close_date | date | NO | “ | |
total_sales | numeric(12,2) | NO | “ | |
taxable_sales | numeric(12,2) | NO | “ | |
tax_amount | numeric(12,2) | NO | “ | |
cash_total | numeric(12,2) | 0 | ||
card_total | numeric(12,2) | 0 | ||
transfer_total | numeric(12,2) | 0 | ||
total_orders | integer | NO | “ | |
total_cancellations | integer | 0 | ||
total_tips | numeric(12,2) | 0 | ||
closed_by | uuid | NO | “ | |
pdf_url | text | “ | ||
created_at | timestamptz | now() |
expenses
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
amount | numeric(10,2) | NO | “ | |
category | text | NO | 'otro'::text | |
description | text | “ | ||
supplier | text | “ | ||
note | text | “ | ||
date | date | NO | CURRENT_DATE | |
created_by | uuid | “ | → operators.id (set null) | |
created_at | timestamptz | NO | now() |
Fidelidad, CRM y Recepción
loyalty_customers
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
phone | text | NO | “ | |
name | text | “ | ||
stamps | integer | NO | 0 | |
total_redeemed | integer | NO | 0 | |
aprovecho_user_id | uuid | “ | ||
created_at | timestamptz | NO | now() | |
updated_at | timestamptz | NO | now() |
loyalty_history
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
customer_id | uuid | NO | “ | → loyalty_customers.id (cascade) |
order_id | uuid | “ | ||
action | text | NO | “ | |
stamps_change | integer | NO | “ | |
created_by | uuid | “ | → operators.id (set null) | |
created_at | timestamptz | NO | now() |
crm_interactions
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | “ | → restaurants.id (cascade) | |
prospecto_id | uuid | “ | ||
tipo | text | NO | “ | |
direccion | text | NO | 'saliente'::text | |
resumen | text | NO | “ | |
resultado | text | “ | ||
siguiente_accion | text | “ | ||
siguiente_fecha | date | “ | ||
created_by | uuid | “ | → fogon_users.id (set null) | |
created_at | timestamptz | NO | now() |
waitlist
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
nombre | text | NO | “ | |
personas | integer | NO | “ | |
telefono | text | “ | ||
fuente | text | NO | 'manual'::text | |
estado | text | NO | 'esperando'::text | |
hora_llegada | timestamptz | NO | now() | |
hora_asignacion | timestamptz | “ | ||
mesa_id | uuid | “ | → tables.id (set null) |
cupones
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
codigo | text | NO | “ | |
tipo | text | NO | “ | |
valor | numeric | NO | “ | |
usos_maximos | integer | “ | ||
usos_actuales | integer | NO | 0 | |
fecha_fin | date | “ | ||
descripcion | text | “ | ||
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
Publicidad, Manteles y Crecimiento (superficie E)
advertisers
PK: id · filas (staging): 1
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
email | text | NO | “ | |
name | text | NO | “ | |
company | text | NO | “ | |
phone | text | “ | ||
status | text | NO | 'active'::text | |
created_at | timestamptz | now() | ||
auth_user_id | uuid | “ | ||
logo_url | text | “ |
campaigns
PK: id · filas (staging): 4
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
advertiser_id | uuid | NO | “ | → advertisers.id |
name | text | NO | “ | |
package | text | NO | “ | |
total_cost | numeric | NO | “ | |
status | text | NO | 'draft'::text | |
qr_code_url | text | “ | ||
qr_scans | integer | NO | 0 | |
created_at | timestamptz | now() | ||
expires_at | timestamptz | “ |
invoices
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
advertiser_id | uuid | NO | “ | → advertisers.id |
campaign_id | uuid | NO | “ | → campaigns.id |
amount | numeric | NO | “ | |
status | text | NO | 'pending'::text | |
stripe_payment_intent_id | text | “ | ||
created_at | timestamptz | now() |
qr_scans
PK: id · filas (staging): 1
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
campaign_id | uuid | NO | “ | → campaigns.id |
location | text | NO | “ | |
restaurant_id | uuid | “ | ||
scanned_at | timestamptz | now() | ||
user_id | uuid | “ |
napkins
PK: id · filas (staging): 0
Nota: Ads Fase 2 (servilletas impresas). Schema listo; flujo diferido.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
created_by_admin_id | uuid | NO | “ | |
name | text | NO | “ | |
anuncio_ids | uuid[] | '{}'::uuid[] | ||
quantity | integer | NO | 0 | |
status | text | NO | 'draft'::text | |
pdf_url | text | “ | ||
created_at | timestamptz | now() |
napkin_distributions
PK: id · filas (staging): 0
Nota: Ads Fase 2 (distribución de servilletas). Diferido.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
napkin_id | uuid | NO | “ | → napkins.id |
restaurant_id | uuid | NO | “ | → restaurants.id |
status | text | NO | 'pending'::text | |
distributed_at | timestamptz | “ | ||
received_at | timestamptz | “ | ||
created_at | timestamptz | now() |
promoters
PK: id · filas (staging): 0
Nota: Crecimiento: promotores de zona. Schema listo; sin flujo activo.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
name | text | NO | “ | |
phone | text | NO | “ | |
referral_code | text | NO | “ | |
commission_per_activation | numeric(10,2) | 300.00 | ||
assigned_zones | text[] | '{}'::text[] | ||
is_active | boolean | true | ||
created_at | timestamptz | now() |
referrals
PK: id · filas (staging): 0
Nota: Crecimiento: referidos multi-tipo. Schema listo; sin flujo activo.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
referrer_type | text | NO | “ | |
referrer_id | uuid | NO | “ | |
referred_type | text | NO | “ | |
referred_id | uuid | “ | ||
referral_code | text | NO | “ | |
status | text | 'pending'::text | ||
created_at | timestamptz | now() |
mantel_designs
PK: id · filas (staging): 0
Nota: Editor de manteles (Fogón). Activo en panel Fogón.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
nombre | text | NO | “ | |
config | jsonb | NO | '{}'::jsonb | |
preview_url | text | “ | ||
estado | text | NO | 'borrador'::text | |
created_at | timestamptz | NO | now() |
mantel_stock
PK: id · filas (staging): 0
Nota: Inventario de manteles físicos. Fase 1 manteles.
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
stock_actual | integer | NO | 0 | |
stock_minimo | integer | NO | 200 | |
ultimo_reabastecimiento | timestamptz | “ | ||
updated_at | timestamptz | NO | now() |
solicitudes_manteles
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
plan | text | NO | 'basico'::text | |
cantidad | integer | NO | 50 | |
estado | text | NO | 'solicitado'::text | |
archivo_arte_url | text | “ | ||
brief | text | “ | ||
notas_cliente | text | “ | ||
tracking_number | text | “ | ||
fecha_estimada_entrega | date | “ | ||
revision_comentario | text | “ | ||
revision_at | timestamptz | “ | ||
created_at | timestamptz | NO | now() |
Notificaciones y Zonas
push_subscriptions
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
restaurant_id | uuid | NO | “ | → restaurants.id (cascade) |
endpoint | text | NO | “ | |
p256dh | text | NO | “ | |
auth | text | NO | “ | |
user_agent | text | “ | ||
activo | boolean | NO | true | |
created_at | timestamptz | NO | now() |
zones
PK: id · filas (staging): 0
| Columna | Tipo | Null | Default | Referencia (FK) |
|---|---|---|---|---|
id 🔑 | uuid | NO | gen_random_uuid() | |
name | text | NO | “ | |
slug | text | NO | “ | |
polygon | geography | “ | ||
status | text | 'planned'::text | ||
min_restaurants | integer | 5 | ||
created_at | timestamptz | now() |
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:
| Tabla | Columna(s) | Valores / regla |
|---|---|---|
active_sessions | user_type | restaurant, advertiser, comensal, admin |
admin_users | role | super_admin, admin_ops, admin_commercial, admin_content, support, promoter, viewer |
advertisers | status | active, inactive |
app_versions | platform | ios, android, web |
campaigns | package | basic, zone, premium |
campaigns | status | draft, active, paused, completed |
combos | combo_type | comida_corrida, desayuno, cena, promo |
cupones | — | CHECK (((tipo <> 'porcentaje') OR (valor <= (100)))) |
cupones | tipo | porcentaje, monto_fijo |
cupones | — | CHECK ((valor > (0))) |
expenses | — | CHECK ((amount > (0))) |
expenses | category | mercado, gas, limpieza, servicios, nomina, otro |
fiscal_invoices | estado | pendiente, pagada, vencida, cancelada |
fogon_users | rol | admin, superadmin, soporte, finanzas, TI |
import_logs | status | processing, completed, failed |
inventory_movements | movement_type | purchase, consumption, adjustment, waste |
invoices | status | pending, paid, failed |
loyalty_history | action | stamp, redeem |
menu_86_log | action | 86, restore |
menu_item_modifier_groups | seleccion | unico, multiple |
menu_item_modifier_groups | tipo | opcional, obligatorio |
napkin_distributions | status | pending, received, in_use, exhausted |
napkins | status | draft, pending_print, printed, distributed |
operators | — | CHECK (((nivel >= 1) AND (nivel <= 5))) |
operators | role | duena, mesero, cocina, caja, repartidor |
order_items | kds_status | pending, preparing, ready, served, cancelled |
orders | cc_status | pending, accepted, preparing, ready, picked_up, no_show, cancelled |
orders | order_type | dine_in, takeout, click_collect, delivery |
orders | payment_method | cash, card, transfer, mixed, online |
orders | status | open, preparing, ready, served, paid, cancelled |
referrals | referred_type | restaurant, advertiser, comensal |
referrals | referrer_type | promoter, restaurant, advertiser, comensal |
referrals | status | pending, registered, activated, paid |
restaurant_services | res_confirmation_mode | auto, manual |
restaurants | contract_status | pending, active, suspended, terminated |
restaurants | plan | free, basic, pro |
restaurants | service_model | a_la_carta, comida_corrida, buffet, mixed |
restaurants | tier | bronze, silver, gold |
tables | status | free, occupied, served, check_requested, dirty, reserved |
waitlist | estado | esperando, asignado, expirado, no_show, cancelada |
zones | status | planned, prospecting, active, paused |
UNIQUE constraints e índices parciales relevantes
| Tabla | Constraint / índice | Definición |
|---|---|---|
admin_users | admin_users_auth_user_id_key | UNIQUE (auth_user_id) |
advertisers | advertisers_email_key | UNIQUE (email) |
advertisers | advertisers_auth_user_id_key | UNIQUE (auth_user_id) |
cupones | cupones_restaurant_id_codigo_key | UNIQUE (restaurant_id, codigo) |
daily_menus | daily_menus_restaurant_id_date_key | UNIQUE (restaurant_id, date) |
fiscal_closes | fiscal_closes_restaurant_id_folio_number_key | UNIQUE (restaurant_id, folio_number) |
fiscal_invoices | fiscal_invoices_folio_key | UNIQUE (folio) |
loyalty_customers | loyalty_customers_restaurant_id_phone_key | UNIQUE (restaurant_id, phone) |
mantel_stock | mantel_stock_restaurant_id_key | UNIQUE (restaurant_id) |
menu_item_modifier_group_links | menu_item_modifier_group_links_menu_item_id_group_id_key | UNIQUE (menu_item_id, group_id) |
onboarding_progress | onboarding_progress_restaurant_id_key | UNIQUE (restaurant_id) |
promoters | promoters_referral_code_key | UNIQUE (referral_code) |
push_subscriptions | push_subscriptions_restaurant_id_endpoint_key | UNIQUE (restaurant_id, endpoint) |
restaurant_blocked_dates | restaurant_blocked_dates_restaurant_id_blocked_date_key | UNIQUE (restaurant_id, blocked_date) |
restaurant_services | restaurant_services_restaurant_id_key | UNIQUE (restaurant_id) |
restaurants | restaurants_slug_key | UNIQUE (slug) |
restaurants | restaurants_referral_code_key | UNIQUE (referral_code) |
restaurants | restaurants_auth_user_id_key | UNIQUE (auth_user_id) |
zones | zones_slug_key | UNIQUE (slug) |
cash_sessions | cash_sessions_one_open_per_restaurant (UNIQUE parcial) | ... USING btree (restaurant_id) WHERE (closed_at IS NULL) |
operators | idx_operators_email (UNIQUE parcial) | ... USING btree (restaurant_id, lower(email)) WHERE (email IS NOT NULL) |
orders | idx_orders_table (parcial) | ... USING btree (table_id) WHERE (status = 'open'::text) |
restaurants | idx_restaurants_zone (parcial) | ... USING btree (zone_id) WHERE (is_active = true) |
cash_sessions_one_open_per_restaurant(UNIQUE parcialWHERE closed_at IS NULL) es el candado de A0-A2 T5: garantiza una sola caja abierta por fonda. DosPOST /api/cash/openconcurrentes → el segundo recibe23505traducido a400 Ya hay una caja abierta.
Notas de estado: DORMANT, diferidas y deuda técnica
active_sessions— DORMANT. 0 filas; la auditoría víarestaurant_sessionsfue retirada (arregla issue #101). Se conserva como superficie futura de gestión de sesiones/dispositivos.- Sin tabla
reservations— el routerreception.tsexpone/reservas, pero las reservas no tienen tabla propia en este schema (superficie de reservas parcialmente diferida).waitlistsí es real. - Sin tablas de comensal/Aprovecho en
public— los usuarios Aprovecho viven en Supabase Auth / otra capa;loyalty_customers.aprovecho_user_ides el puente.qr_scans.user_ides 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). restaurantses amplia (72 columnas) — concentra config de fonda, fiscal, delivery (rappi/uber HMAC), 2FA (totp_*), fidelidad y auth (auth_user_id→ Supabase). Deuda conocida: la entidadrestaurantsestuvo triplicada históricamente (PG UUID + Supabase UUID + migraciones SERIAL); este schema es el UUID canónico.operators.role(duena|mesero|cocina|caja|repartidor) alimentarequireRole.operatorIdsufre un type-lie conocido (middleware lo tipanumber, en runtime es eluuiddeoperators.id);cash.tslo 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.