الأبحاث · جولةُ البرمجة الأولى
CB-2-10 — تصميم معماري · — · متوسّط
جولةُ البرمجة الأولى · 2026-10-15 · GAIBRA v2.31.01 · بلا تشغيل
| الترتيب | المتنافس | الرمز أمام المحكّم | الكلمات | الاختبارات | Opus | Grok | المجموع | الصدق (Opus · Grok) |
|---|---|---|---|---|---|---|---|---|
| 1 | Claude Sonnet | ج | 1,640 | — | 92.0 | 90.0 | 91.0 | 11 · 11 |
| 2 | ChatGPT | أ | 2,511 | — | 84.0 | 93.0 | 88.5 | 11 · 11 |
| 3 | DeepSeek | د | 1,617 | — | 64.0 | 70.0 | 67.0 | 6 · 7 |
| 4 | Gemini | ب | 1,198 | — | 62.0 | 71.0 | 66.5 | 8 · 8 |
| 5 | GAIBRA | هـ | 663 | — | 53.0 | 77.0 | 65.0 | 3 · 10 |
اختلف المحكّمان على المركز الأوّل: Opus اختار Claude Sonnet، وGrok اختار ChatGPT.
نصُّ السيناريو كما كتبه المولّد
انا المؤسس التقني لسلسلة عيادات أسنان (12 فرعاً في مدينتين)، ونستبدل نظام الحجز الورقي/الهاتفي بتطبيق حجز مواعيد، وأريد مراجعة تصميمي الأولي قبل التنفيذ.
الأرقام والقيود:
\* 40 ألف حجز شهرياً متوقع خلال السنة الأولى، الذروة 120 حجزاً في الساعة مساءً.
\* كل فرع 4–8 كراسي علاج، والموعد 30 أو 60 دقيقة حسب نوع الإجراء، والجدول ينقسم ورديتين.
\* تعارض الحجز مستحيل: نفس الكرسي ونفس الفترة لا يقبل حجزين، حتى مع حجزين متزامنين من مريضين مختلفين عبر التطبيق والاستقبال في نفس اللحظة.
\* 3 عيادات من الفروع الاثني عشر تفتقر لإنترنت مستقر، لكن الاستقبال يجب أن يعرف جدول يومه ويسجّل الحضور حتى أثناء انقطاع الشبكة، والمزامنة عند عودتها.
\* إشعارات SMS للتذكير قبل 24 ساعة وقبل ساعتين عبر مزود خارجي واحد، مع إعادة المحاولة عند فشل الإرسال.
\* تقرير إشغال الكراسي اليومي لكل فرع، يُبنى ليلاً ويُرسل للإدارة صباحاً.
\* الفريق: 4 مطورين (2 باك إند، 1 ويب، 1 موبايل)، خبرتهم متوسطة، لا خبرة سابقة في أنظمة موزعة.
\* الميزانية السحابية: 800 دولار شهرياً كحد أقى.
المطلوب: تصميم على مستوى المكونات — خدمات أو وحدات، نموذج بيانات موجز لأهم الكيانات، وآلية ضمان عدم التعارض في الحجز مع قيود الاتصال الضعيف في الفروع النائية (مقارنة بين البدائل المرشحة ولماذا اخترت ما اخترت)، وخطة مراحل تنفيذ تناسب حجم الفريق. وضّح أيضاً أين سيتجاوز التصميم ميزانية 800 دولار أولاً إذا تضاعفت الأحمال.
الردودُ الخمسة كما وصلت المحكّمين
هـGAIBRA
الخلاصة
الأنسب لفريقكم مونوليث مُقسَّم (Modular Monolith) فوق قاعدة بيانات مركزية واحدة، ومنع التعارض يُحسم داخل القاعدة لا في كود التطبيق.
الفروع الثلاثة غير المستقرة تقرأ جدولها وتسجّل الحضور محلياً ثم تُزامن، لكنها لا تنشئ حجوزات جديدة دون اتصال إلا بحصص مسبقة.
عند تضاعف الأحمال تضغط الميزانية أولاً قاعدة البيانات مع التقرير الليلي، ثم رسائل SMS.
الخيار المعتمد هو المونوليث المُقسَّم (ModularMonolith) بملاءمة 78٪، يليه الشرائح العمودية (VerticalSliceArchitecture) بـ0٫77 ثم المعمارية النظيفة (CleanArchitecture) بـ0٫76. المعمارية السداسية بـ0٫75 رُفضت لأن ملاءمة الفريق لها أقل. الفروق بين الخيارات ضيقة، لكن فريقاً من 4 مطورين بلا خبرة في الأنظمة الموزعة لا ينبغي أن يتحمّل تعقيد شبكة بين خدمات منفصلة. جاهزية الفريق 88٪، وفترة التأهيل نحو 1٫2 أسبوع، ومخاطر التنفيذ 14٪.
في مسوّدة السجل خطآن يلزم تصحيحهما: كتبت أن الفريق نحو 8 مطورين وأنتم 4، وقيّمت الملاءمة بـ0٫92 بينما التقييم 78٪. حالة القرار ما زالت Proposed.
أما منع التعارض فهو لبّ التصميم، وهذه البدائل:
آلية منع التعارض
حجز مركزي بقيد في القاعدة
القاعدة تُسلسل الطلبين المتزامنين، فيقبل أحدهما ويُرفض الآخر
التعارض مستحيل بالبناء لا بالاختبار
بسيطة على فريق بلا خبرة موزعة
إنشاء الحجز يحتاج اتصالاً بالخادم
الفروع الثلاثة لا تحجز أثناء الانقطاع
حجز محلي ثم تسوية عند المزامنة
يعمل في الفرع المنقطع كاملاً
التعارض يُكتشف بعد وقوعه، وهذا يخالف شرطكم
يحتاج قواعد لحلّ التعارض واعتذاراً للمريض
حصص مسبقة للفروع المنقطعة
كل فرع منقطع يصدر حجوزاته من كراسٍ وفترات مخصّصة له، فلا تتقاطع مع غيره
يحافظ على استحالة التعارض
يقلّل مرونة الجدول
يحتاج شاشة لإدارة الحصص
نسخ متعددة الكتابة (multi-master / CRDT)
استقلال كامل لكل فرع
لا يضمن حصرية الكرسي والفترة
تعقيد يفوق خبرة الفريق
الترجيح: المركزي لكل الفروع، وتُضاف الحصص المسبقة للفروع الثلاثة إن لزم الحجز دون اتصال.
تسجيل الحضور لا ينافس على فترة، فيُسجَّل محلياً أحداثاً تُضاف ولا تُعدَّل (append-only)، وكل حدث بمعرّف فريد يجعل إعادة إرساله عند المزامنة آمنة. وتكفي الكيانات التالية: Branch وChair وPatient، وAppointment (الكرسي والبداية والنهاية والحالة)، وAttendanceEvent (معرّف الجهاز ومفتاح عدم التكرار)، وReminderJob (نوعه 24 ساعة أو ساعتان، وعدد المحاولات، وحالته). القيد يُوضع على الكرسي والفترة الزمنية في جدول Appointment نفسه.
الرسومات المعماريَّة
الوحدتان A وB هيكل عام. اقتراحي أن تكون الأولى للحجز والجدولة والثانية للتذكيرات والتقارير، فيبقى القيد الحاسم في وحدة واحدة.
خريطة الملفَّات
MyApp/Modules/ModuleA/Domain/ \[Domain/DTO] — نطاق الوحدة A
MyApp/Modules/ModuleA/Application/ \[Application/Implementation] — تطبيق الوحدة A
MyApp/Modules/ModuleA/Infrastructure/ \[Infrastructure/Implementation] — بنية الوحدة A
MyApp/Modules/ModuleA/API/ \[Presentation/Controller] — واجهة الوحدة A
MyApp/Shared/Contracts/ \[Application/Interface] — عقود بين الوحدات
MyApp/Shared/Events/ \[Application/DTO] — أحداث مشتركة
MyApp/Host/Program.cs \[Presentation/Configuration] — تجميع الوحدات
الجهد المقدَّر نحو 8 يوم عمل لـ50 ملفاً. أقرؤه تقدير هيكلٍ أولي لا تقدير اكتمال المزامنة دون اتصال.
تخطيط السعة
الحمل نفسه خفيف: 40 ألف حجز شهرياً وذروة 120 في الساعة لا تحتاج حوسبة ضخمة. لذلك لن يكون الخادم أول ما يتجاوز الـ800 دولار.
أول بند يرتفع قاعدة البيانات المُدارة، لأن التقرير الليلي لإشغال الكراسي في 12 فرعاً يزاحم الحجز الحي عليها. فإذا تضاعف الحمل فصلتُ التقرير إلى نسخة قراءة، وهي أول زيادة فعلية في الفاتورة. بعدها تأتي رسائل SMS: لكل حجز تذكيران، وكل إعادة محاولة تُحسب إرسالاً جديداً، فتنمو الكلفة خطياً مع الحجوزات. فإن حُسبت ضمن الـ800 تقدّمت على قاعدة البيانات. هذا ترتيبٌ لا مبالغ، فلا رقم كلفة لدي لكل بند، وهذا تقديري.
بنية الفرق التقنيَّة
أقترح أن يملك أحد مطوّري الباك إند نواة الحجز والقيد ومزامنة الحضور، ويملك الآخر التذكيرات والتقارير. ويتولى مطوّر الموبايل التخزين المحلي للفروع المنقطعة، ومطوّر الويب شاشة الاستقبال.
مراحل التنفيذ
١
نواة الحجز
نموذج البيانات والقيد، مع اختبار طلبين متزامنين على الكرسي والفترة نفسيهما.
٢
شاشة الاستقبال على الويب
الحجز المركزي وعرض الجدول.
٣
العمل دون اتصال
نسخة محلية من جدول اليوم وأحداث الحضور مع مزامنة آمنة للتكرار.
٤
التذكيرات
SMS قبل 24 ساعة وقبل ساعتين مع إعادة المحاولة.
٥
التقرير الليلي
إشغال الكراسي لكل فرع وإرساله للإدارة صباحاً.
٦
تجربة الفروع الثلاثة ثم التعميم
البدء بالفروع المنقطعة لأنها أصعب اختبار.
ترجيحي إذن: المونوليث المُقسَّم مع حجز مركزي بقيد في القاعدة. الشرط الذي يقلب ترجيحي: أن يحتاج استقبال الفروع الثلاثة إلى إنشاء حجوزات جديدة أثناء الانقطاع، وعندها تُضاف الحصص المسبقة. فهل يحجز استقبالهم مرضى جدداً أثناء الانقطاع، أم يكتفي بالاطلاع على الجدول وتسجيل الحضور؟ جوابك يحسم هذه النقطة، والقرار لك.
جClaude Sonnet
مراجعة تصميم نظام حجز مواعيد العيادات
الخلاصة
اعتمد نظاماً واحداً متماسكاً (Modular Monolith) مع PostgreSQL مصدراً وحيداً للحقيقة. لا تبدأ بخدمات مصغّرة. الحمل صغير جداً، وفريقك بلا خبرة في الأنظمة الموزعة.
اجعل قاعدة البيانات هي التي تمنع التعارض، عبر Exclusion Constraint على (الكرسي، الفترة الزمنية)، لا كود التطبيق ولا Redis.
أثناء انقطاع الإنترنت، لا يمكن ضمان "عدم التعارض" وقبول حجوزات جديدة من أكثر من كاتب في آن واحد. أقترح قاعدة "كاتب وحيد" للنافذة القريبة في الفروع الثلاثة الضعيفة، مع وضع عمل دون اتصال يقتصر على قراءة الجدول وتسجيل الحضور.
أول ما سيكسر الميزانية هو تكلفة الرسائل SMS على الأرجح، لا الخوادم.
1\) حجم الحمل
120 حجزاً في الساعة تعني نحو حجز كل 30 ثانية. حتى لو كان كل حجز يسبقه 20–50 استعلام توفّر، فالحمل في حدود 1–2 طلب في الثانية. نسخة PostgreSQL متوسطة تتحمل هذا بسهولة، فالصعوبة هنا في الصحة والعمل دون اتصال والتعقيد التشغيلي، لا في الأداء.
2\) المكونات
 Patient App (mobile) Reception PWA (offline-capable) Admin (web)
  \\ | (outbox sync) /
  +-------------- HTTPS / REST API ----------------+
  |
  ┌────────────── Modular Monolith (كود واحد) ─────────────┐
  │ Identity | Catalog \& Schedules | Booking | Sync │
  │ Notifications | Reporting | Audit │
  └───────────────┬───────────────────────────┬────────────┘
  | |
  PostgreSQL (primary Worker process (نفس الكود)
  + standby + PITR) - إرسال SMS وإعادة المحاولة
  + جداول المهام - إنهاء الحجوزات المعلّقة
  - تقرير الإشغال الليلي
  |
  SMS Provider / Email
الوحدة المسؤولية
Identity دخول المرضى (OTP)، وأدوار الموظفين
Catalog \& Schedules الفروع، الكراسي، الورديات، الإجازات، أنواع الإجراءات ومددها
Booking حساب التوفّر، الحجز المؤقت (Hold)، الإنشاء/الإلغاء/إعادة الجدولة، آلة الحالات
Sync لقطة اليوم للفرع، استقبال العمليات المتراكمة، نتائج كل عملية
Notifications جدولة التذكيرات، مهام الإرسال، إعادة المحاولة، محوّل مزوّد SMS
Reporting حساب الإشغال ليلاً وإرسال التقرير صباحاً
Audit سجل تغيّر حالات كل موعد
قواعد تحفظ لك خيار التقسيم لاحقاً: كل وحدة تملك جداولها، وتتواصل الوحدات عبر واجهات داخلية لا عبر Joins مباشرة. وليكن Worker عملية منفصلة بنفس الكود.
العملاء: تطبيق المريض (مطوّر الموبايل، يُفضَّل Flutter أو React Native لقاعدة كود واحدة)، وواجهة الاستقبال كـ PWA (مطوّر الويب) لأنها تدعم التخزين المحلي والعمل دون اتصال، ولوحة الإدارة في نفس مشروع الويب.
3\) نموذج البيانات (الكيانات الأهم)
الكيان أهم الحقول
branch id, city, name, timezone, offline\_prone, local\_authority\_hours
chair id, branch\_id, label, active
chair\_shift chair\_id, weekday, start\_time, end\_time (وردية صباحية/مسائية)
schedule\_exception chair\_id/branch\_id, date\_range, reason (إجازة، صيانة)
service id, name, duration\_min (30/60)
practitioner id, branch\_id, name
patient id, phone (فريد)، name، بيانات التواصل
appointment id (UUID)، branch\_id، chair\_id، patient\_id، service\_id، practitioner\_id، during (tstzrange)، status، source (app/reception/phone)، expires\_at (للحجز المعلّق)، version، created\_at
appointment\_event appointment\_id, from→to status, actor, device\_id, at
processed\_op op\_id (UUID فريد)، device\_id، result، at (لمنع التكرار في المزامنة)
notification\_job appointment\_id, kind (remind\_24h/remind\_2h)، scheduled\_for، status، attempts، next\_attempt\_at، provider\_ref؛ فريد على (appointment\_id, kind)
occupancy\_daily branch\_id, date, available\_min, booked\_min, attended\_min, no\_show\_min, cancelled\_min
الحالات: held → confirmed → checked\_in → completed، مع cancelled وno\_show وexpired.
الأوقات: خزّنها بتوقيت UTC (timestamptz)، والعرض بتوقيت الفرع.
4\) ضمان عدم تعارض الحجز
البدائل
البديل الإيجابيات السلبيات الحكم
فحص ثم إدخال في الكود (حتى مع SERIALIZABLE) لا شيء جديد عرضة لسباقات التزامن إن أخطأ أحد، ويعتمد على انضباط الكود مرفوض
قفل موزّع (Redis) شائع مكوّن إضافي، ومشاكل انتهاء القفل، ولا يحمي من أخطاء الكود مرفوض
جدول خانات UNIQUE(chair, slot\_start) بسيط وآمن شبكة ثابتة 30 دقيقة، والموعد 60 دقيقة يحتاج صفين في معاملة واحدة، وتغيير الدقة مؤلم بديل احتياطي مقبول
Exclusion Constraint على الفترة الزمنية القاعدة تفرضه حتى لو أخطأ الكود، وأي مدة تعمل، ولا مكوّن إضافي خاص بـ PostgreSQL، ويحتاج btree\_gist (متاح في المزودين الرئيسيين، تحقق من مزودك) المختار
مزامنة متعددة الكتّاب (CRDT/PouchDB) عمل دون اتصال كامل لا يمكن دمج حجز على مورد حصري تلقائياً، وصعب على فريقك مرفوض
التطبيق
sql
CREATE EXTENSION IF NOT EXISTS btree\_gist;
ALTER TABLE appointment ADD CONSTRAINT no\_chair\_overlap
EXCLUDE USING gist (chair\_id WITH =, during WITH \&\&)
WHERE (status IN ('held','confirmed','checked\_in','completed'));
تدفق الحجز، في معاملة واحدة:
أنهِ الحجوزات المعلّقة المنتهية على هذا الكرسي (held وexpires\_at < now()).
أدخل الموعد بحالة held (مدة 5–10 دقائق) أو confirmed.
إن رجع الخطأ 23P01 فالفترة مأخوذة. ارجع 409 مع بدائل قريبة، أو جرّب الكرسي التالي من المرشحين.
المريض لا يختار كرسياً. يختار الفرع والخدمة والوقت، والنظام يجرّب الكراسي المتاحة بالترتيب.
كل الطلبات تحمل Idempotency-Key، فإعادة إرسال نفس الطلب لا تنشئ حجزين.
نقطة قد تفاجئك: إن كان طبيب الأسنان لا يستطيع التواجد على كرسيين في نفس الوقت، أضف قيداً ثانياً على practitioner\_id. تحقق من هذا قبل التنفيذ.
5\) الفروع الثلاثة ضعيفة الاتصال
صراحةً: أثناء الانقطاع، إذا سمحت لعدة كتّاب (الاستقبال المحلي والتطبيق ومركز الاتصال) بإنشاء حجوزات على نفس الجدول، فالتعارض ممكن ولا يمنعه أي تصميم. الحل أن يبقى كاتب واحد في النافذة الحرجة.
الحالة السلوك
متصل كل شيء يمر عبر الخادم والقيد في القاعدة
انقطاع: القراءة الاستقبال يملك لقطة محلية (IndexedDB) لجدول اليوم والغد، تُحدَّث عند كل اتصال
انقطاع: تسجيل الحضور/عدم الحضور/الإتمام يُسجَّل كعملية في صندوق صادر محلي بمعرّف UUID، وتُدفع عند عودة الشبكة. هذه عمليات حالة، لا تسبب تعارضاً
انقطاع: حجز زائر (Walk-in) لنفس اليوم مسموح لجهاز الاستقبال المعيَّن فقط، على فترات حرة في اللقطة
انقطاع: حجز لتاريخ لاحق غير مسموح. يُسجَّل "طلب اتصال" للمعالجة عند عودة الشبكة
قاعدة الكاتب الوحيد: للفروع الموسومة offline\_prone، الحجوزات التي تبدأ خلال local\_authority\_hours القادمة (أقترح "اليوم الحالي" كبداية) لا ينشئها إلا استقبال الفرع نفسه. المريض يرى هذه الفترات "اتصل بالعيادة". الثمن هو فقدان الحجز الإلكتروني لنفس اليوم في 3 فروع، وهو ثمن صغير مقابل ضمان حقيقي.
بروتوكول المزامنة:
Pull: لقطة الفرع مع cursor، بالحد الأدنى من بيانات المرضى، ومشفّرة على الجهاز مع تسجيل دخول ومسح تلقائي.
Push: دفعة عمليات {op\_id, type, appointment\_id, client\_ts, base\_version}. الخادم يعالج كل عملية في معاملة مستقلة ويرجع لكل واحدة applied أو duplicate أو rejected(reason).
حقيقة الحضور تُقبل دائماً: إن سُجِّل حضور على موعد أُلغي مركزياً، يُقبل ويُرفع تنبيه، لأن المريض حضر فعلاً.
شبكة أمان: إن فشل إدخال Walk-in عند المزامنة بسبب القيد، يُسجَّل بحالة conflict لا تشغل الكرسي، وتظهر مهمة معالجة يدوية للاستقبال. لا يحدث تعارض صامت في السجل المركزي.
فائدة جانبية: نفس الـ PWA يمنح الفروع الاثني عشر قراءة الجدول وتسجيل الحضور إن تعطل الخادم نفسه.
6\) الإشعارات والتقرير
الإشعارات:
لا تضف Kafka ولا RabbitMQ. استخدم جدول notification\_job مع SELECT … FOR UPDATE SKIP LOCKED، أو مكتبة طوابير مبنية على Postgres في لغتك.
الجدولة: عند تأكيد الحجز يُنشأ job لكل تذكير. التذكير الذي وقته مضى (حجز قبل أقل من 24 ساعة) يُتخطّى. الإلغاء أو التعديل يلغي الـ jobs ويعيد إنشاءها.
إعادة المحاولة: تراجع أسي (backoff) وحد أقصى للمحاولات، ثم حالة failed تظهر في لوحة المراقبة.
منع التكرار: القيد الفريد على (appointment\_id, kind)، ومعرّف مرجعي مع المزوّد إن كان يدعمه.
محوّل (Adapter) لمزوّد SMS: ليسهل تبديله لاحقاً، رغم أنك الآن مع مزوّد واحد.
تقرير الإشغال: job ليلي (مثلاً 02:00) يحسب لكل فرع ويكتب في occupancy\_daily (upsert، فيمكن إعادته بأمان)، ويُرسل بريد 07:00 للإدارة. احسب مقياسين: المحجوز والفعلي (الحضور)، فالفرق بينهما هو عدم الحضور.
7\) الميزانية (800 دولار)
هذه تقديرات تقريبية، تحقق منها بحاسبة مزودك:
البند تقدير شهري
PostgreSQL مُدار (2 vCPU / 8GB + standby + نسخ احتياطي PITR) 200–300
خادمان للتطبيق + Worker صغير 100–160
موازن حمل ونطاق وشبكة 30–50
مراقبة وسجلات (طبقات مجانية/صغيرة) 30–80
بيئة اختبار (Staging) مصغّرة 60–100
المجموع ≈ 420–690
تحقق من أمرين قبل الاعتماد:
إقامة البيانات: بيانات المرضى صحية. راجع متطلبات نظام حماية البيانات الشخصية وضوابط القطاع الصحي، فقد تحصرك في مزودين أو مناطق معينة وتغيّر الأسعار.
رسائل SMS: 40 ألف حجز × تذكيرين = 80 ألف رسالة شهرياً، غير رسائل OTP وتأكيدات الحجز. بسعر افتراضي 0.02 دولار للرسالة يكون الناتج 1,600 دولار، وهذا يتجاوز الميزانية دون أي مضاعفة. إن كانت الرسائل ضمن الـ 800 دولار، فهي أول مشكلة.
ترتيب ما يتجاوز الميزانية إن تضاعفت الأحمال:
SMS: خطي مع الحجوزات وهو الأكبر. لتخفيفه، اجعل الإشعار الفوري (Push) هو القناة الأولى لمستخدمي التطبيق، واحتفظ بـ SMS لغيرهم وكاحتياط. ويمكن الاكتفاء بتذكير واحد عبر SMS.
السجلات والمراقبة إن اخترت خدمة تُحاسب بالحجم (GB).
قاعدة البيانات: رفع الفئة يضيف نحو 100–150 دولار. هذا مطلب مالي لا معماري، وأقدّر أن الحاجة الحقيقية إليه بعد 5–10 أضعاف الحمل لا ضعفين. أجرِ اختبار حمل لتتأكد.
الخوادم: مستبعد عملياً عند الضعف.
أول قيد معماري (لا مالي) سيظهر هو استعلامات التوفّر إن كانت التطبيقات تستعلم بكثرة. ولن تحتاج لنسخة قراءة منفصلة للتقرير الليلي ما لم تصل لعشرات الأضعاف.
8\) خطة المراحل (تقدير تقريبي: 5–6 أشهر)
المرحلة المدة المحتوى
0 — تحقق 1–2 أسبوع تأكيد الافتراضات أدناه، واختبار الـ Constraint بـ 100 طلب متزامن، وآلة الحالات، وبيئات CI، وتجربة مزوّد SMS في Sandbox
1 — النواة 4–5 أسابيع Catalog + Booking + واجهة الاستقبال (متصلة فقط) + لوحة الإدارة. ضع Idempotency-Key وversion وappointment\_event من اليوم الأول، فهي أساس المزامنة لاحقاً. بالتوازي: مطوّر الموبايل يبني تطبيق المريض (دخول وتصفح وحجز وإلغاء). تجربة في فرع واحد مستقر الشبكة
2 — الإشعارات 3–4 أسابيع Jobs وSMS وإعادة المحاولة، وتجربة تطبيق المريض على 2–3 فروع
3 — العمل دون اتصال 4–5 أسابيع PWA مع لقطة محلية وصندوق صادر ونقطة Sync وشاشة حل التعارضات وقاعدة الكاتب الوحيد، واختبار قطع الشبكة فعلياً، ثم تجربة على فرع ضعيف واحد
4 — التقارير والتشغيل 2 أسبوع تقرير الإشغال، وتنبيهات (عمر طابور الإشعارات، فشل SMS، آخر مزامنة لكل فرع)، واختبار استعادة النسخ الاحتياطية، واختبار حمل بثلاثة أضعاف الذروة
5 — الإطلاق تدريجي موجات من الفروع، والورق بالتوازي أسبوعين في كل فرع
سبب تأخير العمل دون اتصال: هو أعقد جزء، ويحتاج النواة مستقرة أولاً.
9\) الافتراضات والمخاطر
أحتاج تأكيدك على:
هل الاستقبال في الفروع الضعيفة يحتاج إنشاء حجوزات جديدة أثناء الانقطاع، أم يكفي الجدول والحضور؟ تصميمي يفترض Walk-in لنفس اليوم فقط.
هل يوجد جهاز استقبال واحد معيَّن للكتابة في كل فرع؟ تعدد الأجهزة الكاتبة يتطلب تنسيقاً إضافياً.
هل الطبيب مقيّد بكرسي واحد في الوقت نفسه؟
هل رسائل SMS ضمن الـ 800 دولار؟ وكم سعر الرسالة؟
هل توجد متطلبات إقامة بيانات تحدد المزود؟
مخاطر إضافية:
تكرار ملفات المرضى: استخدم الجوال كمفتاح فريد مع التحقق بـ OTP.
OTP تضيف رسائل SMS: احسبها في الميزانية.
المزوّد الوحيد لـ SMS نقطة فشل: الاحتياط هو Push وإظهار الموعد داخل التطبيق.
النسخ الاحتياطي: فعّل PITR ودرّب الفريق على الاستعادة قبل الإطلاق.
إن أردت، أكتب لك الخطوة التالية: سكريبت اختبار التزامن للـ Constraint (المرحلة 0)، أو مخطط الجداول الكامل بصيغة SQL.
أChatGPT
التصميم المقترح لنظام حجز 12 فرعاً لعيادات الأسنان
توصيتي: تطبيق مركزي ببنية Modular Monolith، وقاعدة بيانات PostgreSQL واحدة مُدارة، مع آلية حجز ذرّية تمنع التعارض داخل قاعدة البيانات، وواجهة تشغيل محلية محدودة للفروع الثلاثة ضعيفة الاتصال.
القرار الأهم هنا ليس اختيار عدد الخدمات أو استخدام أحدث تقنيات التوزيع؛ بل الفصل بين عمليتين مختلفتين في متطلبات الاتساق:
حجز موعد جديد أو نقله: يجب ضمان عدم وجود تعارض حتى مع الطلبات المتزامنة.
تشغيل الاستقبال أثناء انقطاع الإنترنت: يجب أن يتيح الاطلاع على جدول اليوم وتسجيل الحضور دون الادعاء بأن حجزاً جديداً أصبح مؤكداً مركزياً.
هذا الفصل هو ما يجعل التصميم قابلاً للتنفيذ بفريق من أربعة مطورين وضمن ميزانية سحابية محدودة، دون بناء نظام موزع معقد منذ اليوم الأول.
1\. الصورة المعمارية
تطبيق المريض + واجهة الاستقبال
ويب متجاوب / تطبيق جوال / صلاحيات الموظفين
HTTPS · API · مصادقة وتفويض
الخادم المركزي — Modular Monolith
الحجز والجدولة
توافر الكراسي، الحجز، النقل والإلغاء
المرضى والموظفون
الهوية، الفروع، الصلاحيات
الإشعارات
تذكيرات SMS وإعادة المحاولة
التقارير
إشغال الكراسي ومؤشرات الفروع
PostgreSQL مُدار — مصدر الحقيقة للحجوزات
قيود سلامة · معاملات · سجل أحداث Outbox · نسخ احتياطية
عامل خلفي
إرسال SMS، إعادة المحاولة، بناء التقارير
تطبيق استقبال محلي
ذاكرة محلية مشفرة، طابور مزامنة، وضع Offline
لا أنصح ببدء المشروع بـ Microservices أو Kafka أو Kubernetes. الحجم المتوقع لا يبرر تكلفة التشغيل والمراقبة وتعقيد المعاملات الموزعة، كما أن خبرة الفريق لا تتطلب تحمل هذه الأعباء.
يمكن تنفيذ الخادم بوحدات داخل تطبيق ASP.NET Core واحد، مع عامل خلفي منفصل في عملية النشر عند الحاجة. فصل الوحدات منطقياً يتيح استخراج أي وحدة إلى خدمة مستقلة لاحقاً إذا ظهرت ضرورة فعلية.
2\. نموذج البيانات الأساسي
أقترح أن تكون جميع الأوقات مخزنة كـ timestamptz في PostgreSQL، وأن تُعرض للمستخدم وفق المنطقة الزمنية للفرع. وبما أن الفروع في مدينتين سعوديتين، فإن المنطقة الزمنية المعتادة هي Asia/Riyadh.
الكيان
	
أهم الحقول
	
الغرض
Branch
	
Id, Name, City, TimeZone, IsActive
	
الفروع الاثنا عشر
Chair
	
Id, BranchId, Name, IsActive
	
كراسي العلاج الفعلية
Staff
	
Id, Name, Role, IsActive
	
موظفو الاستقبال والأطباء والإدارة
Patient
	
Id, Name, Phone, ConsentStatus
	
بيانات المريض وموافقاته
ProcedureType
	
Id, Name, DurationMinutes
	
أنواع الإجراءات ومدتها
StaffShift
	
Id, BranchId, StaffId, StartAt, EndAt
	
الورديات وتوافر الموظفين
ChairAvailability
	
Id, ChairId, StartAt, EndAt, Reason
	
أوقات إغلاق الكرسي أو عدم توافره
Appointment
	
Id, BranchId, ChairId, PatientId, ProcedureTypeId, StartAt, EndAt, Status, Version
	
الحجز وحالته
AppointmentEvent
	
Id, AppointmentId, EventType, OccurredAt, ActorId
	
سجل التدقيق والتغييرات
OutboxMessage
	
Id, Type, Payload, CreatedAt, ProcessedAt
	
الأحداث التي تنتظر المعالجة
SmsDelivery
	
Id, AppointmentId, ReminderType, DueAt, Status, AttemptCount, NextAttemptAt
	
تتبع كل تذكير ومحاولات إرساله
BranchSyncOperation
	
Id, BranchId, DeviceId, OperationId, Status, ServerVersion
	
تتبع عمليات الاستقبال المحلي ومزامنتها
قواعد مهمة في النموذج
يجب أن تكون مدة الإجراء معتمدة من نوع الإجراء، لكن تحفظ أيضاً في الحجز كقيمة ثابتة وقت الحجز، حتى لا يؤدي تغيير مدة الإجراء لاحقاً إلى تعديل مواعيد قديمة تلقائياً.
جدول الورديات لا يكفي وحده لتحديد صلاحية الحجز؛ يجب التأكد من توافر الكرسي والطبيب والموارد الأخرى المطلوبة طوال الفترة.
يجب حفظ وقت بداية الحجز ونهايته، لا الاكتفاء بوقت البداية ونوع الإجراء.
استخدم معرفاً فريداً للعملية OperationId للمزامنة، ومفتاح Idempotency-Key لطلبات الحجز، حتى لا يؤدي تكرار الطلب بعد انقطاع الاتصال إلى إنشاء حجزين.
تُحذف بيانات الاتصال أو تُقيد صلاحيات الوصول إليها وفق سياسة الاحتفاظ والخصوصية؛ ولا تُرسل بيانات المريض الطبية في رسائل التذكير.
3\. كيف نضمن استحالة تعارض الحجز؟
لا تعتمد على فحص التوافر في التطبيق وحده. حتى لو نفّذ الخادم استعلاماً مثل «هل الكرسي متاح؟» ثم أنشأ الحجز، يمكن لطلبين متزامنين قراءة النتيجة نفسها قبل أن يكتب أي منهما.
الضمان النهائي يجب أن يكون داخل قاعدة البيانات.
الخيار الذي أوصي به: قيد عدم التداخل
يدعم PostgreSQL أنواع النطاقات الزمنية وقيود الاستبعاد EXCLUDE التي ترفض وجود فترتين متداخلتين للكرسي نفسه.
PostgreSQL
+1
مثال توضيحي لمخطط الحجز:
CREATE EXTENSION IF NOT EXISTS btree\_gist;
CREATE TABLE appointments (
  id uuid PRIMARY KEY,
  branch\_id uuid NOT NULL,
  chair\_id uuid NOT NULL,
  patient\_id uuid NOT NULL,
  start\_at timestamptz NOT NULL,
  end\_at timestamptz NOT NULL,
  status text NOT NULL,
  CHECK (end\_at > start\_at),
  EXCLUDE USING gist (
  chair\_id WITH =,
  tstzrange(start\_at, end\_at, '\[)') WITH \&\&
  )
  WHERE (status IN ('Pending', 'Confirmed', 'CheckedIn'))
);
هذا مثال توضيحي؛ يجب أن تتطابق أسماء الحالات مع حالات النظام الفعلية، وأن تُعرّف سياسة انتهاء الحجوزات المؤقتة بوضوح. كما يجب أن تكون بيانات الكراسي ومراجع الفروع محمية بمفاتيح خارجية.
الفترة \[) تعني أن وقت النهاية غير مشمول. لذلك، إذا انتهى موعد الساعة 10:30 وبدأ التالي في الساعة 10:30، فلا يوجد تداخل.
ماذا يحدث عند طلبين متزامنين؟
طلب المريض A
حجز الكرسي 3 من 10:00 إلى 11:00
طلب الاستقبال B
حجز الكرسي 3 من 10:30 إلى 11:00
قاعدة البيانات
تقبل إحدى العمليتين، وترفض الأخرى عند التعارض. لا تستطيع العمليتان تثبيت حجزين متداخلين بنجاح.
الطلب المقبول يحصل على تأكيد بعد نجاح المعاملة.
الطلب المرفوض يحصل على استجابة واضحة تطلب اختيار فترة أخرى.
هل يكفي قيد الكرسي وحده؟
لا. يجب تطبيق مجموعة قواعد متكاملة:
الكرسي: منع تداخل المواعيد الفعالة على الكرسي نفسه.
الطبيب: منع حجز الطبيب في موعدين متداخلين، حتى لو كانا على كرسيين مختلفين.
الموارد الإضافية: تطبيق القيود نفسها على أي مورد لا يمكن استخدامه في وقت واحد.
الفرع والوردية: التحقق من أن الحجز داخل ساعات العمل وأن الموظف أو الطبيب متاح.
الحالات: لا تحتل المواعيد الملغاة أو المنتهية مساحة الحجز، أما المواعيد المؤقتة فتحتلها إلى أن تنتهي صلاحيتها بطريقة آمنة.
إذا كانت قاعدة البيانات تدعم قيود التداخل، فهي الضمان الأقوى والأوضح للكرسي. أما القواعد التي تعتمد على تجميع بيانات عدة جداول، فيمكن تنفيذها ضمن معاملة مع قفل مناسب أو مستوى SERIALIZABLE مع إعادة المحاولة عند فشل التسلسل. توثيق PostgreSQL يوضح أن مستوى SERIALIZABLE قد يتطلب إعادة تنفيذ المعاملة عند حدوث تعارض تسلسلي.
PostgreSQL
+1
وأوصي باختبارات ضغط تتضمن طلبات متزامنة حقيقية على الكرسي نفسه، مع التأكد من أن عدد الحجوزات المؤكدة المتداخلة يظل صفراً.
4\. الفروع الثلاثة ضعيفة الاتصال: المقارنة والقرار
هذه هي أصعب نقطة في التصميم، لأن العمل دون اتصال لا يمكن أن يمنح النظامين المحلي والمركزي معرفة فورية بما يحجزه الطرف الآخر.
هناك فرق جوهري بين إتاحة البيانات أثناء الانقطاع، والسماح بإنشاء حجوزات جديدة أثناءه.
البدائل المرشحة
البديل
	
منع التعارض
	
العمل دون إنترنت
	
التعقيد
حجز مركزي فقط
	
قوي
	
يتوقف الحجز عند الانقطاع
	
منخفض
قاعدة محلية مستقلة بكل فرع مع مزامنة لاحقة
	
لا يضمنه وحده
	
جيد
	
مرتفع
حجز مركزي مع صلاحيات حجز مسبقة موزعة
	
ممكن بشروط صارمة
	
جيد ضمن الحدود المخصصة
	
مرتفع جداً
عرض محلي للجدول وتسجيل الحضور دون اتصال
	
قوي للحجوزات المركزية
	
يحقق المطلوب
	
متوسط
اختياري هو البديل الرابع. فهو يحقق احتياجات الاستقبال المذكورة دون التضحية بضمان عدم التعارض أو إدخال نظام موزع معقد.
كيف يعمل عملياً؟
أثناء الاتصال
تُحمّل نسخة محلية مشفرة من جدول اليوم، مع أرقام الحجوزات وحالاتها اللازمة للاستقبال فقط. وتُحدّث دورياً مع الخادم.
أثناء الانقطاع
عرض جدول اليوم المحفوظ محلياً، مع إظهار وقت آخر مزامنة بوضوح.
تسجيل حضور المريض محلياً مع وقت العملية ومعرّف فريد.
تسجيل تغييرات الحضور في طابور محلي دائم ومشفّر.
إيقاف تأكيد أي حجز جديد أو نقل موعد لا يمكن التحقق منه مركزياً.
عند عودة الاتصال
إرسال عمليات الحضور إلى الخادم بترتيب واضح ومعرّفات ثابتة.
التحقق من صلاحية كل عملية وحفظها بمعاملة آمنة.
إرجاع نتيجة المزامنة لكل عملية؛ وتُعاد المحاولة عند الفشل.
تحديث الجدول المحلي من أحدث نسخة مركزية.
لماذا لا نسمح بالحجز دون اتصال؟
لنفترض أن فرعاً فقد الاتصال، وأن موظف الاستقبال حجز الكرسي 2 في الساعة 14:00. في الوقت نفسه، حجز مريض عبر التطبيق المركزي الكرسي نفسه في الوقت نفسه.
لا توجد وسيلة موثوقة للفرع المنقطع كي يعرف بالحجز المركزي الجديد. وإذا أكد كلا الطرفين الموعدين، فقد وقع التعارض بالفعل قبل بدء المزامنة.
لذلك، هناك ثلاثة خيارات إذا تغيرت المتطلبات مستقبلاً:
الاستمرار بالمنع: يمكن تسجيل طلب حجز محلي بوصفه غير مؤكد، ثم محاولة تثبيته عند عودة الاتصال.
حجز مسبق لصلاحية محددة: تخصيص فترات زمنية للفرع لا يمكن للخادم بيعها أو تخصيصها لطرف آخر طوال مدة التفويض. يتطلب هذا إدارة انتهاء الصلاحية وساعات الأجهزة ومخاطر فقدان الاتصال.
السماح بالحجز المحلي مع فض التعارض لاحقاً: لا يحقق شرط عدم التعارض، ولذلك لا أوصي به.
إذا كانت الأولوية هي عدم وجود حجزين متداخلين، فإن الحجز المحلي المؤكد دون تنسيق مسبق ليس خياراً مقبولاً.
نقاط أمنية وتشغيلية مهمة
تشفير قاعدة البيانات المحلية أو التخزين المحلي، مع قفل الجلسة وصلاحيات الموظفين.
تقليل البيانات الطبية المحفوظة محلياً إلى الحد الأدنى؛ فجدول الاستقبال لا يحتاج إلى نسخة كاملة من السجل الطبي.
وضع مدة صلاحية للنسخة المحلية، والتنبيه عند قدمها، وتحديد إجراء واضح إذا تجاوزت هذه المدة.
استخدام OperationId فريد لكل تسجيل حضور، وقيد فريد في الخادم لمنع تكرار تطبيق العملية.
إذا سجل موظفان تغييرات متعارضة على الحالة نفسها، فلا يُطبّق مبدأ «آخر كتابة تفوز» بشكل أعمى؛ بل تُحدد قاعدة عمل واضحة أو تُحال العملية للمراجعة.
5\. الإشعارات والتقارير الليلية
تذكيرات SMS
لا تجعل إرسال الرسالة جزءاً من معاملة حجز الموعد. إذا توقف مزود الرسائل، يجب ألا يفشل الحجز نفسه.
التدفق المقترح:
عند تأكيد الحجز أو تعديله، تُسجل الأحداث المطلوبة في OutboxMessage ضمن المعاملة نفسها.
يلتقط عامل خلفي الأحداث، وينشئ مهمتي تذكير: قبل 24 ساعة وقبل ساعتين.
يرسل العامل الرسالة عبر مزود SMS واحد.
عند الفشل المؤقت، يعيد المحاولة بتأخير تصاعدي، مع حد أقصى للمحاولات وطابور للرسائل التي تحتاج إلى مراجعة.
عند تغيير الموعد أو إلغائه، تُلغى التذكيرات القديمة أو يُتحقق من إصدار الحجز قبل الإرسال.
يجب أن يتضمن كل تذكير مفتاحاً فريداً، مثل (AppointmentId, ReminderType, AppointmentVersion)، لمنع إنشاء مهمات مكررة.
لكن توجد نقطة مهمة: إذا قبل مزود الرسالة الطلب ثم انقطع الاتصال قبل وصول تأكيده، فقد لا نعرف هل أُرسلت الرسالة أم لا. لذلك فإن ضمان «الإرسال مرة واحدة بالضبط» غير واقعي ما لم يدعم المزود آلية موثوقة لمنع التكرار. الهدف العملي هو إرسال قابل لإعادة المحاولة، مع تقليل الرسائل المكررة وتسجيل حالة التسليم.
تقرير إشغال الكراسي
أوصي بتشغيل مهمة مجدولة ليلاً، بعد نهاية دوام الفروع:
تجمع دقائق الحجز الفعلية لكل كرسي وفرع.
تستبعد الحجوزات الملغاة والمواعيد التي لا تدخل في تعريف الإشغال المتفق عليه.
تقارن دقائق الحجز بدقائق توافر الكراسي، مع مراعاة الورديات والإغلاق والصيانة.
تحفظ النتائج في جدول ملخص يومي، مثل DailyBranchOccupancy.
ترسل تقريراً للإدارة صباحاً، مع تسجيل نجاح التوليد والإرسال.
صيغة الإشغال الأساسية:
الإشغال=
دقائق الكراسي المتاحة
دقائق الكراسي المحجوزة
	
×100
يجب أن تتفق الإدارة على ما إذا كانت تريد إشغالاً حسب المواعيد المحجوزة أم حسب الحضور الفعلي؛ فهما مقياسان مختلفان. ويمكن إصدار الاثنين لتوضيح أثر عدم الحضور والإلغاء.
6\. خطة التنفيذ المناسبة للفريق
أقترح خطة من 10–12 أسبوعاً لإطلاق نسخة أولية قابلة للاستخدام، بشرط أن تكون التكاملات مع مزود الرسائل والبنية السحابية اعتيادية، وأن تتوفر متطلبات العمل مبكراً. لا تشمل المدة ضمان اجتياز أي اعتماد تنظيمي غير معروف مسبقاً.
1–2
أسبوعان
المرحلة الأولى: تثبيت الأساس
باك إند 1 + باك إند 2
تحديد قواعد الحجز والإلغاء والنقل.
تصميم قاعدة البيانات والقيود والفهارس.
بناء اختبارات التزامن ومخطط الصلاحيات.
حسم سياسات الخصوصية والاحتفاظ بالبيانات.
3–5
3 أسابيع
المرحلة الثانية: الحجز المركزي
باك إند 1 للحجز، وباك إند 2 للبيانات والتكاملات؛ الويب والموبايل بالتوازي
المرضى والموظفون والفروع والكراسي والورديات.
البحث عن المواعيد المتاحة وإنشاء الحجز ونقله وإلغاؤه.
واجهات الاستقبال وتطبيق المريض.
تسجيل التدقيق ومنع تكرار الطلبات.
6–7
أسبوعان
المرحلة الثالثة: العمل دون اتصال
مطور الموبايل والويب مع أحد مطوري الباك إند
التخزين المحلي المشفر وجدول اليوم.
تسجيل الحضور وطابور المزامنة.
معالجة التكرار وتعارض تغييرات الحالة.
اختبار انقطاع الشبكة وعودتها مراراً.
8–9
أسبوعان
المرحلة الرابعة: التشغيل الخلفي
باك إند 2 يقود التكاملات، وبقية الفريق للاختبار
SMS والتذكيرات وإعادة المحاولة.
التقارير الليلية والإرسال الصباحي.
النسخ الاحتياطي والتنبيهات والسجلات التشغيلية.
10–12
3 أسابيع
المرحلة الخامسة: تجربة وإطلاق تدريجي
تجربة فرع متصل وآخر ضعيف الاتصال.
اختبار الضغط والنسخ الاحتياطي والاستعادة.
تدريب موظفي الاستقبال.
إطلاق تدريجي للفروع الاثني عشر، مع مراقبة الأخطاء والتراجع عند الحاجة.
هذه خطة مستهدفة وليست وعداً زمنياً. إذا كانت تطبيقات الويب والموبايل ستُبنى من الصفر، أو كانت متطلبات الخصوصية والتكاملات غير محسومة، فقد تحتاج إلى مدة إضافية.
معيار قبول أساسي قبل الإطلاق: إجراء اختبار يولّد طلبات متزامنة على الكرسي والفترة نفسيهما من التطبيق والاستقبال، ثم إثبات أن قاعدة البيانات لم تسمح إلا بحجز فعّال واحد. ويجب اختبار نقل الموعد والإلغاء وتكرار الطلبات وانقطاع الشبكة أيضاً.
7\. الميزانية: هل تكفي 800 دولار شهرياً؟
نعم، يمكن أن تكون كافية لنسخة أولية بهذا الحجم إذا اخترتم خدمات مُدارة اقتصادية، وحددتم الاحتفاظ بالبيانات، وتجنبتم البنية الموزعة المكلفة. لكنها ليست ميزانية مضمونة؛ فسعر الرسائل، وتوافر الخدمة، والنسخ الاحتياطي، ومتطلبات التعافي من الكوارث قد تغير الحساب جذرياً.
الأرقام التالية تقديرات تخطيطية شهرية بالدولار، وليست عروض أسعار من مزود سحابي بعينه.
البند
	
ميزانية أولية مقترحة
استضافة API والعامل الخلفي
	
100–160 دولاراً
PostgreSQL مُدار
	
150–220 دولاراً
تخزين النسخ الاحتياطية
	
25–50 دولاراً
السجلات والمراقبة والتنبيهات
	
30–60 دولاراً
الشبكة والتخزين والخدمات المساندة
	
30–60 دولاراً
هامش توسع وأعطال
	
100 دولار
المجموع التقريبي
	
435–650 دولاراً
يتبقى نحو 150–365 دولاراً من الميزانية بحسب التكلفة الفعلية. لا يتضمن الجدول بالضرورة رسوم الرسائل القصيرة، أو الضرائب، أو تراخيص البرامج، أو رسوم بوابة الرسائل، أو تكاليف فريق التطوير.
يجب الحصول على عرض سعر من مزود SMS قبل اعتماد الميزانية النهائية، لأن رسوم الرسائل قد تصبح أكبر بند متغير.
ماذا يحدث إذا تضاعف الحمل؟
الحمل المتوقع هو 40 ألف حجز شهرياً، مع ذروة 120 حجزاً في الساعة. إذا تضاعف العدد والذروة، فسيصبح التقدير 80 ألف حجز شهرياً و240 حجزاً في الساعة، وهو حمل لا يفرض وحده استخدام Microservices.
لكن الحجز ليس كل حركة النظام؛ فتحقق التوافر وتحديث الجداول وطلبات الاستقبال والتذكيرات قد تولّد طلبات أكثر بكثير من عدد الحجوزات النهائية.
ترتيب نقاط الضغط المتوقعة
1\. قاعدة البيانات
راقبها أولاً
قد تحتاج إلى زيادة موارد المعالج والذاكرة والاتصالات، خصوصاً مع استعلامات التوافر والتقارير. لا تضاعف عدد الاتصالات بلا حدود؛ استخدم تجميع الاتصالات والفهارس المناسبة.
2\. خادم التطبيق والعامل الخلفي
يمكن زيادة موارد الخادم أو عدد النسخ عند الحاجة. أبقِ التطبيق عديم الحالة قدر الإمكان، وخزّن حالة المهام في قاعدة البيانات.
3\. الرسائل والتقارير
تضاعف الحجوزات قد يضاعف طلبات SMS والتذكيرات. كما قد تتنافس التقارير الليلية مع عمليات الحجز على موارد قاعدة البيانات؛ لذا شغّل التقارير خارج أوقات الذروة، وحسّن الاستعلامات قبل إضافة مستودع بيانات منفصل.
كيف نقرر أن الوقت قد حان لزيادة الإنفاق؟
راقب أربعة مؤشرات من البداية:
زمن استجابة الحجز عند المئين 95، مع هدف مبدئي مثل أقل من 500 مللي ثانية لعمليات API المعتادة، باستثناء الشبكة الخارجية.
استهلاك CPU والذاكرة في قاعدة البيانات.
انتظار الاتصالات وأقفال المعاملات وأخطاء التزامن.
طول طابور SMS وزمن تأخر المزامنة والتقارير.
لا تنتظر بلوغ 800 دولار. إذا اقترب الإنفاق المتوقع من 650–700 دولار، أو ظهرت مؤشرات ضغط مستمرة، ابدأ اختبار التوسعة قبل تجاوز الحد.
أول تجاوز محتمل للميزانية غالباً سيكون ترقية قاعدة البيانات أو رفع مستوى التوافر والنسخ الاحتياطي، ثم رسوم SMS بحسب حجم الإرسال. أما عند تضاعف الحمل وحده، فلا يمكن الجزم بأن الميزانية ستُتجاوز؛ فقد تستوعب البنية الأولية الزيادة إذا كانت الاستعلامات بسيطة والاتصالات مضبوطة.
8\. القرارات التي أحسمها قبل بدء التطوير
اعتماد PostgreSQL كمصدر وحيد للحقيقة في الحجز.
فرض منع التداخل على مستوى قاعدة البيانات، لا التطبيق فقط.
عدم تأكيد الحجوزات الجديدة أثناء انقطاع الفرع عن المركز، مع استمرار عرض الجدول وتسجيل الحضور محلياً.
استخدام Outbox ومهام خلفية للتذكيرات والتقارير، بدلاً من تنفيذها داخل طلبات المستخدم.
إطلاق النظام بوحدات مترابطة داخل تطبيق واحد، ثم فصل أي مكوّن لاحقاً بناءً على قياسات فعلية.
اعتماد اختبار الضغط، واختبار استعادة النسخ الاحتياطية، واختبار انقطاع الشبكة كمعايير إطلاق إلزامية.
الخلاصة: أبسط تصميم يحقق الاتساق المركزي مع وضع Offline محدود هو الأنسب لهذا الفريق وهذه الميزانية. لا تضحي بمنع التعارض لتقديم حجز غير متصل يبدو ناجحاً للمستخدم؛ قدّم بديلاً تشغيلياً واضحاً للاستقبال، وأبقِ تأكيد الموعد مرتبطاً بضمان قاعدة البيانات.
دDeepSeek
مراجعة تصميم نظام حجز عيادات الأسنان
1\. نظرة عامة على التصميم
أقترح Monolith معياري (Modular Monolith) على خدمات قليلة، لأن الفريق 4 مطورين بخبرة متوسطة ولا خبرة في الأنظمة الموزعة. تقسيم مبكر إلى Microservices سيكون تكلفة تشغيلية وبشرية أعلى من الميزانية.
text
\[تطبيق الموبايل] ──┐
\[بوابة الويب] ──┼──> API Gateway (REST)
\[استقبال الفرع] ──┘ │
  ▼
  ┌──────────────────────────────────────┐
  │ Modular Monolith (NestJS) │
  │ ┌──────────┐ ┌──────────┐ ┌───────┐ │
  │ │ Booking │ │ Schedule │ │Patients│ │
  │ └──────────┘ └──────────┘ └───────┘ │
  │ ┌──────────┐ ┌──────────┐ ┌───────┐ │
  │ │Notifica- │ │ Reports │ │ Auth │ │
  │ │ tions │ │ │ │ │ │
  │ └──────────┘ └──────────┘ └───────┘ │
  └──────────────────────────────────────┘
  │ │ │
  Postgres Redis Outbox/Queue
  (Primary + (cache + (BullMQ على
  Read Rep) locks) Redis)
2\. المكونات (Modules / Services)
خدمات التطبيق (API)
الوحدة المسؤولية
Auth \& Tenancy مصادقة الموظفين/المرضى، RBAC (استقبال، طبيب، مدير فرع، إدارة)
Patients ملف المريض، التاريخ الطبي المختصر، الربط بين الفروع
Catalog أنواع الإجراءات، المدة (30/60 د)، الكراسي، الورديات
Scheduling توليد Slots، جداول الأطباء والكراسي، الإجازات
Booking إنشاء/تعديل/إلغاء الحجز، منع التعارض (الحاسم)
Check-in تسجيل الحضور، حالات الموعد (Scheduled/Arrived/InProgress/Done/NoShow)
Notifications جدولة SMS (24h، 2h)، إعادة المحاولة، قوالب
Reports تقرير الإشغال الليلي، إرسال صباحي
Sync نقطة نهاية خاصة بالفروع ضعيفة الاتصال (Batch push/pull)
مكونات البنية التحتية
PostgreSQL — قاعدة البيانات الأساسية (Managed: RDS / Cloud SQL).
Redis — قفل موزع + Cache للـ Slots + Queue.
BullMQ — مهام الإشعارات والتقارير (مؤجلة، retries).
SMS Provider Adapter — واجهة مجردة فوق المزود الواحد (يسمح بالتبديل لاحقاً).
Object Storage — نسخ احتياطية + تقارير PDF.
تطبيقات العميل
موبايل (Flutter/React Native): حجز المرضى.
ويب للاستقبال: يعمل Offline-first (IndexedDB + Service Worker + طابور مزامنة).
لوحة إدارة: تقارير وإدارة الفروع.
3\. نموذج البيانات الموجز
text
Clinic(id, name, city, timezone, has\_stable\_internet)
Chair(id, clinic\_id, label, active)
Provider(id, clinic\_id, name, specialty) -- الطبيب
Shift(id, clinic\_id, name, start\_time, end\_time) -- ورديتان
ProcedureType(id, name, duration\_min, requires\_chair)
Patient(id, national\_id, name, phone, created\_at)
Slot(id, clinic\_id, chair\_id, provider\_id,
  start\_ts, end\_ts, status) -- status: OPEN|HELD|BOOKED|BLOCKED
  UNIQUE(chair\_id, start\_ts) -- الضمانة النهائية
Appointment(id, slot\_id UNIQUE, patient\_id, procedure\_type\_id,
  status, source, -- app|reception|phone
  created\_by, idempotency\_key UNIQUE,
  version INT, -- Optimistic Lock
  updated\_at)
CheckIn(id, appointment\_id, checked\_in\_at, by\_user\_id)
Notification(id, appointment\_id, channel, template,
  scheduled\_at, sent\_at, status, attempt\_count,
  provider\_message\_id)
OutboxEvent(id, aggregate\_id, type, payload, created\_at, published\_at)
SyncBatch(id, clinic\_id, device\_id, payload\_hash, received\_at)
نقاط مهمة:
UNIQUE(chair\_id, start\_ts) هو خط الدفاع الأخير ضد التعارض.
idempotency\_key على الحجز يمنع التكرار من تطبيق الموبايل عند إعادة الإرسال.
version للـ Optimistic Locking عند التعديل.
4\. آلية منع التعارض — مقارنة البدائل
البديل A: قيد UNIQUE في قاعدة البيانات فقط
✅ بسيط جداً، لا يحتاج Redis، مضمون 100% على مستوى DB.
❌ لا يعمل offline في الفروع النائية.
❌ يعطي خطأ DB قبيح بدل رسالة واضحة "الكرسي محجوز".
❌ لا يحمي من نافذة السباق قبل الكتابة (المستخدم يرى Slot متاحاً ثم يفشل).
البديل B: قفل موزع (Redis Redlock) + DB
✅ يمنع السباق قبل الكتابة، تجربة مستخدم أفضل.
❌ يحتاج Redis عالي التوفر (+تكلفة).
❌ Redlock معروف بهشاشته في بعض السيناريوهات (فقدان قفل عند Network Partition).
❌ لا يحل مشكلة الفروع offline.
البديل C: Optimistic Locking (version) + UNIQUE
✅ لا يحتاج بنية إضافية.
✅ يعمل مع قيود DB كطبقة أخيرة.
❌ يحتاج Retry logic في التطبيق.
البديل D (المختار): Slot-based Reservation + Optimistic + UNIQUE + Idempotency + Redis Lock اختياري
الطبقات الدفاعية:
Client: idempotency\_key مع كل طلب (UUID يولّد على الجهاز).
API: Redis SET NX PX على lock:slot:{slot\_id} لمدة 3 ثوانٍ (اختياري، فقط لتقليل الضغط على DB).
DB Transaction:
sql
BEGIN;
SELECT status FROM slot WHERE id = ? FOR UPDATE;
\-- إن كان BOOKED أو HELD → rollback
UPDATE slot SET status='BOOKED', version=version+1
  WHERE id=? AND status='OPEN' AND version=?;
\-- إذا 0 صفوف → تعارض
INSERT INTO appointment(...);
INSERT INTO outbox\_event(...); -- لإشعارات لاحقاً
COMMIT;
UNIQUE(chair\_id, start\_ts): شبكة الأمان الأخيرة — لو فشلت كل الطبقات، DB يرفض.
تحويل خطأ UNIQUE إلى رسالة عربية واضحة: "هذا الموعد حُجز للتو، اختر فترة أخرى".
لماذا هذا الخيار؟
لا يضيف بنية تحتية جديدة على الفريق.
يضمن الصحة الكاملة (Correctness) عبر DB.
يعطي تجربة مستخدم مقبولة.
يعمل بنفس المنطق في الفروع المتصلة وغير المتصلة (انظر القسم 5).
5\. استراتيجية الفروع ضعيفة الاتصال (3 فروع)
البديل 1: Terminal Server / VPN دائم
مرفوض: الإنترنت غير مستقر أصلاً، VPN سيسقط باستمرار.
البديل 2: قاعدة بيانات محلية كاملة (Replica) لكل فرع
مكلف، معقد، ويحتاج حل تعارض على مستوى DB. مرفوض لفريق متوسط.
البديل 3 (المختار): Offline-First Web App + Local Queue + Server Reconciliation
آلية العمل:
عند بداية اليوم (إنترنت متاح): الاستقبال يسحب "حزمة اليوم" — كل Slots والمواعيد الخاصة بالفرع ليومه + اليوم التالي → تخزن في IndexedDB.
أثناء الانقطاع:
الاستقبال يرى الجدول كاملاً من IndexedDB.
عند تسجيل الحضور أو حجز موعد جديد → يُنشئ عملية محلية (Local Operation) بحالة PENDING مع:
client\_op\_id (UUID).
slot\_id + expected\_version (النسخة التي رآها).
clinic\_id, device\_id, timestamp.
تُخزَّن في طابور محلي في IndexedDB.
عند عودة الشبكة:
الـ Service Worker يدفع الطابور إلى POST /sync/batch.
السيرفر يعالج العمليات بالترتيب:
Idempotency: تجاهل client\_op\_id مكرر.
Conflict Detection: قارن expected\_version بالنسخة الحالية للـ Slot.
✅ متطابق → commit.
⚠️ مختلف + لا تعارض فعلي → commit مع تسجيل Conflict سجلّي.
❌ تعارض فعلي (Slot محجوز من فرع/مريض آخر) → أعد العملية كـ REJECTED مع سبب واضح.
يستقبل الجهاز قائمة accepted / rejected / conflicts ويُحدّث الـ UI.
قواعد خاصة للفروع النائية:
حجز نفس الفرع فقط من الجهاز المحلي (لا يوزع مواعيد بين المدن أثناء offline).
لتقليل التعارض، الفرع النائي يملك أولوية كتابة على كراسيه خلال نافذة زمنية قصيرة (Lease قصير يُسحب عند المزامنة).
إذا نادراً ما يُحجز نفس الكرسي من التطبيق المركزي لنفس الفرع → قاعدة: الحجز المركزي لا يقبل Slots اليوم الحالي والغد للفروع الثلاثة إذا كان آخر Heartbeat للفرع خلال 15 دقيقة (حماية من التضارب).
نتيجة: ضمان Best-effort offline مع تعريف واضح لأولوية الكتابة، ولا نضطر لحل تعارضات معقدة CRDT.
6\. الإشعارات
Outbox Pattern: كل حجز يكتب outbox\_event في نفس الـ Transaction → Worker يقرأ ويُنشئ Notification مجدولة.
BullMQ Delayed Jobs لـ T-24h و T-2h.
Retries: Exponential backoff (1m, 5m, 30m) — 3 محاولات.
Circuit Breaker على مزود SMS: لو فشل > 50% خلال دقيقة، أوقف الإرسال مؤقتاً وأعد المحاولة بعد 5 دقائق (يمنع استنزاف الرصيد).
Webhook من المزود لتأكيد التسليم.
تخزين حالة كل إشعار لأغراض التدقيق.
7\. التقارير
Cron يومي 02:00 (توقيت الفرع) → Job يجمع إشغال كل كرسي:
text
utilization = occupied\_minutes / (shift\_minutes \* chair\_count)
تُخزَّن في جدول DailyUtilization مسبقاً (Materialized) للقراءة السريعة.
07:00 صباحاً: إيميل/PDF للإدارة + Dashboard.
لو الفروع كثيرة لاحقاً: ننقل التقارير لـ Read Replica.
8\. خطة التنفيذ (4 مطورين، خبرة متوسطة)
المرحلة المدة المخرج المسؤول
0\. تأسيس أسبوعان Repo، CI/CD، Postgres، Redis، Auth، Environments Backend 1 + Web
1\. Core Booking 4 أسابيع Catalog, Slots, Booking + منع التعارض + UNIQUE + Tests حمل Backend 1+2
2\. Reception Web 3 أسابيع ويب الاستقبال Online-first (بدون Offline بعد) Web + Backend
3\. Mobile MVP 4 أسابيع تسجيل، حجز، إلغاء، عرض المواعيد Mobile + Backend
4\. Notifications أسبوعان Outbox + BullMQ + SMS Adapter + Retries Backend 2
5\. Offline Mode 4 أسابيع IndexedDB + Sync Batch + Conflict Handling (يبدأ فقط بعد استقرار Core) Web + Backend 1
6\. Reports أسبوعان Daily Utilization + Email Backend 2
7\. Hardening 3 أسابيع Load Test 120 booking/h، Monitoring، Backups، DR الكل
الإجمالي: \~24 أسبوعاً (6 أشهر) — قابل للضغط لـ 5 أشهر بإسقاط Offline للمرحلة 2.
مبادئ:
Offline آخر شيء (لا تبنيه قبل أن يعمل Online بثبات).
كل مرحلة تُطلق خلف Feature Flag.
اختبار حمل مبكر من المرحلة 1 (k6 أو Locust).
9\. أين ستنفجر الميزانية (800$/شهر) أولاً؟
الترتيب متوقع عند مضاعفة الحمل (80K حجز، 240/h ذروة):
المكوّن التكلفة الحالية التقديرية عند المضاعفة ملاحظة
RDS/Cloud SQL \~150–200$ \~400–600$ ⚠️ أول ما ينفجر — يحتاج Upgrade Instance أو Read Replica
Redis (Managed) \~50–80$ \~150$ Memory + HA
SMS \~200–400$ \~800$+ 💥 2 SMS × 40K = 80K SMS/شهر — أكبر بند متغير
Compute (App) \~80–120$ \~200–250$ Horizontal scaling
Object Storage/Backups \~20$ \~50$ نمو سجلات + نسخ
Monitoring/Logs \~50$ \~150$ Datadog أو ما شابه مكلف
أول نقطة انفجار فعلياً:
SMS — بند متغير خطي مع الحمل. عند 80K حجز → \~80K رسالة/شهر → قد يلتهم الميزانية وحده. الحل: تفضيل Push Notifications في التطبيق، وإرسال SMS فقط عند فشل Push أو لمن لا يملك التطبيق. كذلك تجميع الرسائل.
قاعدة البيانات — ثاني نقطة. الحل: Partitioning على appointment حسب الشهر، وأرشفة السجلات > 12 شهراً، واستخدام Read Replica للتقارير.
Redis — ثالثاً عند تحويله HA. يمكن تأجيل HA إذا قبلنا بـ Downtime قصير.
توصيات للسيطرة على الميزانية:
استخدام Managed Postgres صغير + Read Replica بدل Upgrade مبكر.
تخزين Cold Data في S3 (Parquet) بعد 12 شهراً.
حدود صارمة على عدد SMS (Fallback إلى Push/Email).
Reserved Instances / Savings Plans بعد 6 أشهر من الاستقرار.
10\. مخاطر يجب مراقبتها
المخاطرة التخفيف
تعارض غير متوقع في الفروع النائية Lease + رفض حجز مركزي للفروع غير المستقرة > 15 دقيقة
فشل مزود SMS Circuit Breaker + Adapter لتبديل المزود خلال ساعات
نمو الفريق لاحقاً Modular Monolith يسهّل فصل Booking إلى خدمة مستقلة
تسرّب بيانات مرضى تشفير عند الراحة، Audit Log، RBAC صارم، عدم تخزين تفاصيل طبية حساسة في التطبيق
بطء التقارير Materialized + Read Replica + Table Partitioning
الخلاصة
Monolith معياري + Postgres + Redis + BullMQ — مناسب للفريق والميزانية.
منع التعارض: 5 طبقات (Idempotency → Redis Lock → Transaction FOR UPDATE → Optimistic Version → UNIQUE constraint).
Offline: IndexedDB + Batch Sync + Lease للفروع النائية، مع قبول Best-effort ورفض واضح للتعارض.
الميزانية: تنفجر أولاً في SMS ثم Postgres — خطط للـ Push-first و Partitioning مبكراً.
التنفيذ: 6 أشهر، Offline في المرحلة الأخيرة، Feature Flags، اختبار حمل مبكر.
هل تريد أن أعمّق في جزء معين — مثل مخطط الـ Sync Protocol بالتفصيل، أو سكربتات اختبار الحمل، أو Partitioning استراتيجية appointment؟
هذا الرد تم إنشاؤه بواسطة AI، وهو للإشارة فقط.
بGemini
تออกแบบ هذا النظام يتطلب توازناً دقيقاً بين البساطة العملية (تناسب خبرة الفريق والميزانية) والموثوقية العالية (تمنع التعارض تماماً وتتحمل الانقطاع).إليك مراجعة معمارية شاملة ومُفصلة للتصميم الأولي قبل البدء في التنفيذ.1. المعمارية العامة ومكونات النظام (System Architecture)لتقليل التعقيد الاستثماري وضمان استقرار النظام مع فريق متوسط الخبرة، نعتمد على نمط Modular Monolith (تطبيق مركزي موحد مقسم مجالياً) يسهل تنفيذه وصيانته، بدلاً من خدمات مصغرة (Microservices) موزعة قد ترهق الفريق. +-----------------------------------+
  | Clients (Apps/Web) |
  +-----------------+-----------------+
  |
  v
  +--------------+--------------+
  | Cloud Flare / ALB |
  +--------------+--------------+
  |
  v
  +-----------------------+-----------------------+
  | Core Monolith API Application |
  | |
  | \[ Booking Module ] \[ Notification Module ] |
  | \[ Patient Module ] \[ Reporting Module ] |
  +-------+--------------------+------------------+
  | |
  +-----------+---+ +------+--------+
  | PostgreSQL | | Redis Cache |
  | (Primary DB) | | (Locks/Cache) |
  +---------------+ +---------------+
المكونات الرئيسية:API Gateway / Load Balancer:إدارة المرور والتوجيه وحماية النظام (Rate Limiting).Core Monolith Application (Node.js/NestJS أو Go/C#):Booking Module: إدارة جداول الكراسي، إنشاء المواعيد، والتحقق من التعارض.Branch Sync Module: استقبل مزامنة الفروع ودفع جدول اليوم للعيادات.Notification Module: إدارة الطابور لإرسال رسائل SMS وإعادة المحاولة.Reporting Module: تجميع البيانات وبناء التقرير اليومي.PostgreSQL Database:قاعدة البيانات الرئيسية والوحيدة المصرح لها بإنشاء الحجوزات المؤكدة.Redis Cache \& Queue (Managed):كاش سريع لجدول المواعيد المتاحة، وقائمة انتظار (Queue) معالجة الرسائل والإشعارات.Branch Offline Agent (تطبيق الاستقبال بالفرع):تطبيق Electron/Web مخصص للعمل أوفلاين مع قاعدة بيانات محلية مصغرة (SQLite/IndexedDB).2. نموذج البيانات الموجز (Core Data Model)تم تقليل النموذج للكيانات الجوهرية للحد من التعقيد:SQL-- 1. الفروع والكراسي
TABLE branches (
  id UUID PRIMARY KEY,
  name VARCHAR(100),
  timezone VARCHAR(50) DEFAULT 'Asia/Riyadh'
);
TABLE chairs (
  id UUID PRIMARY KEY,
  branch\_id UUID REFERENCES branches(id),
  name VARCHAR(50), -- مثل: كرسي 1، كرسي 2
  is\_active BOOLEAN DEFAULT TRUE
);
\-- 2. المواعيد والحجوزات (الكيان الجوهري)
TABLE appointments (
  id UUID PRIMARY KEY,
  branch\_id UUID REFERENCES branches(id),
  chair\_id UUID REFERENCES chairs(id),
  patient\_id UUID REFERENCES patients(id),
  start\_time TIMESTAMP WITH TIME ZONE NOT NULL,
  end\_time TIMESTAMP WITH TIME ZONE NOT NULL,
  status VARCHAR(20) CHECK (status IN ('booked', 'checked\_in', 'completed', 'cancelled')),
  created\_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated\_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  source VARCHAR(20) CHECK (source IN ('app', 'reception', 'offline\_sync'))
);
\-- مؤشر مخصص (Exclusion Constraint) للوقاية المباشرة على مستوى قاعدة البيانات من التعارض
\-- يمنع إضافة أي تداخل زمني لنفس الكرسي إذا كان الحجز غير ملغى
CREATE EXTENSION IF NOT EXISTS btree\_gist;
ALTER TABLE appointments ADD CONSTRAINT no\_chair\_time\_overlap
EXCLUDE USING gist (
  chair\_id WITH =,
  tstzrange(start\_time, end\_time, '\[)') WITH \&\&
) WHERE (status != 'cancelled');
\-- 3. سجل الإشعارات
TABLE notification\_logs (
  id UUID PRIMARY KEY,
  appointment\_id UUID REFERENCES appointments(id),
  type VARCHAR(20), -- 'REMINDER\_24H', 'REMINDER\_2H'
  status VARCHAR(20), -- 'PENDING', 'SENT', 'FAILED'
  retry\_count INT DEFAULT 0,
  scheduled\_at TIMESTAMP WITH TIME ZONE,
  sent\_at TIMESTAMP WITH TIME ZONE
);
3\. آلية منع تعارض الحجز + التعامل مع الفروع النائيةأ. منع التعارض الزمني في اللحظة نفسها (Concurrent Booking)لضمان عدم حجز نفس الكرسي في نفس اللحظة عبر التطبيق والاستقبال:البديل المختار: Database Pessimistic Locking + PostgreSQL Exclusion Constraint.سبب الاختيار: تجنب التعقيد التشغيلي لأدوات مثل Redis Distributed Lock (Redlock)، واقتطاع الأمر مباشرة في قاعدة البيانات المركزية لضمان ذرية المعاملة (ACID Consistency) بأسهل شكل ممكن للفريق.خطوات عملية الحجز المباشرة (Online Booking):يبدأ الطلب داخل المعاملة (BEGIN TRANSACTION).تنفيذ الاستعلام مع خيار المنع أو الاعتماد المباشر على EXCLUDE Constraint.في حال محاولة حجزين لنفس الكرسي بنفس الوقت، سيفشل أحدهما فوراً بقاعدة المعاملة ويُرجع خطأ للمستخدم: "الموعد تم حجزه للتو، يرجى اختيار موعد آخر".ب. مقارنة بدائل التعامل مع الفروع النائية (3 فروع بدون إنترنت مستقر)البديل المرشحالإيجابياتالسلبياتالقرار1. Distributed Database / Multi-Master Replicationمزامنة فورية ومستمرة تلقائياًتعقيد شديد جداً، صعوبة حل التعارضات، لا تناسب فريق متوسط الخبرةمرفوض2. Offline-First (الفرع يحجز محلّياً ويخزن ثم يزامن)الاستقبال يمارس الحجز بحرية حتى أوفلاينيستحيل منع التعارض مع التطبيق؛ سيرد حجز من التطبيق أونلاين لنفس الكرسي أوفلاينمرفوض (يتعارض مع شرط القيود الصارم)3. Read-Only Offline with Sync Queue (المعتمد)استقرار موثوق، منع تعارض تام، سهولة تنفيذالاستقبال لا يمكنه إنشاء حجز جديد عند الانقطاع الكامل للشبكةمُعتمد (مُوضح أدناه)آلية العمل المعتمدة للعيادات النائية:وضع Read-Only المحلّي (تسجيل الحضور وجدول اليوم):عند الفجر، يزامن تطبيق الاستقبال جدول اليوم وتفاصيل الحجوزات كاملة إلى قاعدة بيانات محلية (SQLite).في حال انقطاع الإنترنت: يستطيع الاستقبال استعراض الجدول، وتسجيل حضور المرضى (Check-in)، وتعديل حالة الموعد، وتُسجّل هذه التغييرات في طابور محلي (Local Action Queue).عند عودة الإنترنت: تُرسل الأفعال إلى السيرفر الرئيسي للمزامنة.صلاحية إنشاء حجز جديد أثناء انقطاع الإنترنت:الشرط الصارم: منع التعارض التام.الحل: إذا انقطع الإنترنت نهائياً عن موظف الاستقبال، يُمنع من إنشاء حجز جديد في النظام (يمكنه تسجيله ورقياً مؤقتاً أو الاتصال هاتفياً بالمركز الرئيسي الحاضر أونلاين)، أو يتم التحويل التلقائي عبر شريحة 4G احتياطية (Failover Modem) مخصصة لعمليات الحجز الخفيفة فقط.4. خطة مراحل التنفيذ (Roadmap)خطة مقسمة إلى 4 مراحل تناسب طاقة الفريق (4 مطورين: 2 Back-end, 1 Web, 1 Mobile) على مدار 12 إلى 14 أسبوعاً:\[ المرحلة 1: النواة وقاعدة البيانات ] ----> (3 أسابيع)
  |
  v
\[ المرحلة 2: محرك الحجز والتطبيقات ] ----> (4 أسابيع)
  |
  v
\[ المرحلة 3: أوفلاين العيادات والإشعارات] ----> (3 أسابيع)
  |
  v
\[ المرحلة 4: التقارير والاختبارات الشاملة] ----> (3 أسابيع)
المرحلة 1: البنية التحتية ونواة البيانات (3 أسابيع):إعداد PostgreSQL وRedis والقيود البرمجية للوقاية من التعارض.بناء APIs إدارة الفروع، الكراسي، والخدمات (Backend).المرحلة 2: محرك الحجز الواجهاتي (4 أسابيع):تطوير منطق الحجز وحل حالات التزامن (Backend).بناء شاشات التطبيق للمرضى (Mobile) ولوحة حجز الاستقبال (Web).المرحلة 3: المزامنة أوفلاين ونظام الإشعارات (3 أسابيع):إعداد نظام الحفظ المحلي (SQLite) وطابور المزامنة لتطبيق الاستقبال.ربط مزود الـ SMS وبناء طابور الإشعارات والتذكير (24h / 2h) باستخدام Redis Queues (BullMQ).المرحلة 4: التقارير، الاختبار الضغطي، والإطلاق (3 أسابيع):بناء المهمة المجدولة (Cron Job) لبناء تقارير إشغال الكراسي ليلاً.إجراء اختبارات حمل لتأكيد تحمل الذروة (120 حجز/ساعة = حجزين بالدقيقة، وهو أداء يت تحمله بسهولة).إطلاق تجريبي (Pilot) في فرعين ثم التعميم.5. ميزانية السحاب وتوقع نقاط تجاوزها (Budget \& Scalability)أ. توزيع الميزانية الحالية ($800 شهرياً)مع أحمال 40 ألف حجز شهرياً، تكون الميزانية الفعلية أقل بكثير من $800 (في حدود $200 - $350 شهرياً):Managed PostgreSQL (e.g., AWS RDS PostgreSQL / DigitalOcean Managed DB):$80 - $120 شهرياً (Node مع 2-4 vCPU و8GB RAM كافية جداً).Application Servers (App \& API Container):$40 - $80 شهرياً (مثلاً خادمان ملحقان بنظام Load Balancer للتوافرية).Managed Redis:$30 - $50 شهرياً.إشعارات SMS (المزود الخارجي):40 ألف حجز × 2 رسالة = 80 ألف رسالة شهرياً (+ إخفاقات/إعادات).التكلفة التقريبية: $100 - $200 شهرياً (تعتمد على التعرفة المحلية للمزود).ب. أين تتجاوز الميزانية الـ $800 عند تضاعف الأحمال؟إذا تضاعفت الأحمال (مثلاً 5 إلى 10 أضعاف: 400 ألف حجز شهرياً)، فإن النقاط التالية هي أول ما سيتجاوز الميزانية بالترتيب:تكلفة رسائل الـ SMS (المُستهلِك الأول والريع للبودجيت):السبب: تكلفة الـ SMS خطية (Linear Cost). عند الوصول لـ 400 ألف حجز شهرياً، سيتطلب الأمر نحو 800 ألف إلى مليون رسالة.التكلفة المتوقعة: قد تتجاوز $1,500 - $2,500 شهرياً للرسائل وحدها.الحل الوقائي: تحويل التذكير الأولي ليصبح عبر Push Notifications أو WhatsApp Business API (أرخص بكثير) والاعتماد على الـ SMS فقط كبديل ثانوي.قاعدة البيانات (Database Scaling):السبب: زيادة القراءات/الكتابات وتعاظم حجم جداول السجلات المكتوبة بشكل مستمر.التكلفة المتوقعة: الترقية لنود أكبر مع High Availability (Multi-AZ) ستكلف $300 - $500 شهرياً.أدوات المراقبة وإدارة السجلات (Monitoring \& Logging):استخدام أدوات مثل Datadog أو NewRelic للـ APM والسجلات سيتجاوز $200 شهرياً مع المقياس العالي إذا لم يتم ضبط الحد الأدنى للسجلات (Log Retention limits).