Skip to main content

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';
Table is available for immediate seating.Conditions:
  • No active orders
  • No upcoming reservations
  • Not manually blocked/occupied

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:
  1. Generates random 4-digit code (1000-9999)
  2. Sets overrideStatus: 'Ocupada'
  3. Records occupiedSince timestamp
  4. Stores accessCode on table
  5. 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:
  1. Manual overrides (overrideStatus)
  2. Active dine-in orders at each table
  3. Upcoming reservations (within 2 hours)
  4. 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

1

Check Available Tables

In TablesPanel, view tables with status “Libre” (green).
2

Select Table

Choose appropriate table based on party size.
3

Occupy Table

Click “Ocupar Mesa” button. System:
  • Generates 4-digit access code
  • Sets status to “Ocupada”
  • Records current time as occupiedSince
4

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
5

Take Orders

Create dine-in orders with this table’s ID. Multiple orders accumulate.
6

Process Payment

View accumulated total in table details. Mark all orders as paid.
7

Clear Table

Click “Liberar Mesa” to reset:
  • Clears occupancy
  • Removes access code
  • Table returns to “Libre” status

Blocking Table for Maintenance

1

Select Table

In TablesPanel, click on table to block.
2

Set Blocked Status

Click “Bloquear Mesa” button.
3

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
4

Unblock When Ready

Click “Desbloquear Mesa” to return to normal status.

Processing Reservation with Tables

1

Reservation Created

When admin or AI creates reservation, tables automatically assigned.
2

2 Hours Before: Table Shows 'Reservada'

Yellow status indicator, shows customer name and time.
3

1 Hour Before: Table Shows 'Bloqueada'

Red status, cannot be used for walk-ins.
4

Customer Arrives

Admin marks reservation as “Seated” in ReservationsPanel.Table status updates to show reservation customer is now dining.
5

Create Orders

Dine-in orders linked to reservation’s tableIds.
6

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:
  1. Admin A marks Table 3 as occupied (generates code “4521”)
  2. Firebase update triggers
  3. Admin B’s panel immediately shows Table 3 as “Ocupada”
  4. 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

Build docs developers (and LLMs) love