CDC Manager · Dental operations platform · 2025 – 2026
Replacing a fragmented clinic operation with one integrated platform.
Clinic management system for multi-site dental clinics, built to fully replace a legacy system.
- Product
- Full stack
- SaaS
- Client
- Centro Dentário Colombo (Lisbon and Amadora)
- Status
- Going live at the clinic; being prepared as a product for other clinics.
- 34 / 37
- stakeholder requirements delivered
- 749
- records migrated from the legacy system
- 25+
- data models, 3 areas, 2 clinics

Admin dashboard — "Today", "Up next" and "Needs attention".
Context
Centro Dentário Colombo runs two clinics: one at the Colombo shopping centre with five surgeries and rotating doctors, another in Buraca with one surgery and a single doctor. Operations ran on a legacy management system (Dentoral) the team wanted to replace entirely.
I started by building the clinic's public website. Once the owners saw what was possible, the brief grew: a system covering patients, scheduling, clinical records, billing, invoicing, doctor commissions, stock and a patient portal — with global clinical data and per-clinic operations.
The problem
Information lived in several places: the schedule in one system, commissions in spreadsheets, stock in people's heads, appointment confirmations by phone. Double-booking and double-charging errors were common and hard to reconstruct afterwards.
Doctors needed to see their schedule and production on their phone — in the car, between clinics — and patients needed to confirm attendance and access documents without calling reception.
After a presentation to the partners I received 37 improvement requirements (E1–E19, P1–P16, X1–X2). They had to be phased without stopping the operation.
My role
Sole developer and product owner: requirements with the partners, domain modelling, architecture, implementation, data migration from the legacy system, documentation and approval presentations.
Constraints
- Legacy data: 749 acts and 22 categories imported from a system still in use during the migration.
- Financial accuracy: doctor commissions and certified invoicing — one wrong cent is an accounting problem.
- Concurrent bookings across five surgeries with rotating doctors.
- Three user roles with distinct permissions and clinical data under GDPR.
- Real users in production: the operation could not stop between phases.
Solution
A Next.js application with three areas — admin/reception, doctor and patient portal — on a single MongoDB database with 25+ models. Everything operational (schedule, billing, invoicing, recalls, stock) is segregated per clinic; patients and clinical records are global.
The 37 requirements were organised into a seven-phase plan. Phase one was the financial engine: discount and direct cost per treatment line, commission resolution chain (doctor override → doctor base → act rate → 40% default), cancellation and reversal. The remaining phases covered scheduling, the doctor panel, patient portal, X-ray, stock and dashboards. 34 of the 37 requests were delivered; the remaining three depend on third parties (Citizen Card reading, PEM/SPMS) or were deferred until after approval.




What was built
- Multi-clinic schedule with a 15-minute grid, per-clinic and per-professional hours, DST handling and atomic double-booking prevention via MongoDB transactions.
- Three-email circuit per appointment (creation, cron reminder, public confirmation) with a login-free confirmation page.
- Consultation flow driven by a state machine: pending → confirmed → checked-in → in-progress → completed.
- 32-tooth SVG odontogram with five faces, versioned and immutable; treatment plans with phased execution; append-only clinical notes.
- Catalog of 749 acts in 22 categories imported from the legacy system with an idempotent, dry-run script.
- Per-clinic billing queue with front-desk checkout, double-charge guard and documents ready for certified issuance via Moloni.
- X-ray module with request, state machine, digital consent signature and image capture via Cloudinary (private assets, signed URLs).
- Stock by location: central intake, requisitions to surgeries, fortnightly counts, immutable movement ledger, inter-clinic transfers as an atomic pair.
- Recalls generated automatically when acts are completed, one open cycle per patient and act type.
- Admin dashboards (KPIs, occupancy, "Needs attention") and doctor dashboards (own production, commissions, consultations to close).
- Patient portal: appointments, attendance confirmation, .ics file, documents, personal data, GDPR consents.
- Strict RBAC: only admins see doctors' production and commissions; reception scoped per clinic; doctors only see their own data. Audit trail on every write.
- Dynamic branding (Organization model: name, logo, colour, legal data, sender) so the system can be installed at another clinic without touching code.
Key decisions
Financial immutability
Integer cents with banker's rounding. Every act freezes price and commission rate when recorded. Monthly reports are computed from those snapshots, never from the current table.
Never delete
Nothing is deleted. Records are voided with author and reason, stock is corrected with adjustment movements, the odontogram is versioned, invoices are voided by credit note. It is what makes any situation in a clinic reconstructible.
Golden rule of schedules
Changing clinic or professional hours never cancels or moves existing appointments. The system flags conflicts and rescheduling is always a human decision.
Multi-tenancy per database
To sell to other clinics, the chosen path was one database per client (separate deployment and MONGODB_URI) instead of an organizationId in every collection. Simple, isolated, and enough until several paying clients exist.
Mobile as a product priority
Doctors use the panel on their phones. The app shell was rebuilt with a drawer, two-height rows and collapsing grids, and raw tables got controlled horizontal scroll. Nothing was "adapted" at the end; it was designed for it.
Architecture
Next.js 16 App Router with Server Actions as the main layer (one per domain: schedule, billing, invoicing, recalls, stock, settings). API routes only for auth, the reminder cron and integrations.
MongoDB Atlas with Mongoose; transactions for invariants (double booking, double charge, stock balance). Domain constants shared in src/lib/domain.ts with no dependencies, so client components never pull in server code.
NextAuth v5 (JWT) with RBAC; Zod for validation and per-domain transition machines; Resend for email (EU region); Cloudinary for private images; Vercel.
Outcome
34 of 37 stakeholder requirements delivered across eight phased deliveries, with a development report and an approval presentation to the partners.
System ready to run both clinics and prepared, with dynamic branding and per-database isolation, to be installed at other dental clinics.
What this project shows: the ability to take a product from stakeholder discovery to a system in production, with architecture decisions designed for financial and clinical data.
What I would change
In a second iteration I would start with the permissions model and the audit trail before the financial engine: those were the areas that forced the most refactoring once the partners' requirements arrived.
One database per clinic was the right call to sell a second installation fast, but past a handful of paying clinics an organizationId per collection starts paying off — the code is already prepared for that migration.
Stack
Next.js 16 · TypeScript · MongoDB Atlas · Mongoose · NextAuth v5 · Zod · Resend · Cloudinary · Tailwind CSS v4 · Vercel
