Overview
The Table Management system provides real-time tracking of table status, manual occupancy controls, and integration with orders and reservations. It supports session-based billing for dine-in customers with accumulated totals across multiple orders.
Key Benefits
Real-Time Status : Live view of free, occupied, reserved, and blocked tables
Manual Controls : Override status, occupy tables, and block for maintenance
Access Codes : Generate secure 4-digit codes for customer table access
Session Tracking : Track all orders at a table from occupancy to payment
Accumulated Billing : Sum multiple orders for single table bill
Reservation Integration : Tables automatically blocked before reservation time
Table Data Structure
Base Table Interface
From types.ts:152-164:
export interface Table {
id : string ; // Format: TBL-{timestamp}-{random}
name : string ; // Display name (e.g., "Mesa 1", "Barra 3")
capacity : number ; // Maximum guests
allowsReservations : boolean ; // Whether table can be reserved
overrideStatus : 'Bloqueada' | 'Ocupada' | null ; // Manual status override
occupiedSince ?: string | null ; // ISO timestamp when manually occupied
accessCode ?: string ; // 4-digit code for customer access
currentSession ?: {
customerName : string ;
guests : number ;
};
}
Enriched Table Interface
From types.ts:168-180:
export interface EnrichedTable extends Table {
status : TableStatus ; // Computed status
details ?: {
type : 'order' | 'reservation' ;
id : string ;
customerName : string ;
time ?: string ; // For reservations
startTime ?: string ; // For orders/occupancy
orderStatus ?: OrderStatus ; // Current order status
};
activeOrdersOnTable ?: Order []; // All active orders at this table
accumulatedTotal ?: number ; // Sum of all orders in current session
}
Table Status Types
From types.ts:166:
export type TableStatus = 'Libre' | 'Ocupada' | 'Reservada' | 'Bloqueada' ;
Libre (Free)
Ocupada (Occupied)
Reservada (Reserved)
Bloqueada (Blocked)
Table is available for immediate seating. Conditions :
No active orders
No upcoming reservations
Not manually blocked/occupied
Table currently has diners. Conditions :
Has active dine-in orders, OR
Manually marked as occupied
Table has upcoming reservation (1-2 hours away). Conditions :
Confirmed reservation exists
Reservation time is 1-2 hours from now
Table unavailable for customers. Conditions :
Manually blocked by admin, OR
Reservation starting within 1 hour
Core Service Functions
From services/tableService.ts:
Managing Tables
Creating Tables
addTable (
tableData : Omit < Table , 'id' >
): Promise < Table >
Example :
import { addTable } from './services/tableService' ;
const newTable = await addTable ({
name: 'Mesa 10' ,
capacity: 6 ,
allowsReservations: true ,
overrideStatus: null
});
console . log ( newTable . id ); // "TBL-1710087654321-abc5d"
Updating Tables
updateTable ( updatedTable : Table ): Promise < Table >
Manual Status Override
setTableOverrideStatus (
tableId : string ,
status : 'Bloqueada' | 'Ocupada' | null
): Promise < Table >
Example :
import { setTableOverrideStatus } from './services/tableService' ;
// Block table for maintenance
await setTableOverrideStatus ( 'TBL-123' , 'Bloqueada' );
// Manually mark as occupied
await setTableOverrideStatus ( 'TBL-123' , 'Ocupada' );
// Sets occupiedSince to current timestamp
// Remove override (return to automatic status)
await setTableOverrideStatus ( 'TBL-123' , null );
// Clears occupiedSince, currentSession, and accessCode
Status Override Behavior :
Setting overrideStatus: 'Ocupada' auto-sets occupiedSince to now
Setting overrideStatus: null clears session data (occupiedSince, currentSession, accessCode)
Blocked tables cannot be reserved or used for dine-in orders
Session Management
Generate Access Code
From tableService.ts:137-153:
occupyTableAndGenerateCode ( tableId : string ): Promise < string >
How It Works :
Generates random 4-digit code (1000-9999)
Sets overrideStatus: 'Ocupada'
Records occupiedSince timestamp
Stores accessCode on table
Returns code for customer
Example :
import { occupyTableAndGenerateCode } from './services/tableService' ;
const code = await occupyTableAndGenerateCode ( 'TBL-123' );
console . log ( `Customer access code: ${ code } ` ); // "7342"
// Customer can use this code to order from their table
Verify Access Code
verifyTableAccessCode ( tableId : string , code : string ): boolean
Example :
import { verifyTableAccessCode } from './services/tableService' ;
const isValid = verifyTableAccessCode ( 'TBL-123' , '7342' );
if ( isValid ) {
// Allow customer to place order for this table
}
Update Session Info
updateTableSession (
tableId : string ,
customerName : string ,
guests : number
): Promise < void >
Example :
import { updateTableSession } from './services/tableService' ;
// Record who's sitting at the table
await updateTableSession ( 'TBL-123' , 'Familia Pérez' , 4 );
// Table now has:
// currentSession: { customerName: 'Familia Pérez', guests: 4 }
Enriching Tables with Status
From tableService.ts:188-291:
enrichTables (
tables : Table [],
orders : Order [],
reservations : Reservation []
): EnrichedTable []
This function computes dynamic table status by checking:
Manual overrides (overrideStatus)
Active dine-in orders at each table
Upcoming reservations (within 2 hours)
Session accumulated totals
Example :
import { enrichTables , getTablesFromCache } from './services/tableService' ;
import { getOrdersFromCache } from './services/orderService' ;
import { getReservationsFromCache } from './services/reservationService' ;
const tables = getTablesFromCache ();
const orders = getOrdersFromCache ();
const reservations = getReservationsFromCache ();
const enrichedTables = enrichTables ( tables , orders , reservations );
enrichedTables . forEach ( table => {
console . log ( ` ${ table . name } : ${ table . status } ` );
if ( table . accumulatedTotal ) {
console . log ( ` Total acumulado: $ ${ table . accumulatedTotal } ` );
}
});
// Output:
// Mesa 1: Ocupada
// Total acumulado: $24500
// Mesa 2: Libre
// Mesa 3: Reservada
// Mesa 4: Bloqueada
Finding Available Tables
getAvailableTablesForDineIn (
reservationToIgnoreId ?: string
): Table []
Returns tables that are :
Not manually blocked or occupied
Don’t have active dine-in orders
Don’t have reservations starting within 1 hour
Example :
import { getAvailableTablesForDineIn } from './services/tableService' ;
const availableTables = getAvailableTablesForDineIn ();
console . log ( ` ${ availableTables . length } tables available for walk-ins` );
availableTables . forEach ( table => {
console . log ( ` ${ table . name } - Capacity: ${ table . capacity } ` );
});
Status Computation Logic
From tableService.ts:216-289, the system computes status in priority order:
1. Manual Override (Highest Priority)
if ( table . overrideStatus === 'Bloqueada' ) {
return {
... table ,
status: 'Bloqueada' ,
details: { type: 'order' , id: 'manual' , customerName: 'Administrador' }
};
}
2. Manual Occupancy or Active Orders
if ( table . overrideStatus === 'Ocupada' ) {
// Find all orders since occupiedSince timestamp
const sessionOrders = orders . filter (
o => o . tableIds ?. includes ( table . id ) &&
o . type === OrderType . DINE_IN &&
table . occupiedSince &&
new Date ( o . createdAt ) >= new Date ( table . occupiedSince )
);
const accumulatedTotal = sessionOrders . reduce (( sum , order ) => sum + order . total , 0 );
return {
... table ,
status: 'Ocupada' ,
activeOrdersOnTable: sessionOrders ,
accumulatedTotal ,
details: { /* ... */ }
};
}
3. Reservation Blocking (Within 1 Hour)
const blockWindowEnd = new Date ( now . getTime () + 60 * 60 * 1000 );
if ( reservation . time > now && reservation . time <= blockWindowEnd ) {
return {
... table ,
status: 'Bloqueada' ,
details: {
type: 'reservation' ,
id: reservation . id ,
customerName: reservation . customerName ,
time: '20:00' // Formatted time
}
};
}
4. Reservation Preview (1-2 Hours Away)
const reservationWindowEnd = new Date ( now . getTime () + 2 * 60 * 60 * 1000 );
if ( reservation . time > blockWindowEnd && reservation . time <= reservationWindowEnd ) {
return {
... table ,
status: 'Reservada' ,
details: { /* ... */ }
};
}
5. Default to Free
return { ... table , status: 'Libre' };
Session-Based Billing
From tableService.ts:219-250:
When a table is manually occupied, the system tracks all orders from that moment:
Session Start :
const code = await occupyTableAndGenerateCode ( 'TBL-5' );
// Sets occupiedSince: "2026-03-10T19:30:00.000Z"
Customer Orders Multiple Times :
// Order 1 at 19:35
await saveOrder ({
customer: { name: 'Mesa 5' , phone: '' },
items: [{ name: 'Pizza Muzzarella' , quantity: 1 , price: 9200 , ... }],
total: 9200 ,
type: OrderType . DINE_IN ,
tableIds: [ 'TBL-5' ],
// ...
});
// Order 2 at 20:10 (customer orders dessert)
await saveOrder ({
customer: { name: 'Mesa 5' , phone: '' },
items: [{ name: 'Flan' , quantity: 2 , price: 1500 , ... }],
total: 3000 ,
type: OrderType . DINE_IN ,
tableIds: [ 'TBL-5' ],
// ...
});
Accumulated Total Calculation :
const enrichedTable = getEnrichedTableById ( 'TBL-5' );
console . log ( enrichedTable . accumulatedTotal ); // 12200 (9200 + 3000)
console . log ( enrichedTable . activeOrdersOnTable ?. length ); // 2
Session End :
// Mark both orders as paid and completed
await markOrderAsPaid ( order1 . id , PaymentMethod . CASH );
await markOrderAsPaid ( order2 . id , PaymentMethod . CASH );
// Clear table session
await setTableOverrideStatus ( 'TBL-5' , null );
// Clears: occupiedSince, accessCode, currentSession
Session Persistence :Sessions survive across page refreshes and admin device switches because:
Table data syncs via Firebase
Orders link to tables via tableIds
occupiedSince timestamp determines session start
User Workflows
Seating Walk-In Customer
Check Available Tables
In TablesPanel, view tables with status “Libre” (green).
Select Table
Choose appropriate table based on party size.
Occupy Table
Click “Ocupar Mesa” button. System:
Generates 4-digit access code
Sets status to “Ocupada”
Records current time as occupiedSince
Provide Code to Customer
Give customer the 4-digit code for self-ordering (if enabled). Or record customer name:
Click “Actualizar Sesión”
Enter customer name and guest count
Take Orders
Create dine-in orders with this table’s ID. Multiple orders accumulate.
Process Payment
View accumulated total in table details. Mark all orders as paid.
Clear Table
Click “Liberar Mesa” to reset:
Clears occupancy
Removes access code
Table returns to “Libre” status
Blocking Table for Maintenance
Select Table
In TablesPanel, click on table to block.
Set Blocked Status
Click “Bloquear Mesa” button.
Table Becomes Unavailable
Table shows as “Bloqueada” (red) and:
Cannot be reserved
Cannot be used for dine-in orders
Appears in admin panel with maintenance indicator
Unblock When Ready
Click “Desbloquear Mesa” to return to normal status.
Processing Reservation with Tables
Reservation Created
When admin or AI creates reservation, tables automatically assigned.
2 Hours Before: Table Shows 'Reservada'
Yellow status indicator, shows customer name and time.
1 Hour Before: Table Shows 'Bloqueada'
Red status, cannot be used for walk-ins.
Customer Arrives
Admin marks reservation as “Seated” in ReservationsPanel. Table status updates to show reservation customer is now dining.
Create Orders
Dine-in orders linked to reservation’s tableIds.
Complete Reservation
After payment, mark reservation as “Completed”. Tables return to “Libre” status.
Initial Table Data
From tableService.ts:28-34:
const initialTables : Table [] = [
{ id: 'T1' , name: 'Mesa 1' , capacity: 4 , allowsReservations: true , overrideStatus: null },
{ id: 'T2' , name: 'Mesa 2' , capacity: 4 , allowsReservations: true , overrideStatus: null },
{ id: 'T3' , name: 'Mesa 3' , capacity: 2 , allowsReservations: true , overrideStatus: null },
{ id: 'T4' , name: 'Mesa 4' , capacity: 6 , allowsReservations: true , overrideStatus: null },
{ id: 'T5' , name: 'Barra 1' , capacity: 1 , allowsReservations: false , overrideStatus: null },
];
Customize this initial data before going live. Add/remove tables to match your restaurant layout.
Real-Time Synchronization
From components/AdminDashboard.tsx:115:
const unsubTables = onSnapshot (
collection ( db , 'Tables' ),
( querySnapshot ) => {
const tables = querySnapshot . docs . map ( doc => doc . data () as Table );
updateTablesCache ( tables );
setDataTimestamp ( Date . now ()); // Triggers UI refresh
}
);
Multi-Admin Scenario :
Admin A marks Table 3 as occupied (generates code “4521”)
Firebase update triggers
Admin B’s panel immediately shows Table 3 as “Ocupada”
Both admins see same accumulated total as orders are placed
Configuration
Reservation Allowance
interface Table {
allowsReservations : boolean ; // Set false for bar seating, etc.
}
Use Cases :
true : Regular dining tables, booths
false : Bar counter seats, outdoor tables in rain season
Table Capacity
interface Table {
capacity : number ; // Maximum guests
}
Reservation system uses capacity to:
Find suitable tables for party size
Combine tables when needed
Prevent overbooking
Keep capacity values accurate!
Best Practices
Use Session Tracking Always use “Ocupar Mesa” to start sessions. This ensures accurate accumulated totals for billing.
Clear Sessions Promptly After customers pay and leave, immediately clear the table session to make it available.
Block for Maintenance Use the block feature instead of deleting tables during cleaning or repairs.
Accurate Capacity Set realistic capacity values. System uses these for automatic table assignment.
Monitor Reserved Tables Check TablesPanel 1 hour before reservation times to ensure tables are clean and ready.
Access Codes for Self-Service Generate access codes for customers to order via QR menu or web interface.
Orders - Dine-in orders link to tables, session tracking
Reservations - Reservations automatically assign and block tables
AI Assistants - AI can help customers order by table