في اجتماع نهاية الشهر يسأل المؤسس سؤالاً بسيطاً: كم سوف يدخل الشركة في الربع القادم؟ فيفتح مدير المبيعات الجدول ويقول إن المسار فيه 2.6 مليون ريال، ويخرج الجميع مطمئنين، ثم ينتهي الربع بربع هذا الرقم. والمشكلة هنا ليست في البائعين، وإنما في أن رقم 2.6 مليون يجمع صفقة وقع العميل على عقدها مع صفقة لم يرد صاحبها على البريد منذ شهر، وكأن الاثنتين متساويتان. وفي هذا المقال سوف نبني مسار البيع Sales Pipeline بطريقة تجعل هذا الرقم صادقاً: مراحل لها شروط خروج، واحتمالات محسوبة من صفقاتك السابقة، ثم توقع للإيرادات Forecast نحسبه بأنفسنا من ملف بيانات.
المواضيع في السلسلة:
- المبيعات B2B للتقنيين: من العميل المحتمل إلى العقد
- مسار البيع Sales Pipeline: المراحل والتوقعات Forecast (الموضوع الحالي)
- إدارة علاقات العملاء CRM: Salesforce وبدائله مفتوحة المصدر
- CRM على سيرفرك مع Twenty: مسار بيع كامل
دليلالمبيعات B2B للتقنيين: من العميل المحتمل إلى العقدكيف تبيع شركة تقنية لشركات أخرى: من يقرر عند العميل، وكيف تؤهل الصفقة، وما الوثائق والتسعير من أول اجتماع إلى الفاتورة.
دليلإدارة علاقات العملاء CRM: Salesforce وبدائله مفتوحة المصدرمقارنة أنظمة CRM التجارية ومفتوحة المصدر بالترخيص والصيانة والأسعار، مع اختبار فعلي لمسار البيع بالعربية في أربعة أنظمة.
دليلCRM على سيرفرك مع Twenty: مسار بيع كاملنشغل Twenty في Docker ونبني مسار بيع بأسماء عربية، ونستورد العملاء ونحسب توقع الإيرادات، ونرسل كل تغيير إلى نظام آخر عبر Webhook.وفي هذا المقال سوف نتناول:
- الفرق بين مسار البيع وقمع التسويق، ولماذا يحتاج كل منهما أرقامه.
- مراحل البيع للشركات وشرط الخروج من كل مرحلة.
- المقاييس الأربعة: نسبة الفوز Win Rate ومتوسط قيمة الصفقة وطول دورة البيع وسرعة البيع Sales Velocity، ومعها نسبة التغطية Pipeline Coverage.
- طريقتان للتوقع: الموزون Weighted وفئات التوقع Forecast Categories، ومتى تختلفان.
- تجربة عملية في Docker نحسب فيها كل ذلك من 60 صفقة مغلقة و8 صفقات مفتوحة، ثم نرى ماذا يحدث إذا تأخرت صفقة واحدة.
- قواعد نظافة المسار Pipeline Hygiene، والأخطاء الشائعة.
مسار البيع وقمع التسويق
قمع التسويق Marketing Funnel يقيس الناس: كم زائراً وصل، وكم منهم سجل، وكم أصبح عميلاً محتملاً Lead، وقد شرحناه في مقاله. أما مسار البيع فيقيس الصفقات: كل فرصة Opportunity لها قيمة بالريال وتاريخ إغلاق متوقع ومرحلة، ويملكها بائع واحد. والانتقال بينهما يحدث عندما يتحول العميل المحتمل إلى فرصة بعد التأهيل، أي بعد أن نعرف أن عنده مشكلة نحلها وأن هناك من يقرر ومن يدفع، كما شرحنا في مقال المبيعات B2B. والفرق العملي أن القمع يجيب عن سؤال «هل نجلب ما يكفي من الناس؟» والمسار يجيب عن سؤال «كم سوف نبيع ومتى؟».
المراحل وشرط الخروج Exit Criteria
المرحلة في مسار البيع ليست وصفاً لما فعله البائع، وإنما لما فعله العميل، والسبب أن البائع يستطيع أن يرسل عشرة عروض في يوم، ولكنه لا يستطيع أن يجعل العميل يقيمها. لذلك نعرف لكل مرحلة شرطاً يمكن التحقق منه، ولا تنتقل الصفقة إلا إذا تحقق، والصورة التالية تبين المراحل التي نستخدمها في هذه السلسلة:

- عميل مؤهل Qualified: عرفنا المشكلة، وعرفنا من يملك القرار، وهناك ميزانية أو موعد يجعل الشراء حقيقياً. والصفقة التي لا تعرف من يوقع عليها ليست فرصة بعد، وإنما عميل محتمل.
- اكتشاف الاحتياج Discovery: جلسنا مع من سوف يستخدم النظام ومع التقنيين عند العميل، وكتبنا المتطلبات، واتفقنا على المعايير التي سوف يقرر بها. وهذه المرحلة يملكها في الغالب مهندس ما قبل البيع Presales.
- عرض فني ومالي Proposal: أرسلنا العرض، وأكد العميل أنه في التقييم. وإذا كان العميل جهة تطلب عروضاً رسمية فهذه المرحلة هي الرد على طلب العروض RFP.
- تفاوض Negotiation: اختارنا العميل أو وضعنا في القائمة القصيرة، وبدأ النقاش في السعر وبنود العقد مع المشتريات والقانونية.
- فوز Won أو خسارة Lost: الفوز يعني عقداً موقعاً أو أمر شراء Purchase Order، لا وعداً شفهياً، والخسارة تكتب معها السبب، لأن أسباب الخسارة هي أرخص بحث سوق تملكه.
وقد يتساءل البعض: لماذا أربع مراحل فقط وأنظمة CRM تأتي بعشر؟ والإجابة أن كل مرحلة إضافية تحتاج شرطاً واضحاً، والمراحل التي لا يفرق بينها شرط يضع فيها البائعون الصفقات بحسب مزاجهم، فتفقد الأرقام معناها. والقاعدة أن تبدأ بخمس أو ست مراحل مع الفوز والخسارة، ثم تضيف مرحلة عندما تجد أن صفقات كثيرة تقف عند نقطة لا تراها في التقارير، مثل مراجعة الأمن السيبراني عند العميل.
المقاييس التي تهمك
أغلب لوحات المبيعات مزدحمة بالأرقام، ولكن أربعة مقاييس تكفي لفهم المسار، وكلها تحسب من الصفقات المغلقة:
- نسبة الفوز Win Rate: عدد الصفقات الرابحة على عدد الصفقات المغلقة كلها، رابحة وخاسرة. ولا تدخل فيها الصفقات المفتوحة، لأنها لم تنته بعد.
- متوسط قيمة الصفقة Average Deal Size: مجموع قيمة الصفقات الرابحة على عددها، ويختلف كثيراً بين عميل حكومي وشركة صغيرة، فاحسبه لكل شريحة إذا كان عندك أكثر من نوع.
- طول دورة البيع Sales Cycle: متوسط الأيام بين إنشاء الفرصة والفوز بها. وفي البيع للشركات يقاس بالأشهر، وفي المنافسات الحكومية قد يزيد كثيراً لأن مواعيد المنافسة تحددها الجهة لا أنت.
- نسبة التحويل بين المراحل Stage Conversion: كم من الصفقات التي وصلت إلى مرحلة انتقلت إلى التي بعدها، وهي التي تقول لك أين يتسرب المسار.
ومن هذه الأرقام نحسب سرعة البيع Sales Velocity، وهي ما يدخل الشركة في اليوم من المسار الحالي إذا بقيت الأرقام كما هي:

وفائدة المعادلة أنها تبين لك أين تعمل: زيادة الفرص مسؤولية التسويق، ونسبة الفوز مسؤولية التأهيل وجودة العرض، وقيمة الصفقة مسؤولية التسعير والتموضع، وطول الدورة مسؤولية كل من يؤخر العميل، بما فيهم فريقك التقني إذا تأخر في الرد على أسئلة الأمن.
وآخر مقياس هو نسبة التغطية Pipeline Coverage: قيمة المسار المفتوح على الهدف Target للفترة نفسها. فإذا كان هدف الربع 1.2 مليون ريال والمسار المفتوح 2.6 مليون فالتغطية 2.2 ضعف. وكم تحتاج؟ يعتمد على نسبة فوزك، فإذا كنت تفوز بصفقة من كل ثلاث تحتاج ثلاثة أضعاف على الأقل، وإذا كانت نسبة فوزك 13% كما في تجربتنا فـ 2.2 ضعف لا تكفي أبداً، وسوف نرى ذلك بالأرقام.
طريقتان للتوقع Forecast
الحل البسيط أن تجمع قيمة المسار كله وتسميه توقعاً، وهذه أسوأ الطرق على الإطلاق، لأنها تفترض أنك ستفوز بكل شيء. والطريقتان المستخدمتان فعلاً:
- التوقع الموزون Weighted Forecast: تضرب قيمة كل صفقة في احتمال الفوز من مرحلتها، ثم تجمع. ونقطة قوته أنه لا يعتمد على رأي أحد، ونقطة ضعفه أنه يعامل صفقتين في نفس المرحلة بنفس الاحتمال، حتى لو كانت إحداهما بعقد جاهز للتوقيع والأخرى توقف صاحبها عن الرد. والاحتمال يجب أن يحسب من صفقاتك المغلقة، لا أن يؤخذ من الإعدادات الافتراضية في نظام CRM.
- فئات التوقع Forecast Categories: يضع البائع كل صفقة في فئة: ملتزم Commit، أي أنه متأكد من إغلاقها في الفترة، وأفضل حالة Best Case، أي ممكنة إذا سار كل شيء جيداً، ومسار Pipeline لما تبقى. ونقطة قوتها أنها تستخدم ما يعرفه البائع عن الصفقة، ونقطة ضعفها أن البائع متفائل بطبعه، أو متحفظ بطبعه إذا كان مكافأته تحسب على تجاوز الرقم الذي التزم به.
والممارسة الجيدة أن تحسب الاثنين وتقارن بينهما كل أسبوع، فإذا كان الالتزام أعلى بكثير من التوقع الموزون فهناك صفقة يراها البائع أقوى مما تقوله مرحلتها، وهذه أول صفقة تسأل عنها في الاجتماع.
تجربة عملية: نحسب المسار من ملف بيانات
لنفرض أن لدينا شركة تقنية صغيرة أغلقت 60 صفقة خلال سنة، ولديها الآن 8 صفقات مفتوحة. البيانات هنا مولدة لهذا المثال ببذرة عشوائية ثابتة لكي تحصل على نفس الأرقام إذا أعدت التجربة، وليست بيانات شركة حقيقية. والمطلوب Docker فقط، فلن نثبت Python على الجهاز. أنشئ مجلداً وفيه أولاً الملف make_data.py الذي يولد تاريخ الصفقات المغلقة:
# Generates the example data used in the article (fixed seed, so the numbers are reproducible).
import csv, random
from datetime import date, timedelta
random.seed(2026)
STAGES = ["qualified", "discovery", "proposal", "negotiation"]
# chance to move on from each stage in this example company
MOVE = {"qualified": 0.55, "discovery": 0.65, "proposal": 0.55, "negotiation": 0.70}
rows = []
for i in range(1, 61):
start = date(2025, 10, 1) + timedelta(days=random.randint(0, 240))
amount = random.choice([60, 90, 120, 150, 180, 240, 300, 420, 600]) * 1000
reached = "qualified"
for s in STAGES:
reached = s
if random.random() > MOVE[s]:
outcome = "lost"; break
else:
outcome = "won"
days = {"qualified": 20, "discovery": 45, "proposal": 70, "negotiation": 95}[reached] + random.randint(-10, 25)
rows.append([f"D{i:03}", start.isoformat(), (start + timedelta(days=days)).isoformat(), reached, outcome, amount])
with open("history.csv", "w", newline="") as f:
w = csv.writer(f); w.writerow(["id", "created", "closed", "last_stage", "outcome", "amount_sar"]); w.writerows(rows)ثم الصفقات المفتوحة في الملف open.csv، ولكل صفقة فئة التوقع التي اختارها البائع:
id,name,stage,amount_sar,expected_close,category
O01,بوابة الطلاب والدفع الإلكتروني,negotiation,900000,2026-12-15,commit
O02,استضافة وصيانة سنوية,negotiation,72000,2026-11-30,commit
O03,ربط المتجر الإلكتروني مع ERP,proposal,180000,2026-12-30,best_case
O04,نظام إدارة الصيانة للمصنع,proposal,240000,2027-01-20,best_case
O05,تطبيق تتبع الشحنات,qualified,620000,2027-01-31,pipeline
O06,مرحلة ثانية: تطبيق المرضى,discovery,350000,2027-02-28,pipeline
O07,منصة التدريب الداخلي,discovery,150000,2027-01-15,pipeline
O08,لوحة مؤشرات الإدارة,qualified,90000,2027-02-10,pipelineوالملف pipeline.py يحسب المقاييس والتوقع:
# Pipeline metrics from closed deals (history.csv) and a forecast for open deals (open.csv).
import csv
from datetime import date
STAGES = ["qualified", "discovery", "proposal", "negotiation"]
hist = list(csv.DictReader(open("history.csv", encoding="utf-8")))
opn = list(csv.DictReader(open("open.csv", encoding="utf-8")))
# 1) How many deals reached each stage, and how many of them were won in the end
reached = {s: [d for d in hist if STAGES.index(d["last_stage"]) >= STAGES.index(s)] for s in STAGES}
won = [d for d in hist if d["outcome"] == "won"]
print("stage reached moved on conversion win prob. from here")
prob = {}
for i, s in enumerate(STAGES):
n = len(reached[s])
nxt = len(reached[STAGES[i + 1]]) if i + 1 < len(STAGES) else len(won)
w = sum(1 for d in reached[s] if d["outcome"] == "won")
prob[s] = w / n
print(f"{s:<13}{n:>8}{nxt:>10}{nxt / n:>12.0%}{prob[s]:>21.0%}")
# 2) Win rate, deal size, cycle length, sales velocity
win_rate = len(won) / len(hist)
avg_won = sum(int(d["amount_sar"]) for d in won) / len(won)
cycle = sum((date.fromisoformat(d["closed"]) - date.fromisoformat(d["created"])).days for d in won) / len(won)
velocity = len(opn) * win_rate * avg_won / cycle
print(f"\nclosed deals {len(hist)}, won {len(won)}, win rate {win_rate:.0%}")
print(f"average won deal {avg_won:,.0f} SAR, average cycle {cycle:.0f} days")
print(f"sales velocity = {len(opn)} x {win_rate:.2f} x {avg_won:,.0f} / {cycle:.0f} = {velocity:,.0f} SAR per day")
# 3) Forecast for the open deals: weighted by stage, and by the rep's category
total = sum(int(d["amount_sar"]) for d in opn)
weighted = sum(int(d["amount_sar"]) * prob[d["stage"]] for d in opn)
cat = {c: sum(int(d["amount_sar"]) for d in opn if d["category"] == c) for c in ["commit", "best_case", "pipeline"]}
print(f"\nopen pipeline {total:,} SAR in {len(opn)} deals")
print(f"weighted forecast {weighted:,.0f} SAR")
print(f"commit {cat['commit']:,} best case {cat['commit'] + cat['best_case']:,} (pipeline only {cat['pipeline']:,})")
target = 1_200_000
print(f"coverage for a target of {target:,} SAR: {total / target:.1f}x")ثم نشغل الاثنين في Container من Docker Image الرسمية لـ Python:
docker run --rm -v "$PWD:/lab" -w /lab python:3.13.17-slim \
sh -c "python make_data.py && python pipeline.py"والمخرج سوف يكون كما يلي:
stage reached moved on conversion win prob. from here
qualified 60 37 62% 13%
discovery 37 28 76% 22%
proposal 28 16 57% 29%
negotiation 16 8 50% 50%
closed deals 60, won 8, win rate 13%
average won deal 251,250 SAR, average cycle 108 days
sales velocity = 8 x 0.13 x 251,250 / 108 = 2,476 SAR per day
open pipeline 2,602,000 SAR in 8 deals
weighted forecast 808,775 SAR
commit 972,000 best case 1,392,000 (pipeline only 1,210,000)
coverage for a target of 1,200,000 SAR: 2.2xفي المخرج أعلاه لاحظ التالي:
- من كل 60 صفقة مؤهلة فزنا بثمان فقط، أي 13%، وأكبر تسرب كان في مرحلة التأهيل نفسها، حيث خرجت 23 صفقة من 60. وهذا يعني أن مشكلة هذه الشركة في جودة ما يدخل المسار قبل أن تكون في العرض أو التفاوض، فراجع شروط التأهيل في مقال المبيعات B2B قبل أن تطلب من التسويق عملاء أكثر.
- احتمال الفوز من مرحلة التفاوض 50% فقط، ولكن البائع وضع صفقة بوابة الطلاب بقيمة 900 ألف في فئة «ملتزم». لذلك الالتزام 972 ألفاً أعلى من التوقع الموزون للمسار كله 808,775 ريالاً، وهذه هي الصفقة التي يسأل عنها المدير: ما الذي يعرفه البائع ولا تعرفه المرحلة؟ هل وصل العقد إلى المشتريات؟ هل هناك أمر شراء؟
- التغطية 2.2 ضعف تبدو مريحة، ولكن مع نسبة فوز 13% يكون المتوقع من المسار كله أقل من الهدف بكثير، وهذا ما يقوله التوقع الموزون صراحة: 808 آلاف مقابل هدف 1.2 مليون.
- سرعة البيع 2,476 ريالاً في اليوم، وفي 90 يوماً هذا حوالي 223 ألف ريال، أي أن المسار الحالي وحده لا يكفي الربع، وعلى الشركة أن تضيف فرصاً أو ترفع نسبة الفوز.
ماذا يحدث إذا تأخرت صفقة واحدة؟
لنفرض أن الجامعة أبلغتنا أن اعتماد الميزانية تأجل إلى السنة المالية القادمة، فخرجت صفقة البوابة من هذا الربع. عدل الملف open.csv واحذف السطر O01 ثم أعد تشغيل pipeline.py، وسوف تجد أن المسار المفتوح نزل من 2,602,000 إلى 1,702,000 ريال، والتغطية من 2.2 إلى 1.4 ضعف، والالتزام من 972 ألفاً إلى 72 ألفاً فقط، والتوقع الموزون نزل 450 ألفاً، وهي قيمة الصفقة مضروبة في احتمال مرحلتها. صفقة واحدة من ثمان حملت 93% من الالتزام، وهذا ما يسمى تركز المسار Pipeline Concentration، وهو أخطر على الشركة الصغيرة من أي رقم آخر في التقرير، لأن تأخر عميل واحد يغير خطة الرواتب والتوظيف. والحل ليس في الحساب، وإنما في أن يكون في المسار دائماً عدة صفقات متوسطة بجانب الصفقة الكبيرة.
نظافة المسار Pipeline Hygiene
كل الأرقام أعلاه صحيحة بقدر صحة البيانات، والبيانات تفسد بهدوء إذا لم تكن هناك قواعد. وهذه القواعد التي تجعل التقرير صادقاً، ويستطيع نظام CRM أن يذكر بها تلقائياً:
- تاريخ إغلاق في الماضي: كل صفقة مفتوحة تاريخ إغلاقها مضى يجب أن تحدث أو تغلق، والسبب أن التوقع الشهري يبنى على هذا التاريخ.
- لا خطوة تالية Next Step: كل صفقة مفتوحة يجب أن يكون لها إجراء قادم بتاريخ، مثل اجتماع أو رد على سؤال، وإلا فهي متوقفة.
- صفقة لم تتحرك منذ مدة: إذا بقيت صفقة في نفس المرحلة أكثر من ضعف متوسط الأيام في تلك المرحلة، فناقشها أو أخرجها، فالصفقة الميتة في المسار ترفع التغطية وتخدع الجميع.
- تحديث التاريخ عند الفوز: عندما تفوز غير تاريخ الإغلاق إلى يوم التوقيع الفعلي، وإلا ظهرت الإيرادات في الشهر الخطأ، وقد رأينا ذلك في دليل Twenty.
- سبب الخسارة إلزامي: السعر، أو المنافس، أو لا قرار، أو لا ميزانية، ومن هذه القائمة تعرف ماذا تصلح في المنتج أو التسعير.
الفوز ليس إيراداً بعد
عندما يوقع العميل عقداً بقيمة 900 ألف لمدة ثلاث سنوات، فهذه حجوزات Bookings بقيمة 900 ألف، ولكن الإيراد Revenue في القوائم المالية يسجل بحسب تقديم الخدمة على مدة العقد، والنقد Cash يدخل بحسب جدول الدفعات في العقد. وهذه ثلاثة أرقام مختلفة لنفس الصفقة، وقد شرحنا الفرق بين أساس النقد وأساس الاستحقاق في مقال المالية للتقنيين. لذلك عندما يسألك المدير المالي عن التوقع، اسأله أولاً: هل يريد الحجوزات، أم الإيراد، أم النقد؟ وفي شركات SaaS يضاف رقم رابع هو الإيراد السنوي المتكرر ARR، وهو قيمة الاشتراكات السنوية الجارية دون الرسوم لمرة واحدة مثل التركيب والتدريب.
أخطاء شائعة
- احتمالات من الإعدادات الافتراضية: أنظمة CRM تأتي باحتمال لكل مرحلة، مثل 10% و50% و80%، وهذه أرقام لا علاقة لها بشركتك، فاستبدلها بما تحسبه من صفقاتك المغلقة كل ربع.
- مراحل باسم نشاط البائع: «أرسلنا العرض» ليست مرحلة، وإنما «العميل يقيم العرض».
- جمع عملات مختلفة: صفقة بالدولار وأخرى بالريال في نفس المجموع دون تحويل.
- نسبة فوز من الصفقات المفتوحة: نسبة الفوز تحسب من المغلقة فقط، وإلا انخفضت كلما أضفت فرصاً جديدة.
- توقع بلا مالك: كل صفقة لها بائع واحد مسؤول عن تاريخها ومرحلتها، والصفقة التي يملكها الجميع لا يحدثها أحد.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- المرحلة تصف ما فعله العميل، ولها شرط خروج يتحقق منه أي شخص.
- أربعة مقاييس تكفي: نسبة الفوز ومتوسط قيمة الصفقة وطول الدورة والتحويل بين المراحل، ومنها سرعة البيع والتغطية.
- احسب التوقع الموزون وفئات التوقع معاً، والفرق بينهما يدلك على الصفقة التي تسأل عنها.
- في تجربتنا كان المسار 2.6 مليون، والتوقع الموزون 808 آلاف، وصفقة واحدة حملت 93% من الالتزام.
- الحجوزات والإيراد والنقد ثلاثة أرقام مختلفة لنفس الصفقة.
تدريب عملي
- عدل
MOVEفيmake_data.pyلترفع التحويل من مرحلة التأهيل إلى 70%، وشاهد كيف تتغير نسبة الفوز وسرعة البيع. - أضف إلى
pipeline.pyتوقعاً لكل شهر بحسبexpected_close، كما فعلنا في دليل Twenty.
وفي المقال القادم بإذن الله سوف نقارن أنظمة CRM التي تحمل هذا المسار. وأقرب المقالات إلى هذا الموضوع على الموقع:
دليلإدارة علاقات العملاء CRM: Salesforce وبدائله مفتوحة المصدرمقارنة أنظمة CRM التجارية ومفتوحة المصدر بالترخيص والصيانة والأسعار، مع اختبار فعلي لمسار البيع بالعربية في أربعة أنظمة.
دليلCRM على سيرفرك مع Twenty: مسار بيع كاملنشغل Twenty في Docker ونبني مسار بيع بأسماء عربية، ونستورد العملاء ونحسب توقع الإيرادات، ونرسل كل تغيير إلى نظام آخر عبر Webhook.
دليلميزانية التسويق وتكلفة العميل CAC والعائد ROIكيف تحسب تكلفة اكتساب العميل وقيمته وفترة الاسترداد والعائد لكل قناة، وكيف تحدد حجم الميزانية من هدفك لا من نسب الشركات الكبيرة.
دليلحساب العائد ROI والقيمة الحالية NPV لمشروعك التقنيكيف تحسب العائد وفترة الاسترداد والقيمة الحالية لمشروع تقني، وتختبر افتراضاته، وتعرضه على قسم المالية في صفحة واحدة.سجل التحديثات
- أكتوبر 2026: كتابة المقال وتشغيل التجربة على python:3.13.17-slim في Docker Engine 29.8.0.
