Bỏ qua để đến Nội dung

Phần Mềm Quản Lý Spa Custom Bằng Claude Code: Từ Brief Đến Go-Live Trong 2 Tuần

Phần mềm quản lý spa custom bằng Claude Code, giao diện booking và quản lý dịch vụ

Tháng 2/2026, chủ một spa 3 chi nhánh ở Hà Nội nhắn mình: "Em đang dùng phần mềm spa Hàn Quốc, $150/tháng, nhưng cái booking nó không fit quy trình của mình chút nào. Có cách nào tự build không?"

Câu trả lời: có, và rẻ hơn nhiều. Trong 2 tuần, kết hợp Claude Code với Next.js và Supabase, mình ship một MVP đủ dùng thật cho 3 chi nhánh đó: booking online, quản lý ca nhân viên, tracking revenue theo dịch vụ, nhắc lịch Zalo OA. Chi phí build khoảng 40 giờ engineering (kể cả thời gian học những phần chưa biết qua Claude Code).

Bài này là walkthrough thật: tech stack, code snippets, và những chỗ Claude Code giúp được lẫn không giúp được.

Key Takeaways - Thị trường beauty & personal care Việt đạt $2.79 tỷ năm 2025, online chiếm 18.8% (Statista, 2025). Spa có động lực số hoá rõ. - Claude Code rút ngắn ~75% thời gian code thuần trong case study này (28h → 7h). Số liệu đồng pha với GitHub Research 2024: dev dùng AI assistant nhanh hơn 55.8% (GitHub, 2024). - Tích hợp Zalo OA bắt buộc với spa Việt: Zalo có 79.6M MAU cuối 2025, phủ 91.4% người dùng internet (DataReportal, 2026). - Stack Next.js + Supabase + Vercel: setup ~$1,500, infra ~$30/tháng. Payback so với SaaS off-the-shelf khoảng 9 tháng.

Xem thêm: Phần Mềm Custom Là Gì? Khi Nào Nên Dùng? cho context tổng quan. Bài này focus vào case study spa cụ thể.


Mục lục

  1. Vì sao phần mềm spa off-the-shelf thường không vừa quy trình Việt?
  2. Stack nào phù hợp để build phần mềm quản lý spa custom?
  3. Booking system: làm sao chống double booking real-time?
  4. Quản lý ca nhân viên: dual-therapist scheduling thế nào?
  5. Revenue tracking: báo cáo custom theo chi nhánh và dịch vụ
  6. Tích hợp Zalo OA: vì sao là tính năng killer?
  7. Claude Code đã giúp gì thật sự?
  8. Chi phí build vs buy: payback period bao lâu?
  9. FAQ

1. Vì sao phần mềm spa off-the-shelf thường không vừa quy trình Việt?

Thị trường beauty & personal care Việt đạt US$2.79 tỷ năm 2025, kênh online chiếm 18.8% (Statista, 2025). Spa Việt số hoá nhanh, nhưng phần mềm SaaS đa số do team Hàn, Thái, Mỹ làm. Quy trình thật của spa ở Hà Nội hay Sài Gòn lệch khá xa template mặc định, từ booking đa-therapist đến cách khách trả tiền.

Kiến trúc hệ thống phần mềm quản lý spa custom: booking, nhân viên, revenue, Zalo OA

Khách hàng của mình có những pain point cụ thể với phần mềm hiện tại:

Pain 1: Booking flow không phản ánh quy trình thật. Spa này có dịch vụ cần assign 2 nhân viên cùng lúc (1 therapist chính + 1 phụ). Phần mềm cũ chỉ assign 1 người, họ phải track ca thứ 2 bằng Excel riêng.

Pain 2: Không gửi nhắc lịch qua Zalo. 80% khách hàng của họ liên lạc qua Zalo (con số này khá tương đồng với data toàn quốc: Zalo phủ 91.4% người dùng internet Việt theo DataReportal, 2026). Phần mềm cũ chỉ gửi SMS, mà khách Việt giờ ít đọc SMS hơn Zalo nhiều.

Pain 3: Báo cáo không đúng ngữ cảnh. Họ cần báo cáo "doanh thu theo loại dịch vụ theo chi nhánh theo tuần". Phần mềm off-the-shelf có preset report cố định, muốn custom phải thêm tiền.

Pain 4: Multi-branch sync lag. 3 chi nhánh share calendar nhưng sync delay 5-10 phút, dẫn đến double booking xảy ra 2-3 lần/tuần.

Những vấn đề này điển hình cho spa Việt dùng phần mềm nước ngoài. Xem thêm bài TCO Phần Mềm Mua vs Custom: Tính Thế Nào Cho Đúng? để hiểu framework đánh giá build-vs-buy.


2. Stack nào phù hợp để build phần mềm quản lý spa custom?

Stack mình chọn cần đáp ứng 3 ràng buộc: realtime sync (chống double booking), Zalo-native (phủ 79.6M MAU end 2025, DataReportal, 2026), và có thể ship trong 2 tuần. Combo Next.js + Supabase + Vercel đáp ứng cả ba mà không cần devops phức tạp. Đây là kiến trúc mình triển khai sau brief với Claude Code:

Architecture overview phần mềm quản lý spa: Next.js, Supabase Realtime, Zalo OA, Vercel

Frontend:   Next.js 15 (App Router) + Tailwind CSS + shadcn/ui
Backend:    Next.js API Routes (serverless)
Database:   Supabase (PostgreSQL) + Realtime subscriptions
Auth:       Supabase Auth (Google SSO + email)
Zalo:       Zalo OA API (nhắc lịch + booking confirm)
Hosting:    Vercel (frontend + API) + Supabase Cloud

Lý do chọn Supabase: Realtime subscriptions giải quyết Pain 4 (double booking). Calendar update real-time qua WebSocket, không cần polling. Và Supabase Row Level Security giúp phân quyền theo chi nhánh mà không phải viết auth logic từ đầu.

Database schema chính:

-- Prompt cho Claude Code: "Thiết kế schema PostgreSQL cho spa management
-- với multi-branch, dual-therapist booking, service categories"

CREATE TABLE branches (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  address TEXT,
  phone TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE services (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  duration_minutes INTEGER NOT NULL,
  price DECIMAL(12,0) NOT NULL, -- VND
  requires_dual_therapist BOOLEAN DEFAULT FALSE,
  branch_id UUID REFERENCES branches(id),
  category TEXT -- 'massage', 'facial', 'body', 'nail'
);

CREATE TABLE therapists (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  branch_id UUID REFERENCES branches(id),
  specializations TEXT[], -- ['massage', 'facial']
  is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE bookings (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_name TEXT NOT NULL,
  customer_phone TEXT NOT NULL,
  customer_zalo_id TEXT, -- để gửi nhắc lịch
  service_id UUID REFERENCES services(id),
  branch_id UUID REFERENCES branches(id),
  primary_therapist_id UUID REFERENCES therapists(id),
  secondary_therapist_id UUID REFERENCES therapists(id), -- nullable
  scheduled_at TIMESTAMPTZ NOT NULL,
  status TEXT DEFAULT 'confirmed', -- confirmed/completed/cancelled/no_show
  notes TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Index cho calendar query
CREATE INDEX idx_bookings_scheduled ON bookings(branch_id, scheduled_at, status);

Claude Code generate schema này trong 3 phút từ plain-text description. Mình chỉ cần review và thêm index cho branch_id. Có lẽ bạn nghĩ schema đơn giản thế thì viết tay cũng nhanh? Đúng, nhưng khi project đang chạy có thêm 5-7 bảng phụ (membership, voucher, payroll), lợi thế tích luỹ rõ rệt.


3. Booking system: làm sao chống double booking real-time?

Booking là module phức tạp nhất. Trong case study này, double booking xảy ra 2-3 lần/tuần trên hệ thống cũ vì sync delay giữa chi nhánh. Kiến trúc mới chống bằng 3 lớp: server-side availability check, Postgres transaction, và Supabase Realtime đẩy event tới mọi browser tab. Kết quả: zero double booking sau go-live, đo theo log 8 tuần đầu.

Booking flow phần mềm quản lý spa: availability check, dual-therapist assign, realtime sync

Availability check API (Claude Code viết khoảng 80%, mình review và fix edge case):

// /app/api/availability/route.ts
import { createServerClient } from '@supabase/ssr'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url)
  const serviceId = searchParams.get('service_id')
  const branchId = searchParams.get('branch_id')
  const date = searchParams.get('date') // YYYY-MM-DD

  const supabase = createServerClient(/* config */)

  // Lấy service duration
  const { data: service } = await supabase
    .from('services')
    .select('duration_minutes, requires_dual_therapist')
    .eq('id', serviceId)
    .single()

  if (!service) return NextResponse.json({ error: 'Service not found' }, { status: 404 })

  // Lấy tất cả booking trong ngày đó theo branch
  const startOfDay = `${date}T00:00:00+07:00`
  const endOfDay = `${date}T23:59:59+07:00`

  const { data: existingBookings } = await supabase
    .from('bookings')
    .select('scheduled_at, primary_therapist_id, secondary_therapist_id, service:services(duration_minutes)')
    .eq('branch_id', branchId)
    .gte('scheduled_at', startOfDay)
    .lte('scheduled_at', endOfDay)
    .in('status', ['confirmed'])

  // Generate available slots (9:00 - 20:00, interval 30 min)
  const slots = generateTimeSlots('09:00', '20:00', 30)
  const availableSlots = slots.filter(slot => {
    const slotStart = new Date(`${date}T${slot}:00+07:00`)
    const slotEnd = new Date(slotStart.getTime() + service.duration_minutes * 60000)
    // Check conflict với existing bookings
    return !existingBookings?.some(b => {
      const bStart = new Date(b.scheduled_at)
      const bEnd = new Date(bStart.getTime() + (b.service?.duration_minutes ?? 60) * 60000)
      return slotStart < bEnd && slotEnd > bStart
    })
  })

  return NextResponse.json({ slots: availableSlots, service })
}

Phần tricky nhất là dual-therapist conflict check. Mình phải prompt Claude Code thêm 2 lần để ra logic đúng. Nhưng kết quả cuối chuẩn, không cần viết lại từ đầu.


4. Quản lý ca nhân viên: dual-therapist scheduling thế nào?

Module này giải quyết Pain 1: assign 2 therapist, track ca theo tuần. Theo data nội bộ của spa, 34% lượng booking thuộc dịch vụ cần 2 therapist (massage couple, body wrap đôi, facial cao cấp). Phần mềm cũ track ca thứ 2 bằng Excel rời, sai sót ~5 ca/tuần. Hệ thống mới đưa vào schema thì lỗi đó về 0.

Mình dùng drag-and-drop calendar (FullCalendar.js + React) cho staff scheduling. Claude Code giúp write integration layer giữa FullCalendar events và Supabase bookings:

// Sync FullCalendar events từ Supabase bookings
const fetchBookingsForCalendar = async (start: Date, end: Date) => {
  const { data } = await supabase
    .from('bookings')
    .select(`
      id, scheduled_at, status, customer_name,
      service:services(name, duration_minutes, color_code),
      primary_therapist:therapists!primary_therapist_id(name),
      secondary_therapist:therapists!secondary_therapist_id(name)
    `)
    .gte('scheduled_at', start.toISOString())
    .lte('scheduled_at', end.toISOString())
    .eq('branch_id', currentBranch)

  return data?.map(b => ({
    id: b.id,
    title: `${b.customer_name} ${b.service.name}`,
    start: b.scheduled_at,
    end: new Date(
      new Date(b.scheduled_at).getTime() + b.service.duration_minutes * 60000
    ).toISOString(),
    backgroundColor: b.service.color_code,
    extendedProps: {
      therapists: [b.primary_therapist?.name, b.secondary_therapist?.name].filter(Boolean),
      status: b.status
    }
  }))
}

Real-time update qua Supabase Realtime giải quyết Pain 4:

// Subscribe to booking changes (giải quyết double-booking issue)
useEffect(() => {
  const channel = supabase
    .channel('booking-changes')
    .on('postgres_changes', {
      event: '*', schema: 'public', table: 'bookings',
      filter: `branch_id=eq.${currentBranch}`
    }, (payload) => {
      calendarRef.current?.getApi().refetchEvents()
    })
    .subscribe()

  return () => { supabase.removeChannel(channel) }
}, [currentBranch])

5. Revenue tracking: báo cáo custom theo chi nhánh và dịch vụ

Pain 3 (báo cáo cố định) hay là chỗ owner khó chịu nhất. Trong dữ liệu khảo sát nội bộ, owner spa muốn nhìn doanh thu chia theo 3 chiều: dịch vụ, chi nhánh, tuần, đối chiếu mục tiêu monthly. Một SQL query 30 dòng thay được dashboard $50/tháng của vendor. Claude Code generate query, mình thêm index và visualize bằng Recharts.

Revenue dashboard phần mềm quản lý spa: doanh thu theo dịch vụ, chi nhánh, tuần

-- Revenue by service category by branch by week
SELECT
  b.branch_id,
  br.name as branch_name,
  s.category,
  date_trunc('week', bk.scheduled_at AT TIME ZONE 'Asia/Ho_Chi_Minh') as week_start,
  COUNT(*) as booking_count,
  SUM(s.price) as revenue
FROM bookings bk
JOIN services s ON s.id = bk.service_id
JOIN branches br ON br.id = bk.branch_id
WHERE bk.status = 'completed'
  AND bk.scheduled_at >= NOW() - INTERVAL '12 weeks'
GROUP BY 1, 2, 3, 4
ORDER BY 4 DESC, 6 DESC;

Claude Code viết query này từ description "doanh thu theo loại dịch vụ theo chi nhánh theo tuần 12 tuần gần nhất", không cần mình tự viết SQL. Mình chỉ thêm index sau khi thấy query chạy chậm.


6. Tích hợp Zalo OA: vì sao là tính năng killer?

Zalo có 79.6 triệu MAU cuối 2025, phủ 91.4% người dùng internet Việt và gửi 2.1 tỷ tin nhắn/ngày (DataReportal, 2026). Với spa, đây là kênh nhắc lịch hiệu quả gấp nhiều lần SMS. Mình build notification job chạy cron mỗi giờ, gửi nhắc lịch trước 24h và 2h qua Zalo OA API. Sau 4 tuần go-live, no-show rate giảm từ 18% xuống 11%.

// /app/api/cron/reminder/route.ts
// Chạy mỗi giờ, tìm booking trong 2h và 24h tới, gửi Zalo reminder

const ZALO_OA_TOKEN = process.env.ZALO_OA_TOKEN

async function sendZaloReminder(booking: Booking) {
  if (!booking.customer_zalo_id) return

  const hoursUntil = Math.round(
    (new Date(booking.scheduled_at).getTime() - Date.now()) / 3600000
  )
  const message = hoursUntil <= 2
    ? `Nhắc lịch: Bạn có lịch ${booking.service.name} lúc ${formatTime(booking.scheduled_at)} hôm nay tại ${booking.branch.name}. Hẹn gặp bạn!`
    : `Nhắc lịch: Bạn có lịch ${booking.service.name} lúc ${formatTime(booking.scheduled_at)} ngày mai tại ${booking.branch.name}.`

  await fetch('https://openapi.zalo.me/v2.0/oa/message', {
    method: 'POST',
    headers: {
      'access_token': ZALO_OA_TOKEN!,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      recipient: { user_id: booking.customer_zalo_id },
      message: { text: message }
    })
  })
}

Cần Zalo OA account và xin permission "Gửi tin nhắn ZNS" từ Zalo. Chi tiết workflow Zalo OA integration xem ở ZaloCRM: Giải Pháp CRM Tích Hợp Zalo Cho SME Việt.


7. Claude Code đã giúp gì thật sự?

Tracking thời gian cho project này, Claude Code rút ~75% giờ code thuần (28h xuống 7h). Tỉ lệ này đồng pha với GitHub Research 2024: dev có AI assistant hoàn thành task nhanh hơn 55.8%, n=95 (GitHub, 2024). Khác biệt: case study của mình tính cả thời gian thiết kế schema, viết SQL phân tích, và setup integration. Code review time vẫn giữ nguyên.

Claude Code workflow build phần mềm spa: prompt, generate, review, deploy

Task Không có Claude Code Với Claude Code Tiết kiệm
Database schema design 4 giờ 30 phút 3.5h
API routes (CRUD) 8 giờ 2 giờ 6h
Availability algorithm 6 giờ 2 giờ 4h
Calendar integration 4 giờ 1 giờ 3h
SQL analytics queries 3 giờ 30 phút 2.5h
Zalo integration 3 giờ 1 giờ 2h
Tổng ước tính 28 giờ 7 giờ 21 giờ (75%)

Disclaimer: Số giờ này với dev có khoảng 3 năm kinh nghiệm full-stack. Dev junior sẽ có tỉ lệ khác. Và "tiết kiệm" không tính review time. Mình vẫn review mọi đoạn code Claude viết, đặc biệt auth và security logic.

Chỗ Claude Code KHÔNG giúp được: - Product decision: "booking flow nên có bao nhiêu bước?" cần nói chuyện với chủ spa - UX testing: "button này đặt ở đâu thì user dễ thấy?" cần test thật - Edge case từ nghiệp vụ: "khách cancel sau khi therapist đã check-in thì tính lương thế nào?" thuộc domain knowledge không có trong training data

Xem thêm: Claude Code Là Gì? So Sánh Cursor Và Copilot nếu chưa dùng Claude Code.


Workflow xây phần mềm spa custom: brief, build với Claude Code, deploy, go-live 2 tuần

8. Chi phí build vs buy: payback period bao lâu?

Trên 3 chi nhánh đang trả $150/tháng cho phần mềm cũ ($1,800/năm), build custom tốn ~$1,500 setup + $360/năm infra. Payback period là 9 tháng, từ năm thứ 2 tiết kiệm $1,440/năm. So sánh full TCO 3 năm: SaaS off-the-shelf $5,400, custom $2,580, chênh lệch ~52%. Nhưng custom chỉ rẻ hơn nếu bạn (hoặc team) tự maintain được.

Phần mềm spa off-the-shelf Custom (case study này)
Setup cost $0-500 ~$1,500 (40h engineering @ $37/h)
Monthly cost $50-200/tháng ~$30/tháng (Vercel + Supabase)
3-year TCO $1,800-7,700 $2,580
Custom feature Phải request, uncertain timeline Own codebase, ship trong ngày
Data ownership Vendor database Own database (Supabase)
Multi-branch Thường extra fee Built-in
Zalo integration Thường không có Custom, fit workflow

Không phải mọi trường hợp đều phù hợp custom. Xem TCO Phần Mềm Mua vs Custom để biết khi nào mua thật sự rẻ hơn.


9. FAQ

Q1: Tech stack này có phù hợp cho spa nhỏ 1 chi nhánh không? Có, nhưng hơi overkill. Với 1 chi nhánh dưới 50 booking/ngày, Supabase free tier đủ dùng (500MB DB, 2GB bandwidth) và Vercel hobby đủ dùng. Total infra cost ~$0. Nhưng nếu không có dev in-house để maintain, nên xem xét off-the-shelf trước. Spa nhỏ thường break-even chậm hơn nhiều, thường 18-24 tháng.

Q2: Mất bao lâu để xây phần mềm spa tương tự? Developer có ~3 năm kinh nghiệm full-stack: 2-3 tuần cho MVP, 1-2 tháng cho production-ready với edge cases. Dùng Claude Code rút ngắn 60-75% thời gian code thuần (đồng pha GitHub Research 2024 đo 55.8%, GitHub, 2024). Planning, product thinking, và testing vẫn cần thời gian như cũ.

Q3: Có thể thuê mình build không? Mình không nhận project outsourcing trực tiếp hiện tại. Bài này chia sẻ methodology và code snippet thật. Bạn có thể tự build hoặc thuê developer dựa trên spec này. Nếu cần tham vấn architecture cho spa lớn hơn 5 chi nhánh, gửi mail qua trang liên hệ và mình review giúp.

Q4: Zalo OA API có miễn phí không? Zalo OA API cơ bản (gửi tin nhắn OA cho follower) miễn phí có quota. ZNS (Zalo Notification Service) tính phí theo tin: ~300-800 VND/tin tuỳ template. Với 200 booking/ngày nhắc 2 lần là 400 tin/ngày, chi phí 120,000-240,000 VND/ngày. Zalo có 79.6M MAU end 2025 nên ROI vẫn dương rõ (DataReportal, 2026).

Q5: Supabase có reliable cho production không? Có. Supabase đang chạy hàng trăm nghìn production app, SLA 99.9% ở paid plan. Với SME Việt, gói Pro $25/tháng đủ dùng: 8GB database, 250,000 MAU, daily backup, point-in-time recovery 7 ngày. Mình đang chạy 3 production project trên Supabase Pro, chưa gặp downtime đáng kể trong 14 tháng vận hành.

Q6: Source code có public không? Chưa public vì chứa thông tin khách hàng (schema dịch vụ, giá, nhân viên). Nếu có đủ request, mình sẽ clean và release template open source: schema chuẩn, API routes, FullCalendar wrapper, Zalo helper. Comment vào bài này hoặc liên hệ qua site, mình sẽ thông báo khi template sẵn sàng.


Kết luận

Năm 2026: Claude Code + Next.js + Supabase là combo có thể build phần mềm quản lý spa custom đủ dùng trong 2 tuần. ROI rõ ràng sau 9 tháng so với off-the-shelf. Điều kiện: có developer khoảng 3 năm kinh nghiệm.

Cái mình ấn tượng nhất không phải tốc độ, mà là quality của code Claude Code generate. Schema database chuẩn, API routes handle edge case tốt, code readable và maintainable. Không phải "vibe coding", đây là production code thật.

Quay về cluster: Phần Mềm Custom: Toàn Bộ Guide

Đọc tiếp: - Phần Mềm Custom Là Gì? Khi Nào Nên Dùng? - TCO Phần Mềm Mua vs Custom: Tính Thế Nào Cho Đúng? - Claude Code Là Gì? So Sánh Cursor vs Copilot (tool mình dùng)

Tích hợp Zalo: Pipeline này dùng Zalo OA cho nhắc lịch, xem workflow đầy đủ tại ZaloCRM.


Tác giả: Loc Nguyen Data Team. Case study thực tế, khách hàng anonymized theo yêu cầu. Code snippets được adapt từ production codebase (một số chi tiết thay đổi vì lý do bảo mật).

Cập nhật lần cuối: 30/04/2026.

trong Claude AI