ITSkillsCenter
الأمن السيبراني

تطبيق المريض الهاتف للصحة الرقمية: درس 2026

4 min de lecture

📍 المقالة الرئيسية للمجموعة: الصحة الرقمية CEDEAO 2026.

المريض الإفريقي في 2026 يريد تطبيقاً هاتفياً، ليس انتظار صف في الاستقبال. حجز موعد، وصول إلى الوصفات، نتائج المختبر، رسائل آمنة مع الطبيب، الدفع mobile money. تطبيق المريض = أداة وفاء قوية + تخفيض على الاستقبال = ROI واضح. هذا الدرس يفصل بناء PWA Astro + FHIR client + توثيق آمن، التكامل الكامل مع OpenMRS.

المتطلبات

OpenMRS مع FHIR module. نطاق منفصل: patient.votre-hopital.com. خدمة SMS (Twilio أو مزود محلي). معرفة Astro + React. المستوى المتوقع: متقدم. الوقت المقدر: 1-2 أسبوع للنسخة v1.

الخطوة 1 — هيكل التطبيق

PWA Astro للأداء (server-side rendering + hydration ذكي). Frontend: React components. Backend: Astro API routes تستفسر FHIR endpoints. Authentication: OAuth2 مع OpenMRS كـ Identity Provider. التطبيق قابل للتثبيت كـ PWA (إضافة إلى الشاشة الرئيسية).

npm create astro@latest patient-app
cd patient-app
npm install @astrojs/react react react-dom
npm install @smile-cdr/fhirts  # FHIR TypeScript SDK
npm install @astrojs/pwa

الخطوة 2 — Authentication OAuth2

المريض يسجل دخول بـ NIN + كلمة سر أو رقم هاتف + OTP. OpenMRS OAuth2 module يوفر authorization_code flow.

// src/lib/auth.ts
const AUTH_URL = 'https://emr.votre-hopital.com/openmrs/oauth/authorize';
const TOKEN_URL = 'https://emr.votre-hopital.com/openmrs/oauth/token';

export async function login(phone: string, otp: string) {
  // الخطوة 1: تحقق OTP
  const otpRes = await fetch('/api/verify-otp', {
    method: 'POST',
    body: JSON.stringify({ phone, otp })
  });
  const { authCode } = await otpRes.json();
  
  // الخطوة 2: تبادل code → access token
  const tokenRes = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: `grant_type=authorization_code&code=${authCode}&client_id=patient_app&client_secret=${SECRET}`
  });
  const { access_token, refresh_token } = await tokenRes.json();
  
  // تخزين آمن (HttpOnly cookie)
  document.cookie = `token=${access_token}; HttpOnly; Secure; SameSite=Strict`;
}

الخطوة 3 — صفحة المواعيد

عرض المواعيد القادمة (FHIR Appointment resource):

// src/pages/appointments.astro
---
const token = Astro.cookies.get('token').value;
const r = await fetch(`https://emr.votre-hopital.com/openmrs/ws/fhir2/R4/Appointment?patient=${userId}&status=booked`, {
  headers: { 'Authorization': `Bearer ${token}` }
});
const bundle = await r.json();
const appointments = bundle.entry?.map(e => e.resource) || [];
---

  

مواعيدي القادمة

{appointments.map(apt => (

{apt.serviceCategory?.[0]?.coding?.[0]?.display}

التاريخ: {new Date(apt.start).toLocaleString('ar-MA')}

الطبيب: {apt.participant?.find(p => p.actor?.reference?.startsWith('Practitioner'))?.actor?.display}

))} حجز موعد جديد

الخطوة 4 — حجز موعد جديد

تدفق الحجز: اختيار التخصص → اختيار الطبيب → اختيار التاريخ/الوقت → تأكيد. POST FHIR Appointment.

// src/pages/book.tsx
const handleBook = async () => {
  const appointment = {
    resourceType: 'Appointment',
    status: 'booked',
    serviceCategory: [{coding: [{code: 'cardiology', display: 'Cardiologie'}]}],
    start: selectedSlot.start,
    end: selectedSlot.end,
    participant: [
      {actor: {reference: `Patient/${userId}`}, status: 'accepted'},
      {actor: {reference: `Practitioner/${doctorId}`}, status: 'accepted'}
    ]
  };
  
  const r = await fetch('/api/appointments', {
    method: 'POST',
    headers: {'Content-Type': 'application/fhir+json'},
    body: JSON.stringify(appointment)
  });
  
  if (r.ok) {
    // SMS تأكيد
    await fetch('/api/send-sms', {
      method: 'POST',
      body: JSON.stringify({
        to: userPhone,
        message: `موعدك مؤكد في ${formatDate(selectedSlot.start)}`
      })
    });
    window.location = '/appointments';
  }
};

الخطوة 5 — الوصفات

المريض يرى وصفاته الحالية والقديمة:

// src/pages/prescriptions.astro
const r = await fetch(`/fhir/MedicationRequest?patient=${userId}`, {headers});
const prescriptions = await r.json();
---
{prescriptions.entry.map(p => {
  const med = p.resource;
  return (
    

{med.medicationCodeableConcept.coding[0].display}

الجرعة: {med.dosageInstruction[0].text}

المدة: {med.dispenseRequest.expectedSupplyDuration.value} {med.dispenseRequest.expectedSupplyDuration.unit}

الطبيب الواصف: {med.requester.display}

); })}

الخطوة 6 — نتائج المختبر

FHIR DiagnosticReport + Observations. عرض نتائج بيانية (charts.js).

// عرض هيموغلوبين على 6 أشهر
const observations = await fetch(`/fhir/Observation?patient=${userId}&code=718-7&_sort=-date`);
const data = observations.entry.map(o => ({
  date: o.resource.effectiveDateTime,
  value: o.resource.valueQuantity.value
}));

// Chart.js
new Chart(canvas, {
  type: 'line',
  data: {
    labels: data.map(d => d.date),
    datasets: [{label: 'الهيموغلوبين (g/dL)', data: data.map(d => d.value)}]
  }
});

الخطوة 7 — الرسائل الآمنة مع الطبيب

FHIR Communication resource. تشفير end-to-end optional عبر libsodium.

// إرسال رسالة
const message = {
  resourceType: 'Communication',
  status: 'in-progress',
  subject: {reference: `Patient/${userId}`},
  recipient: [{reference: `Practitioner/${doctorId}`}],
  payload: [{contentString: 'Bonjour Dr, j\'ai des effets secondaires...'}],
  sent: new Date().toISOString()
};

await fetch('/fhir/Communication', {
  method: 'POST',
  headers: {'Content-Type': 'application/fhir+json'},
  body: JSON.stringify(message)
});

الخطوة 8 — مدفوعات mobile money

دفع المواعيد، الاستشارات، الفواتير. Wave/Orange Money/CMI:

// تكامل Wave
const initPayment = async (amount, billId) => {
  const res = await fetch('/api/wave-checkout', {
    method: 'POST',
    body: JSON.stringify({
      amount,
      currency: 'XOF',
      reference: billId,
      success_url: '/payment-success',
      error_url: '/payment-error'
    })
  });
  const { wave_launch_url } = await res.json();
  window.location = wave_launch_url;
};

الخطوة 9 — PWA + Offline

Service Worker لـ caching + push notifications:

// astro.config.mjs
import pwa from '@astrojs/pwa';

export default defineConfig({
  integrations: [pwa({
    registerType: 'autoUpdate',
    workbox: {
      globPatterns: ['**/*.{js,css,html,png}'],
      runtimeCaching: [{
        urlPattern: /^https:\/\/emr\.votre-hopital\.com\/openmrs\/ws\/fhir2/,
        handler: 'NetworkFirst',
        options: {
          cacheName: 'fhir-api',
          expiration: {maxEntries: 50, maxAgeSeconds: 86400}
        }
      }]
    }
  })]
});

الخطوة 10 — تنبيهات SMS و push

تذكيرات للمواعيد (24h، 2h قبل). توفر النتائج. وصفات قابلة للتجديد.

// Cron يومي
const upcoming = await fetch('/fhir/Appointment?status=booked&date=ge2026-04-28&date=le2026-04-29');
for (const apt of upcoming.entry) {
  const patient = await getPatient(apt.subject.reference);
  await sendSMS(patient.telecom.find(t => t.system === 'phone').value,
    `تذكير: موعدك غداً في ${formatTime(apt.start)} مع الطبيب ${apt.participant[1].actor.display}`);
}

الأخطاء الشائعة

الخطأ السبب الحل
OAuth2 token expired 1h لكل token refresh tokens auto
FHIR queries بطيئة لا cache Service Worker NetworkFirst
Push notifications لا تعمل iOS iOS < 16.4 SMS fallback
الواجهة لا RTL HTML dir منسي dir= »rtl » لـ المحتوى العربي
وصفات PDF فارغة FHIR Bundle مالformed تحقق resource validation
SMS غير مرسلة Twilio rate limit queue + retry exponential

التكيف مع السياق

خمس توضيحات. اللغة العربية بـ RTL. dir= »rtl » في layout. SMS بدلاً من email. 70% من المرضى الإفريقيين لا يستخدمون email يومياً. SMS = القناة الأساسية. USSD fallback. للهواتف غير الذكية، USSD code (*123*1#) للمواعيد. تكامل عبر Twilio أو operator محلي. الوزن الخفيف للتطبيق. < 500 KB initial bundle. حاسم لـ 2G/3G في القرى. Cash on visit. mobile money يفضل لكن نقد عند الاستقبال يبقى الافتراضي للعديد. لا تجبر على mobile money.

دروس الإخوة

الأسئلة المتكررة

تطبيق هاتفي أصلي vs PWA؟ PWA كافٍ في 2026. App Store/Play Store اختياري. PWA يتجنب رسوم Apple 30% + توزيع بسيط.

تشفير end-to-end للرسائل؟ اختياري. libsodium في browser ممكن. لكن الطبيب يجب أن يصل إلى السجلات لذا E2E غالباً غير ضروري.

تكلفة Twilio SMS؟ 0.05 USD/SMS في السنغال. لـ 1,000 مريض × 4 SMS/شهر = 200 USD/شهر. مزود محلي (Africa’s Talking) أرخص.

تطبيق متعدد المستشفيات؟ نعم، اختيار المستشفى عند login. كل مستشفى = OAuth2 endpoint منفصل.

التكوين للمرضى الكبار؟ دعم WhatsApp إضافي مع chatbot Wave/OM.

للاستزادة

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité