Overview
BarberApp uses the Facade pattern to manage application state and business logic. All facades use Angular signals for reactive state management and are located insrc/app/features/*/.
AuthFacade
Manages authentication state and user session. Location:src/app/features/auth/auth.facade.ts:10
Signals
user: Signal<UserBase | null>
user: Signal<UserBase | null>
Signal<UserBase | null>Access: Read-only
Location:
auth.facade.ts:25Current authenticated user or null if not logged in.isAuthenticated: Signal<boolean>
isAuthenticated: Signal<boolean>
Signal<boolean>Access: Read-only (computed)
Location:
auth.facade.ts:26Computed signal that returns true if a user is authenticated.isLoading: Signal<boolean>
isLoading: Signal<boolean>
Signal<boolean>Access: Read-only
Location:
auth.facade.ts:27true when an authentication operation is in progress (login, register, logout).isCheckingAuth: Signal<boolean>
isCheckingAuth: Signal<boolean>
Signal<boolean>Access: Read-only
Location:
auth.facade.ts:28true when checking initial authentication status on app load.error: Signal<string | null>
error: Signal<string | null>
Signal<string | null>Access: Read-only
Location:
auth.facade.ts:29User-friendly error message from the last failed operation, or null if no error.Methods
checkAuthStatus(): Promise<void>
checkAuthStatus(): Promise<void>
auth.facade.ts:45Checks if a user is authenticated and loads their profile.Behavior:- Sets
isCheckingAuthtotrue - Calls
authService.isAuthenticated()andauthService.getCurrentUser() - Updates
usersignal with the result - Sets
isCheckingAuthtofalsewhen complete
authGuard and publicGuardlogin(email: string, password: string): Promise<void>
login(email: string, password: string): Promise<void>
auth.facade.ts:73Authenticates a user with email and password.Parameters:email- User’s email addresspassword- User’s password
- Sets
isLoadingtotrue - Calls
authService.login() - Fetches user profile and updates
usersignal - Redirects user to appropriate dashboard based on role
- Sets user-friendly error message if login fails
auth/invalid-credential→ “Correo o contraseña incorrectos.”auth/too-many-requests→ “Demasiados intentos fallidos. Intenta más tarde.”
register(email: string, password: string): Promise<string | void>
register(email: string, password: string): Promise<string | void>
auth.facade.ts:102Registers a new user account.Parameters:email- User’s email addresspassword- User’s password
void on errorBehavior:- Sets
isLoadingtotrue - Calls
authService.register() - Returns UID but does NOT set
usersignal (you must callsetUser()after creating user profile)
auth/email-already-in-use→ “Ese correo ya está registrado.”auth/weak-password→ “La contraseña es muy débil. Usá una más segura.”
logout(): Promise<void>
logout(): Promise<void>
auth.facade.ts:122Signs out the current user.Behavior:- Sets
isLoadingtotrue - Calls
authService.logout() - Sets
usersignal tonull - Clears any errors
setUser(user: UserBase | null): void
setUser(user: UserBase | null): void
auth.facade.ts:35Manually sets the user signal (typically after registration).Parameters:user- User object to set, ornullto clear
redirectUserByRole(user: UserBase): void
redirectUserByRole(user: UserBase): void
auth.facade.ts:186Redirects user to appropriate dashboard based on their role.Parameters:user- User object withroleproperty
UserRoles.CLIENT→/dashboard/clientUserRoles.SPECIALIST→/dashboard/specialistUserRoles.ADMIN→/dashboard/specialist
UserFacade
Manages user data and operations. Location:src/app/features/auth/user.facade.ts:10
Signals
users: Signal<UserBase[]>
users: Signal<UserBase[]>
Signal<UserBase[]>Access: Read-only
Location:
user.facade.ts:24Array of users loaded by getUsersByRole().clients: Signal<Client[]>
clients: Signal<Client[]>
Signal<Client[]>Access: Read-only
Location:
user.facade.ts:25Array of clients loaded by loadClientsForSpecialist().isSaving: Signal<boolean>
isSaving: Signal<boolean>
Signal<boolean>Access: Read-only
Location:
user.facade.ts:22true when a user operation is in progress.error: Signal<string | null>
error: Signal<string | null>
Signal<string | null>Access: Read-only
Location:
user.facade.ts:23Error message from the last failed operation.Methods
createUser(user: Client | Specialist): Promise<void>
createUser(user: Client | Specialist): Promise<void>
user.facade.ts:27Creates a new user profile in Firestore.Parameters:user- Complete user object (Client or Specialist)
dniExists(dni: string): Promise<boolean>
dniExists(dni: string): Promise<boolean>
user.facade.ts:41Checks if a DNI is already registered.Parameters:dni- National ID number to check
true if DNI exists, false otherwiseSide Effects:- Sets
errorsignal to “Ese DNI ya está registrado.” if DNI exists - Clears
errorif DNI is available
updateUser(updatedData: Partial<Client | Specialist>): Promise<void>
updateUser(updatedData: Partial<Client | Specialist>): Promise<void>
user.facade.ts:53Updates an existing user’s data.Parameters:updatedData- Partial user object withidrequired
- Updates user in Firestore
- If the updated user is the authenticated user, refreshes the
authFacade.user()signal
getUsersByRole(role: string): Promise<void>
getUsersByRole(role: string): Promise<void>
user.facade.ts:78Loads all users with a specific role into the users signal.Parameters:role- User role (e.g.,'client','specialist')
users signalUsage:getUserById(id: string): Promise<UserBase | null>
getUserById(id: string): Promise<UserBase | null>
user.facade.ts:94Fetches a single user by ID.Parameters:id- User ID
null if not foundUsage:loadClientsForSpecialist(specialistId: string): Promise<void>
loadClientsForSpecialist(specialistId: string): Promise<void>
user.facade.ts:107Loads all clients who have had appointments with a specific specialist.Parameters:specialistId- Specialist’s user ID
clients signalBehavior:- Fetches specialist’s pending and completed appointments
- Extracts unique client IDs
- Loads client profiles
- Updates
clientssignal
AppointmentFacade
Manages appointment state and operations. Location:src/app/features/appointments/appointment.facade.ts:9
Signals
appointments: Signal<Appointment[]>
appointments: Signal<Appointment[]>
Signal<Appointment[]>Access: Read-only
Location:
appointment.facade.ts:21Current user’s appointments loaded by loadUserAppointments().selectedAppointment: Signal<Appointment | null>
selectedAppointment: Signal<Appointment | null>
Signal<Appointment | null>Access: Read-only
Location:
appointment.facade.ts:24Single appointment loaded by loadAppointmentById().viewedClientAppointments: Signal<Appointment[] | null>
viewedClientAppointments: Signal<Appointment[] | null>
Signal<Appointment[] | null>Access: Read-only
Location:
appointment.facade.ts:25Completed appointments for a viewed client (used in medical records).isLoading: Signal<boolean>
isLoading: Signal<boolean>
Signal<boolean>Access: Read-only
Location:
appointment.facade.ts:22true when an appointment operation is in progress.error: Signal<string | null>
error: Signal<string | null>
Signal<string | null>Access: Read-only
Location:
appointment.facade.ts:23Error message from the last failed operation.Methods
createAppointment(specialty: Specialty, specialist: Specialist, date: Date): Promise<Appointment | null>
createAppointment(specialty: Specialty, specialist: Specialist, date: Date): Promise<Appointment | null>
appointment.facade.ts:42Creates a new appointment for the authenticated client.Parameters:specialty- Selected specialty/servicespecialist- Selected specialistdate- Appointment date and time
null if user is not a clientBehavior:- Verifies current user is a client
- Generates appointment ID with
AutoId.newId() - Creates appointment with
PENDINGstatus - Adds to
appointmentssignal (sorted by date)
loadUserAppointments(): Promise<void>
loadUserAppointments(): Promise<void>
appointment.facade.ts:80Loads all appointments for the current user.Updates: appointments signalBehavior:- If user is CLIENT: loads client appointments
- If user is SPECIALIST: loads specialist appointments
- Clears appointments if no user is authenticated
authFacade.user() changes.loadAppointmentById(id: string): Promise<void>
loadAppointmentById(id: string): Promise<void>
appointment.facade.ts:107Loads a single appointment by ID.Parameters:id- Appointment ID
selectedAppointment signalBehavior:- First checks if appointment is already in
appointments()array - If not cached, fetches from service
- Updates
selectedAppointmentsignal
updateAppointment(id: string, updates: Partial<Appointment>): Promise<void>
updateAppointment(id: string, updates: Partial<Appointment>): Promise<void>
appointment.facade.ts:129Updates an existing appointment.Parameters:id- Appointment IDupdates- Partial appointment object with fields to update
appointmentssignal (updates the matching appointment)selectedAppointmentsignal (if the ID matches)
loadCompletedAppointmentsByClientId(clientId: string): Promise<void>
loadCompletedAppointmentsByClientId(clientId: string): Promise<void>
appointment.facade.ts:153Loads completed appointments for a specific client (used in medical records).Parameters:clientId- Client’s user ID
viewedClientAppointments signalUsage:getSpecialistAppointmentsByStatus(specialistId: string, statuses: AppointmentStatus[]): Promise<Appointment[]>
getSpecialistAppointmentsByStatus(specialistId: string, statuses: AppointmentStatus[]): Promise<Appointment[]>
appointment.facade.ts:168Fetches specialist appointments filtered by status (does not update signals).Parameters:specialistId- Specialist’s user IDstatuses- Array of appointment statuses
getAppointmentsBySpecialistAndDateRange(specialistId: string, startDate: Date, endDate: Date): Promise<Appointment[]>
getAppointmentsBySpecialistAndDateRange(specialistId: string, startDate: Date, endDate: Date): Promise<Appointment[]>
appointment.facade.ts:187Fetches specialist appointments within a date range.Parameters:specialistId- Specialist’s user IDstartDate- Range startendDate- Range end
getAppointmentsByClientAndDateRange(clientId: string, startDate: Date, endDate: Date): Promise<Appointment[]>
getAppointmentsByClientAndDateRange(clientId: string, startDate: Date, endDate: Date): Promise<Appointment[]>
appointment.facade.ts:208Fetches client appointments within a date range.Parameters:clientId- Client’s user IDstartDate- Range startendDate- Range end
setAppointments(appointments: Appointment[]): void
setAppointments(appointments: Appointment[]): void
appointment.facade.ts:38Manually sets the appointments signal.Parameters:appointments- Array of appointments
SpecialtyFacade
Manages specialty data. Location:src/app/features/specialties/specialty.facade.ts:8
Signals
specialties: Signal<Specialty[]>
specialties: Signal<Specialty[]>
Signal<Specialty[]>Access: Read-only
Location:
specialty.facade.ts:15Array of all available specialties loaded by loadSpecialties().Methods
loadSpecialties(): Promise<void>
loadSpecialties(): Promise<void>
specialty.facade.ts:17Loads all specialties from Firestore.Updates: specialties signalBehavior:- Only fetches if
specialties()is empty (caches result) - Updates
specialtiessignal with all specialties
Signal Patterns
Reactive UI Updates
All facade signals are read-only and automatically update components:Effects
Facades use effects to react to signal changes:Testing Facades
Example test forAuthFacade: