Apuntes DAM
Volver al inicio

Embudo de ventas de un CRM: conversión y previsión ponderada

Ejercicio de PythonDifícilUnos 75 minutos

Analiza las oportunidades de un CRM como lo hace el panel de ventas: cuántas llegaron a cada etapa del embudo y cuánto suman, la conversión entre etapas, la tasa de éxito, la previsión de ingresos ponderada por la probabilidad de cada etapa por mes y el resultado de cada comercial.

  • CRM y embudo de ventas
  • Etapas y probabilidad
  • Tasa de conversión
  • Previsión ponderada
  • Agrupar con diccionarios
  • Formato de moneda

Enunciado

En un CRM cada posible venta es una oportunidad que avanza por las etapas del embudo: Nuevo, Cualificado, Propuesta, Negociación y, al final, Ganado (o Perdido en alguna de ellas). Arriba entran muchas y abajo llegan pocas: por eso se llama embudo.

La dirección quiere saber dónde se pierden las ventas (la conversión de cada etapa a la siguiente) y cuánto se va a facturar. Para lo segundo no basta con sumar los importes abiertos: una oportunidad recién llegada casi nunca se cierra. Se pondera cada una con la probabilidad de su etapa: 10 %, 25 %, 50 %, 75 % (y 100 % si ya está ganada).

Qué tiene que hacer el programa

  1. La entrada es un CSV con cabecera id;cliente;etapa;importe;cierre;comercial. La etapa es una de las cinco o Perdido:Etapa (la etapa en la que se perdió, que no puede ser Ganado). El cierre es el mes previsto (2026-11). Las líneas que no encajan escriben Línea ignorada: línea.
  2. Embudo (oportunidades que llegaron a cada etapa) y una línea por etapa: dos espacios, el nombre en 12 columnas, el número en 3 alineado a la derecha, oportunidad u oportunidades (en 13 columnas), el importe total en 14 columnas a la derecha y, desde la segunda etapa, · conversión P % (las que llegaron a esta entre las que llegaron a la anterior, sin decimales). Una oportunidad llegó a todas las etapas hasta la actual (o hasta aquella en la que se perdió); las perdidas no llegan a Ganado.
  3. Tasa de éxito: G ganadas de C cerradas (P %) (en singular si es 1) (las cerradas son las ganadas más las perdidas; la línea solo aparece si hay alguna cerrada).
  4. Previsión ponderada por mes de cierre (oportunidades abiertas) y, por cada mes en orden, mes: ponderado de bruto en N oportunidades (las abiertas son las que no están ganadas ni perdidas; ponderado = importe × probabilidad de su etapa).
  5. Por comercial y, por cada uno, nombre: ganado G · previsto P · N abiertas (1 abierta), ordenados de más a menos ganado, después de más a menos previsto y después por nombre.
  6. Los importes se escriben con dos decimales, coma decimal, punto de miles y €.

Entrada

Un CSV separado por ; con la cabecera id;cliente;etapa;importe;cierre;comercial.

Datos de referencia

Etapas y probabilidad de cierre
EtapaProbabilidad
Nuevo10 %
Cualificado25 %
Propuesta50 %
Negociación75 %
Ganado100 %

Ejemplos de ejecución

Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.

El embudo de un trimestre

Entrada

id;cliente;etapa;importe;cierre;comercial
1;Talleres Ruiz;Propuesta;12000;2026-11;Ana
2;Hotel Mar;Perdido:Negociación;30000;2026-10;Luis
3;Clínica Sol;Ganado;8500;2026-10;Ana
4;Bodegas Ebro;Nuevo;5000;2026-12;Luis
5;Academia Luz;Negociación;20000;2026-11;Eva
6;Café Plaza;Cualificado;1500;2026-11;Ana
7;Gimnasio Fit;Perdido:Nuevo;3000;2026-10;Eva
8;Hostal Rio;Ganado;4200;2026-09;Luis

Salida por consola

Embudo (oportunidades que llegaron a cada etapa)
  Nuevo          8 oportunidades    84.200,00 €
  Cualificado    6 oportunidades    76.200,00 € · conversión 75 %
  Propuesta      5 oportunidades    74.700,00 € · conversión 83 %
  Negociación    4 oportunidades    62.700,00 € · conversión 80 %
  Ganado         2 oportunidades    12.700,00 € · conversión 50 %
Tasa de éxito: 2 ganadas de 4 cerradas (50 %)
Previsión ponderada por mes de cierre (oportunidades abiertas)
  2026-11: 21.375,00 € de 33.500,00 € en 3 oportunidades
  2026-12: 500,00 € de 5.000,00 € en 1 oportunidad
Por comercial
  Ana: ganado 8.500,00 € · previsto 6.375,00 € · 2 abiertas
  Luis: ganado 4.200,00 € · previsto 500,00 € · 1 abierta
  Eva: ganado 0,00 € · previsto 15.000,00 € · 1 abierta

Líneas que no encajan

Entrada

id;cliente;etapa;importe;cierre;comercial
1;A;Ganado;1000;2026-10;Ana
2;B;Perdido:Ganado;500;2026-10;Ana
3;C;Cerrado;10;2026-10;Ana
4;D;Nuevo;mil;2026-10;Ana
5;E;Propuesta;2000
6;F;Propuesta;2000;2026-10;Bea

Salida por consola

Línea ignorada: 2;B;Perdido:Ganado;500;2026-10;Ana
Línea ignorada: 3;C;Cerrado;10;2026-10;Ana
Línea ignorada: 4;D;Nuevo;mil;2026-10;Ana
Línea ignorada: 5;E;Propuesta;2000
Embudo (oportunidades que llegaron a cada etapa)
  Nuevo          2 oportunidades     3.000,00 €
  Cualificado    2 oportunidades     3.000,00 € · conversión 100 %
  Propuesta      2 oportunidades     3.000,00 € · conversión 100 %
  Negociación    1 oportunidad       1.000,00 € · conversión 50 %
  Ganado         1 oportunidad       1.000,00 € · conversión 100 %
Tasa de éxito: 1 ganada de 1 cerrada (100 %)
Previsión ponderada por mes de cierre (oportunidades abiertas)
  2026-10: 1.000,00 € de 2.000,00 € en 1 oportunidad
Por comercial
  Ana: ganado 1.000,00 € · previsto 0,00 € · 0 abiertas
  Bea: ganado 0,00 € · previsto 1.000,00 € · 1 abierta

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. ¿Hasta dónde llegó?

Guarda para cada oportunidad el índice de la última etapa que alcanzó: la actual o, si está perdida, aquella en la que se perdió. Con eso el embudo es contar las que tienen un índice mayor o igual.

2. La conversión

Divide las que llegaron a una etapa entre las que llegaron a la anterior. Guarda el número de la etapa anterior al recorrer la lista.

3. Agrupar por mes y por comercial

setdefault crea la entrada de un comercial la primera vez que aparece; para los meses, un conjunto ordenado de los meses de las abiertas.

python
d = comerciales.setdefault(o["comercial"], {"ganado": 0.0, "previsto": 0.0, "abiertas": 0})
4. Ordenar por varios criterios

Una clave que es una tupla ordena por el primer elemento y desempata con los siguientes; el signo menos invierte un criterio numérico.

Resuélvelo aquí

El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.

🐍PythonEmbudo de ventas de un CRM: conversión y previsión ponderadaDifícil

Ejemplo

Entrada (lo que se escribe por teclado)
id;cliente;etapa;importe;cierre;comercial
1;Talleres Ruiz;Propuesta;12000;2026-11;Ana
2;Hotel Mar;Perdido:Negociación;30000;2026-10;Luis
3;Clínica Sol;Ganado;8500;2026-10;Ana
4;Bodegas Ebro;Nuevo;5000;2026-12;Luis
5;Academia Luz;Negociación;20000;2026-11;Eva
6;Café Plaza;Cualificado;1500;2026-11;Ana
7;Gimnasio Fit;Perdido:Nuevo;3000;2026-10;Eva
8;Hostal Rio;Ganado;4200;2026-09;Luis
Salida esperada
Embudo (oportunidades que llegaron a cada etapa)
  Nuevo          8 oportunidades    84.200,00 €
  Cualificado    6 oportunidades    76.200,00 € · conversión 75 %
  Propuesta      5 oportunidades    74.700,00 € · conversión 83 %
  Negociación    4 oportunidades    62.700,00 € · conversión 80 %
  Ganado         2 oportunidades    12.700,00 € · conversión 50 %
Tasa de éxito: 2 ganadas de 4 cerradas (50 %)
Previsión ponderada por mes de cierre (oportunidades abiertas)
  2026-11: 21.375,00 € de 33.500,00 € en 3 oportunidades
  2026-12: 500,00 € de 5.000,00 € en 1 oportunidad
Por comercial
  Ana: ganado 8.500,00 € · previsto 6.375,00 € · 2 abiertas
  Luis: ganado 4.200,00 € · previsto 500,00 € · 1 abierta
  Eva: ganado 0,00 € · previsto 15.000,00 € · 1 abierta
⏳
Test oculto #3
⏳
Test oculto #4
0/4 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
python
1import sys
2
3ETAPAS = ["Nuevo", "Cualificado", "Propuesta", "Negociación", "Ganado"]
4PROBABILIDAD = {"Nuevo": 10, "Cualificado": 25, "Propuesta": 50, "Negociación": 75, "Ganado": 100}
5
6
7def euros(x):
8    entero, dec = f"{x:.2f}".split(".")
9    grupos = []
10    while len(entero) > 3:
11        grupos.insert(0, entero[-3:])
12        entero = entero[:-3]
13    grupos.insert(0, entero)
14    return ".".join(grupos) + "," + dec + " €"
15
16
17def main():
18    lineas = [l.strip() for l in sys.stdin.read().split("\n") if l.strip()]
19    opos = []
20    for linea in lineas[1:]:
21        c = linea.split(";")
22        if len(c) != 6:
23            print(f"Línea ignorada: {linea}")
24            continue
25        ident, cliente, etapa, importe, cierre, comercial = c
26        perdida = None
27        if etapa.startswith("Perdido:"):
28            perdida = etapa.split(":", 1)[1]
29            if perdida not in ETAPAS[:-1]:
30                print(f"Línea ignorada: {linea}")
31                continue
32        elif etapa not in ETAPAS:
33            print(f"Línea ignorada: {linea}")
34            continue
35        try:
36            importe = float(importe)
37        except ValueError:
38            print(f"Línea ignorada: {linea}")
39            continue
40        # hasta qué etapa llegó: la actual, o en la que se perdió
41        llego = ETAPAS.index(perdida if perdida else etapa)
42        opos.append({"id": ident, "cliente": cliente, "etapa": "Perdido" if perdida else etapa, "llego": llego, "importe": importe, "cierre": cierre, "comercial": comercial})
43
44    print("Embudo (oportunidades que llegaron a cada etapa)")
45    anterior = None
46    for i, e in enumerate(ETAPAS):
47        llegaron = [o for o in opos if o["llego"] >= i and not (o["etapa"] == "Perdido" and e == "Ganado")]
48        n = len(llegaron)
49        conversion = f" · conversión {n * 100 / anterior:.0f} %" if anterior else ""
50        palabra = "oportunidad" if n == 1 else "oportunidades"
51        print(f"  {e:<12} {n:>3} {palabra:<13} {euros(sum(o['importe'] for o in llegaron)):>14}{conversion}")
52        anterior = n if n else None
53    ganadas = [o for o in opos if o["etapa"] == "Ganado"]
54    perdidas = [o for o in opos if o["etapa"] == "Perdido"]
55    cerradas = len(ganadas) + len(perdidas)
56    if cerradas:
57        g = len(ganadas)
58        print(f"Tasa de éxito: {g} ganada{'' if g == 1 else 's'} de {cerradas} cerrada{'' if cerradas == 1 else 's'} ({g * 100 / cerradas:.0f} %)")
59    abiertas = [o for o in opos if o["etapa"] not in ("Ganado", "Perdido")]
60    print("Previsión ponderada por mes de cierre (oportunidades abiertas)")
61    for mes in sorted({o["cierre"] for o in abiertas}):
62        del_mes = [o for o in abiertas if o["cierre"] == mes]
63        bruto = sum(o["importe"] for o in del_mes)
64        ponderado = sum(o["importe"] * PROBABILIDAD[o["etapa"]] / 100 for o in del_mes)
65        print(f"  {mes}: {euros(ponderado)} de {euros(bruto)} en {len(del_mes)} {'oportunidad' if len(del_mes) == 1 else 'oportunidades'}")
66    print("Por comercial")
67    comerciales = {}
68    for o in opos:
69        d = comerciales.setdefault(o["comercial"], {"ganado": 0.0, "previsto": 0.0, "abiertas": 0})
70        if o["etapa"] == "Ganado":
71            d["ganado"] += o["importe"]
72        elif o["etapa"] != "Perdido":
73            d["previsto"] += o["importe"] * PROBABILIDAD[o["etapa"]] / 100
74            d["abiertas"] += 1
75    for nombre, d in sorted(comerciales.items(), key=lambda x: (-x[1]["ganado"], -x[1]["previsto"], x[0])):
76        a = d["abiertas"]
77        print(f"  {nombre}: ganado {euros(d['ganado'])} · previsto {euros(d['previsto'])} · {a} abierta{'' if a == 1 else 's'}")
78
79
80main()

El embudo cuenta las que llegaron a cada etapa, no las que están ahora en ella: una venta ganada pasó por todas. Contarlo así es lo que permite ver en qué paso se pierden más oportunidades.

La previsión ponderada es una esperanza matemática: si hay diez oportunidades de 10.000 € en Propuesta, no se facturarán 100.000 €, pero a la larga se acercará a 50.000 €. Las probabilidades por etapa suelen ajustarse con el histórico de la propia empresa.

La tasa de éxito solo mira las cerradas: las abiertas todavía pueden ir a cualquier lado.

Ordenar con una tupla como clave es la forma idiomática en Python de ordenar por varios criterios a la vez.

Para ir más allá

  • Calcula la probabilidad real de cada etapa a partir de las oportunidades cerradas y compárala con la configurada.
  • Añade el tiempo medio que pasa una oportunidad en cada etapa (con fechas de cambio de etapa).
  • Genera un gráfico de barras del embudo con caracteres.

Dónde se explica