Embudo de ventas de un CRM: conversión y previsión ponderada
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
- La entrada es un CSV con cabecera
id;cliente;etapa;importe;cierre;comercial. La etapa es una de las cinco oPerdido: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 escribenLínea ignorada: línea. 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,oportunidaduoportunidades(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.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).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).Por comercialy, 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.- 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
| Etapa | Probabilidad |
|---|---|
| Nuevo | 10 % |
| Cualificado | 25 % |
| Propuesta | 50 % |
| Negociación | 75 % |
| Ganado | 100 % |
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.
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.
Ejemplo
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
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
Solución explicada
Ver la solución completa
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.