The Domain Layer is the heart of the application, containing the core business logic and rules. It has no dependencies on external frameworks or libraries—it’s pure Dart code.Location:lib/dominio/
Represents a table reservation with all its business rules.
lib/dominio/entidades/reserva.dart
enum EstadoReserva { pendiente, // Awaiting confirmation confirmada, // Confirmed by SMS verification cancelada, // Cancelled by customer or restaurant}class Reserva { final String id; final String mesaId; final DateTime fechaHora; final int numeroPersonas; final int duracionMinutos; EstadoReserva estado; final String? contactoCliente; // Email for notifications final String? nombreCliente; final String? telefonoCliente; // Verified phone final String? negocioId; Reserva({ required this.id, required this.mesaId, required this.fechaHora, required this.numeroPersonas, this.duracionMinutos = 60, this.estado = EstadoReserva.pendiente, this.contactoCliente, this.nombreCliente, this.telefonoCliente, this.negocioId, }); /// Calculates the end time of the reservation DateTime get horaFin => fechaHora.add(Duration(minutes: duracionMinutos)); /// Confirms a pending reservation void confirmar() { if (estado == EstadoReserva.cancelada) { throw Exception('No se puede confirmar una reserva cancelada.'); } if (estado == EstadoReserva.confirmada) { throw Exception('La reserva ya está confirmada.'); } estado = EstadoReserva.confirmada; } Reserva copyWith({/* ... */}) { // Immutable copy with modified fields }}
The horaFin getter encapsulates business logic: a reservation occupies a table for a specific duration.
Represents a restaurant table with capacity validation logic.
lib/dominio/entidades/mesa.dart
class Mesa { final String id; final String nombre; // e.g., "Mesa 1", "Terraza A" final int capacidad; // Number of seats final String negocioId; // Restaurant ID final String zona; // "Terraza", "Salón", "Jardín", etc. Mesa({ required this.id, required this.nombre, required this.capacidad, required this.negocioId, this.zona = 'Salón', }); /// Business rule: Can this table accommodate the given number of people? bool puedeAcomodar(int numeroPersonas) { // Table must have enough capacity if (capacidad < numeroPersonas) { return false; // Too small } // Table can't be more than 3 seats larger // (avoids wasting large tables on small groups) if (capacidad > numeroPersonas + 3) { return false; // Too large } return true; // Perfect fit } Mesa copyWith({/* ... */}) { /* ... */ }}
Business Rule: A table is suitable if:
capacidad >= numeroPersonas (not too small)
capacidad <= numeroPersonas + 3 (not too large)
This prevents assigning an 8-person table to a party of 2.
Represents the restaurant configuration and business rules.
lib/dominio/entidades/negocio.dart
class Negocio { final String id; final String nombre; final String nombreResponsable; final String email; final String telefono; final String direccion; final String descripcion; final String especialidad; final String icono; // Material icon name // Business Rules Configuration final int minHorasParaCancelar; // Default: 24 hours final int maxDiasAnticipacionReserva; // Default: 14 days final int duracionPromedioMinutos; // Default: 60 minutes final List<String> zonas; // Available zones final bool telefonoVerificado; Negocio({ required this.id, required this.nombre, required this.nombreResponsable, required this.email, required this.telefono, required this.direccion, this.descripcion = '', this.especialidad = '', this.icono = 'restaurant', this.minHorasParaCancelar = 24, this.maxDiasAnticipacionReserva = 14, this.duracionPromedioMinutos = 60, this.telefonoVerificado = false, this.zonas = const ['Salón', 'Terraza'], }); Negocio copyWith({/* ... */}) { /* ... */ }}
Business configuration is stored in the Negocio entity, allowing each restaurant to have custom cancellation policies and reservation durations.
Manages restaurant opening hours with support for multiple intervals per day.
lib/dominio/entidades/horario_apertura.dart
class IntervaloHorario { final int horaInicio; // 0-23 final int minutoInicio; // 0-59 final int horaFin; final int minutoFin; /// Checks if a specific time falls within this interval bool contieneHora(int hora, int minuto) { final minutosTotales = hora * 60 + minuto; final minutosInicio = horaInicio * 60 + minutoInicio; int minutosFin = horaFin * 60 + minutoFin; // Handle midnight crossing if (minutosFin < minutosInicio) { minutosFin += 24 * 60; if (minutosTotales < minutosInicio) { final adjustedMinutosTotales = minutosTotales + 24 * 60; return adjustedMinutosTotales >= minutosInicio && adjustedMinutosTotales < minutosFin; } } return minutosTotales >= minutosInicio && minutosTotales < minutosFin; }}class HorarioDia { final String nombreDia; // "Lunes", "Martes", etc. final bool cerrado; final List<IntervaloHorario> intervalos; // Multiple intervals (lunch/dinner) /// Checks if the restaurant is open at a specific time bool estaAbierto(int hora, int minuto) { if (cerrado || intervalos.isEmpty) return false; return intervalos.any((intervalo) => intervalo.contieneHora(hora, minuto)); }}class HorarioApertura { final String negocioId; final List<HorarioDia> horariosSemanal; // 7 days /// Checks if the restaurant is open at a specific date/time bool estaAbiertoEn(DateTime fecha) { final diaSemana = fecha.weekday; // 1 = Monday, 7 = Sunday final horarioDia = horariosSemanal.firstWhere( (h) => _obtenerNumeroDia(h.nombreDia) == diaSemana, orElse: () => HorarioDia(nombreDia: '', cerrado: true), ); return horarioDia.estaAbierto(fecha.hour, fecha.minute); } /// Returns a user-friendly error message when closed String obtenerMensajeError(DateTime fecha) { // Returns specific message based on day and intervals }}
Example: A restaurant open for lunch (12:00-15:00) and dinner (19:00-23:00):
abstract class ReservaRepositorio { /// Creates a new reservation Future<Reserva> crearReserva(Reserva reserva); /// Retrieves all reservations Future<List<Reserva>> obtenerReserva(); /// Cancels a reservation by ID Future<void> cancelarReserva(String reservaId); /// Updates an existing reservation Future<void> actualizarReserva(Reserva reserva); /// Gets a specific reservation by ID Future<Reserva?> obtenerReservaPorId(String reservaId); /// Gets active reservations for a table on a specific date/time Future<List<Reserva>> obtenerReservasPorMesaYHorario({ required String mesaId, required DateTime fecha, required DateTime hora, }); /// Checks if a table is available for a given time slot /// Detects time collisions with existing reservations Future<bool> mesaDisponible({ required String mesaId, required DateTime fecha, required DateTime hora, required int duracionMinutos, }); /// Gets customer's reservations by phone and restaurant Future<List<Reserva>> obtenerReservasPorTelefonoYNegocio({ required String telefonoCliente, required String negocioId, });}
abstract class MesaRepositorio { /// Retrieves all tables Future<List<Mesa>> obtenerMesas(); /// Gets a specific table by ID Future<Mesa?> obtenerMesaPorId(String mesaId); /// Gets all tables for a specific restaurant Future<List<Mesa>> obtenerMesasPorNegocio(String negocioId); /// Adds a new table Future<Mesa?> agregarMesa(Mesa mesa); /// Updates an existing table Future<bool> actualizarMesa(Mesa mesa); /// Deletes a table Future<bool> eliminarMesa(String mesaId); /// Gets available zones in a restaurant Future<List<String>> obtenerZonasDisponibles(String negocioId); /// Searches for an available table in a specific zone /// Returns the first suitable table or null if none available Future<Mesa?> buscarMesaDisponibleEnZona({ required String zona, required DateTime fecha, required DateTime hora, required int numeroPersonas, required String negocioId, });}
abstract class HorarioAperturaRepositorio { /// Gets business hours for a restaurant Future<HorarioApertura?> obtenerHorarioPorNegocio(String negocioId); /// Checks if restaurant is open at a specific date/time Future<bool> estaAbiertoEn(String negocioId, DateTime fecha); /// Gets error message when restaurant is closed Future<String> obtenerMensajeHorarioCerrado( String negocioId, DateTime fecha, ); /// Gets available time slots for a given date Future<List<String>> obtenerIntervalosDisponibles( String negocioId, DateTime fecha, int intervaloMinutos, ); /// Saves business hours configuration Future<bool> guardarHorario(HorarioApertura horario); /// Converts HorarioApertura to a string map for UI display Map<String, String> horarioAMapString(HorarioApertura horario); /// Converts string map to HorarioApertura entity HorarioApertura mapStringAHorario( String negocioId, Map<String, String> mapa, );}