Skip to content
← All projects
Independent projectPrototype, not deployed

EsteticaPro — Appointment Management Prototype

API-first scheduling platform for beauty salons, built to prove out a SaaS idea end to end. Prototype, not yet deployed to a paying customer.

Role: Solo developer

Team

Solo

Timeline

Jun 2026

Type

Personal

EsteticaPro dashboard

Dashboard

EsteticaPro calendar view

Calendar view

EsteticaPro appointment booking screen

Appointment booking

EsteticaPro clients screen

Clients

EsteticaPro notifications screen

Notifications

EsteticaPro services screen

Services

01 · Context & Problem

What problem this solves

Beauty salons scheduling by phone, paper and WhatsApp run into the same failures every time: double-booked stylists, no-show losses from forgotten reminders, and no single source of truth when more than one person manages the calendar. I built EsteticaPro to prove out that domain end to end, architected from day one as something that could be commercialized.

02 · Solution

What it actually does

A Django REST Framework API with JWT auth, a React/TypeScript frontend, and Celery + Redis for scheduled reminder delivery — including a working WhatsApp notification channel.

  • Conflict-free appointment booking: the end time is computed from the service duration and an overlap query runs on every save, scoped to that stylist, enforced at the model layer.
  • An available-slots endpoint that reuses the exact same overlap query as the booking validation, instead of a separate rules engine.
  • Role-scoped dashboard and data access: admins and receptionists see everything, an esthetician's queryset is hard-filtered to their own appointments.
  • A notification queue with explicit pending/sent/failed states, dispatched and retried by a Celery Beat worker every minute — not fire-and-forget messaging.
  • WhatsApp notifications wired through a dedicated service class, isolating a self-hosted automation gateway (OpenWA) from the scheduling and Celery code.

03 · Architecture

Architecture & decisions

Django apps split by business domain (accounts, services, clients, appointments, notifications, dashboard), each mounted under a flat REST API prefix.

API-first, modular monolith split strictly by domain.

Leaves room for a future mobile client or third-party integration without redesigning the backend.

New-appointment side effects triggered by a direct function call rather than Django signals.

Pragmatic and easy to follow, at the cost of a direct dependency from the appointments app on the notifications app's internals — a trade-off I'd revisit before scaling this to more event types.

WhatsApp delivery isolated behind a single service class.

Keeps the HTTP calls to the automation gateway out of the Celery task and the manual-resend view, so swapping providers later touches one file.

04 · Stack

Full stack, by layer

Frontend

React 19.2Vite 8TypeScriptFullCalendar

Backend

Django 6.0.5Django REST Framework 3.17.1djangorestframework-simplejwt

Database

MySQL

Infrastructure & Deploy

Celery 5.6Redis 7.4 (broker & result backend)

Auth

JWT (SimpleJWT)

External Services

OpenWA — self-hosted, unofficial WhatsApp automation gateway (not Meta's official Business API)

05 · Challenges

Technical challenges solved

Getting an unofficial WhatsApp gateway working end to end

Problem: There was no approved WhatsApp Business API access for a personal project, but the reminder flow needed real WhatsApp delivery to be worth demonstrating.

Solution: Integrated OpenWA, a self-hosted gateway that automates a real WhatsApp Web session via QR login, behind a dedicated service class with Costa Rica phone-number normalization and a scripted local boot sequence that starts the gateway, opens a session over its REST API and polls until it's ready. It works, end to end, without an official Business API integration.

One overlap query serving two different jobs

Problem: Preventing double-bookings and suggesting available slots are normally built as two separate pieces of logic that can drift apart.

Solution: Both the model-level booking validation and the available-slots endpoint call the same overlap predicate, so there's exactly one definition of what counts as a conflict.

Making reminders durable instead of fire-and-forget

Problem: A reminder that silently fails to send is worse than no reminder system at all.

Solution: Notifications carry an explicit pending/sent/failed state, and a Celery Beat worker polls every minute to dispatch and retry, so a failure is visible and re-triggerable instead of lost.

06 · Results

Results

A functional prototype that proves out the core scheduling domain and a working (if unofficial) WhatsApp notification channel. Not deployed and not sold.

No hard metrics confirmed yet for this project — the impact above is qualitative only.

07 · Retrospective

What I'd do differently

Before this could go to a real paying salon, two things would need to change: moving off the unofficial WhatsApp gateway to the official Business API (the current one carries real terms-of-service risk for a commercial customer), and tightening access control on a couple of endpoints that ended up under permissive defaults while the role system was still being built out.

Have a system that needs building?

Tell me what you're running today and what's breaking. I'll tell you honestly whether I can help.