📊 Developer Cookbook - FASE 8: Estimación y Gestión de Proyectos
Story points, planning poker, gestión de riesgos y comunicación de incertidumbre
Receta 8.9: Story Points y Planning Poker
¿Qué son?
- Story Points: Unidad abstracta para estimar complejidad (no tiempo)
- Planning Poker: Técnica de estimación en grupo usando consenso
Por qué Story Points > Horas:
class AgileEstimation:
"""Framework para estimación ágil"""
@staticmethod
def why_story_points():
"""Por qué usar story points en vez de horas"""
# PROBLEM WITH HOUR ESTIMATES:
# - Different developers, different speeds:
# Junior: "16 hours" | Senior: "2 hours" (both correct, not comparable)
# - Same developer, different days:
# Monday focused: 4 hours | Friday tired: 8 hours
# Hours vary. Complexity doesn't.
# STORY POINTS MEASURE:
# - Complexity (How hard?)
# - Uncertainty (How much unknown?)
# - Effort (How much work?)
# NOT time!
# FIBONACCI SCALE: 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
# Why Fibonacci? Bigger gaps for bigger tasks (intentional vagueness)
calibration = {
"1 point (Trivial)": [
"Fix typo in documentation",
"Update environment variable",
"Change button color"
],
"2 points (Simple)": [
"Add logging to existing function",
"Write unit test for existing code",
"Update copy on a page"
],
"3 points (Straightforward)": [
"Add new API endpoint (CRUD)",
"Create simple form with validation",
"Fix minor bug with clear reproduction"
],
"5 points (Moderate)": [
"Build feature with 2-3 components",
"Refactor module",
"Fix bug requiring investigation"
],
"8 points (Complex)": [
"Multi-step workflow",
"Integration with third-party API",
"Performance optimization"
],
"13 points (Very Complex)": [
"Large feature touching many modules",
"Significant refactor",
"Unknown complexity (needs spike)"
],
"21+ points (Epic)": [
"Too large, needs to be broken down",
"Multiple sprints"
]
}
# Rule: If >13 points, split the story
return calibration
@staticmethod
def planning_poker_process():
"""Cómo jugar planning poker"""
process = """
PLANNING POKER PROCESS
======================
SETUP:
- Everyone gets cards: 1, 2, 3, 5, 8, 13, 21, ?, coffee
- Product Owner presents story
- Timer ready (optional)
STEPS:
1. PRESENT STORY (5 min)
PO: "As a user, I want to reset my password via email"
Acceptance criteria:
- User clicks "Forgot password"
- Enter email
- Receive email with reset link
- Click link, set new password
2. CLARIFY (5 min)
Team asks questions:
Dev 1: "Do we already have email service?"
PO: "Yes, SendGrid is set up"
Dev 2: "How long is reset link valid?"
PO: "24 hours"
3. ESTIMATE INDIVIDUALLY (1 min)
- Each person thinks independently
- Choose a card
- Place face-down (don't show yet!)
4. REVEAL SIMULTANEOUSLY
Everyone flips cards at once:
Alice: 3 | Bob: 5 | Charlie: 8 | Diana: 5
5. DISCUSS OUTLIERS (5 min)
Facilitator: "Alice, why 3?"
Alice: "Seems straightforward. Email service exists."
Facilitator: "Charlie, why 8?"
Charlie: "We need to add password validation rules,
test token expiration, handle edge cases. Security concerns too."
Team: "Oh right, we forgot about security!"
6. RE-ESTIMATE
Alice: 5 | Bob: 5 | Charlie: 5 | Diana: 5
Consensus! Story = 5 points
7. REPEAT FOR NEXT STORY
SPECIAL CARDS:
? (Question mark) = "I don't understand the story / need more info"
coffee cup = "I'm tired, need a break"
Infinity = "This is way too big, needs to be split"
TIPS:
- Estimate as team (not individually)
- Discuss outliers (high and low)
- Use previous stories as reference
- Time-box discussions (max 10 min per story)
- Never show cards before everyone is ready (anchoring bias)
- Never average estimates (defeats purpose)
"""
print(process)
@staticmethod
def velocity_and_capacity():
"""Calcular velocity y capacity"""
# VELOCITY: Points completed per sprint (historical data)
# Sprint 1: 23 | Sprint 2: 28 | Sprint 3: 25 | Sprint 4: 20 | Sprint 5: 27
# Average velocity = (23+28+25+20+27) / 5 = 24.6 ~= 25 points
# CAPACITY: Available hours per sprint
# Example (2-week sprint, 4 developers):
# Theoretical: 4 * 10 * 8 = 320 hours
# Subtract meetings (80h) + code reviews (40h) + slack (40h) + PTO (40h) + buffer 10%
# Actual capacity: ~88 hours
# COMMITMENT:
# Conservative: use lowest recent velocity (20 points)
# Moderate: use average velocity (25 points)
# Aggressive: use highest (28 points) - risky
# BURN-DOWN CHART:
# Day 0: 25 points | Day 2: 22 | Day 4: 18 | Day 6: 15 | Day 8: 10 | Day 10: 0
# Ideal: Straight line from start to end
# Too slow -> drop lowest priority stories or ask for help
# Too fast -> pull in more stories from backlog
# FACTORS AFFECTING VELOCITY:
# Increase: reduce tech debt, improve test automation, clearer requirements
# Decrease: new team members, more meetings, production incidents
pass
AgileEstimation.planning_poker_process()
Receta 8.10: Gestión de Riesgos Técnicos
¿Qué es? Identificar y mitigar riesgos técnicos antes de que se conviertan en problemas.
Framework de gestión de riesgos:
from dataclasses import dataclass
from typing import List
from enum import Enum
class RiskProbability(Enum):
"""Probabilidad de que ocurra"""
LOW = 1 # <20% chance
MEDIUM = 2 # 20-50% chance
HIGH = 3 # >50% chance
class RiskImpact(Enum):
"""Impacto si ocurre"""
LOW = 1 # Minor inconvenience
MEDIUM = 2 # Delays sprint
HIGH = 3 # Delays release
CRITICAL = 4 # Blocks launch
@dataclass
class TechnicalRisk:
"""Modelo de riesgo técnico"""
title: str
description: str
probability: RiskProbability
impact: RiskImpact
triggers: List[str] # Señales de que está ocurriendo
mitigation: List[str] # Cómo prevenirlo
contingency: List[str] # Qué hacer si ocurre
owner: str
def risk_score(self) -> int:
"""Calcular score de riesgo (probability * impact)"""
return self.probability.value * self.impact.value
def priority(self) -> str:
"""Prioridad basada en score"""
score = self.risk_score()
if score >= 9:
return "CRITICAL"
elif score >= 6:
return "HIGH"
elif score >= 3:
return "MEDIUM"
else:
return "LOW"
# Ejemplo: Riesgos de un proyecto de migración de DB
risks = [
TechnicalRisk(
title="Data loss during migration",
description="Migrating 100M records from MySQL to PostgreSQL. Risk of data corruption or loss.",
probability=RiskProbability.MEDIUM,
impact=RiskImpact.CRITICAL,
triggers=[
"Test migration shows missing records",
"Checksums don't match",
"Users report missing data"
],
mitigation=[
"Test migration on staging with production snapshot",
"Row-by-row checksum verification",
"Dual-write to both DBs during transition",
"Keep MySQL running for 1 month as backup"
],
contingency=[
"Rollback to MySQL immediately",
"Restore from backup",
"Communicate: 'Service degraded, investigating'"
],
owner="@sarah-backend"
),
TechnicalRisk(
title="Migration takes longer than expected",
description="Estimated 4 hours, but could take 12+ hours if issues arise.",
probability=RiskProbability.HIGH,
impact=RiskImpact.HIGH,
triggers=[
"Test migration took 6 hours (50% slower than estimate)",
"Network bandwidth lower than expected",
"Lock contention on tables"
],
mitigation=[
"Schedule during low-traffic window (3am Sunday)",
"Batch migration in chunks (10M rows at a time)",
"Pre-warm new database",
"Optimize queries before migration"
],
contingency=[
"Extend maintenance window",
"Pause migration, resume later",
"Rollback and reschedule"
],
owner="@devops-lead"
),
TechnicalRisk(
title="Application doesn't work with PostgreSQL",
description="Despite testing, some edge cases might only work with MySQL.",
probability=RiskProbability.LOW,
impact=RiskImpact.HIGH,
triggers=[
"Errors in production logs after migration",
"Users reporting bugs",
"Features not working as expected"
],
mitigation=[
"Comprehensive integration tests on staging",
"Canary deployment (1% of traffic first)",
"Feature flags to toggle between MySQL/PostgreSQL",
"Shadow traffic (compare responses from both DBs)"
],
contingency=[
"Route traffic back to MySQL",
"Hotfix and redeploy",
"Rollback migration"
],
owner="@sarah-backend"
),
]
# Risk Register
class RiskRegister:
"""Registro de riesgos del proyecto"""
def __init__(self, risks: List[TechnicalRisk]):
self.risks = risks
def print_register(self):
"""Imprimir registro de riesgos ordenado por prioridad"""
sorted_risks = sorted(self.risks, key=lambda r: r.risk_score(), reverse=True)
print("TECHNICAL RISK REGISTER")
print("=" * 80)
for risk in sorted_risks:
print(f"\n[{risk.priority()}] {risk.title}")
print(f"Owner: {risk.owner}")
print(f"Score: {risk.risk_score()}/12")
print(f"Description: {risk.description}")
print("Triggers:")
for t in risk.triggers:
print(f" - {t}")
print("Mitigation:")
for m in risk.mitigation:
print(f" - {m}")
print("Contingency:")
for c in risk.contingency:
print(f" - {c}")
print("-" * 80)
register = RiskRegister(risks)
register.print_register()
Risk review meetings:
# Weekly Risk Review Template
**Date**: 2024-02-20
**Project**: Database Migration
**Attendees**: @sarah, @devops-lead, @tech-lead, @pm
## Agenda
1. Review existing risks (15 min)
2. Identify new risks (10 min)
3. Update mitigation plans (10 min)
4. Assign actions (5 min)
## Risk Updates
### CRITICAL: Data loss during migration
**Status**: MITIGATED
- Completed test migration on staging
- 100% checksum match
- Dual-write setup complete
- Keeping MySQL for 1 month (in progress)
**Next actions**:
- [ ] Final production migration dry-run (Saturday 3am) - @sarah
### HIGH: Migration takes too long
**Status**: IN PROGRESS
- Scheduled 6-hour window (3am-9am Sunday)
- Batching implemented (10M rows/batch)
- Network bandwidth: only 500Mbps (expected 1Gbps)
**Concern**: Bandwidth might extend migration to 8 hours
**Actions**:
- [ ] Compress data before transfer - @devops-lead
- [ ] Request ISP upgrade (emergency) - @devops-lead
- [ ] Prepare comms for extended window - @pm
### New Risk Identified
**Title**: Third-party API rate limits
**Probability**: LOW | **Impact**: MEDIUM
**Description**: 1M API calls/day. PostgreSQL setup might change
request patterns and trigger rate limits.
**Mitigation**:
- [ ] Monitor API usage during migration - @sarah
- [ ] Contact API provider for temp limit increase - @pm
## Action Items
- [ ] Network bandwidth upgrade - @devops-lead - Due: Feb 22
- [ ] Production dry-run - @sarah - Due: Feb 24
- [ ] Extended window communication draft - @pm - Due: Feb 23
## Next Review: Feb 27, 2pm
Receta 8.11: Comunicación de Incertidumbre
¿Qué es? Comunicar estimaciones y riesgos de forma honesta sin sonar incompetente o crear pánico.
Framework para comunicar incertidumbre:
class UncertaintyCommunication:
"""Cómo comunicar estimaciones e incertidumbre"""
@staticmethod
def cone_of_uncertainty():
"""Explicar el cono de incertidumbre"""
# Early in project: Wide uncertainty
# Late in project: Narrow uncertainty
#
# Early project: "Could be 2 weeks or 8 weeks" (4x variance)
# After design: "Will be 3-5 weeks" (1.5x variance)
# After impl: "Will be 3.5-4 weeks" (1.1x variance)
#
# BAD (over-confident):
# "This will take exactly 3 weeks" (Day 1, no design yet)
#
# GOOD (acknowledges uncertainty):
# "Based on similar projects, this is 2-8 weeks.
# After we finish design phase next week,
# I can give you a tighter estimate."
#
# PM: "Can you commit to 3 weeks?"
# BAD: "Yes" (sets false expectation)
# GOOD: "I can commit to getting you a confident estimate
# after design is done. Right now, best guess is
# 3-6 weeks, but that could change once we understand
# the requirements better."
pass
@staticmethod
def estimation_templates():
"""Templates para diferentes niveles de confianza"""
templates = {
"HIGH CONFIDENCE (done similar before)": """
"This is 3-4 days of work. I've built similar
features and don't foresee surprises. I'm 80%
confident we'll hit this."
Components: Range + Context (why confident) + Confidence level
""",
"MEDIUM CONFIDENCE (some unknowns)": """
"This is probably 1-2 weeks. The core work is
straightforward, but we need to integrate with
the payment API which I haven't used before.
That could add a few days if their docs are unclear."
Components: Range + What's known + What's unknown + What could go wrong
""",
"LOW CONFIDENCE (many unknowns)": """
"I honestly don't know yet. This requires research
into the legacy codebase which is poorly documented.
Give me 2 days to spike it, and I can give you a
real estimate. Right now, gut feeling is 1-4 weeks,
but that's low confidence."
Components: Honesty + What you need to reduce uncertainty + Rough ballpark
""",
"IMPOSSIBLE TO ESTIMATE": """
"I can't estimate this without breaking it down first.
The requirements are too vague. Can we schedule a
design session to clarify scope? After that, I can
give you an estimate."
Components: Clear 'I can't' + Why not + What's needed + When you can estimate
""",
"RESPONDING TO DEADLINE": """
PM: "We need this by March 1. Can you do it?"
BAD: "I'll try" / "Maybe" / "Yes" (when you mean no)
Option A (Yes, with caveat):
"Yes, if we cut features X and Y. The full scope
is 6 weeks, but MVP is doable in 4 weeks."
Option B (No, with alternative):
"No, realistic estimate is 6 weeks. We could hit
March 1 with 2 more engineers, or phased rollout
(core March 1, rest March 15)."
Option C (Need more info):
"I need to understand requirements better. Give me
1 day to break this down."
""",
"PROJECT IS BEHIND SCHEDULE": """
BAD: "Everything's fine" (when it's not)
BAD: "We're totally screwed!"
GOOD (proactive communication):
"Update: We're behind schedule. Originally estimated
2 weeks, we're on day 12 and 60% done.
Why we're behind:
- Third-party API was down for 2 days
- Requirements changed mid-sprint
New estimate: 4 more days (total 16 days)
Options:
1. Extend deadline by 4 days
2. Cut feature Z (saves 2 days, hit deadline)
3. Add 1 more dev (might save 1-2 days)
Recommendation: Option 2 (cut feature Z)"
"""
}
return templates
@staticmethod
def three_point_estimation():
"""Técnica de estimación de 3 puntos"""
# Instead of single estimate, give 3:
# - OPTIMISTIC (Best case): Everything goes perfectly
# - REALISTIC (Most likely): Normal case
# - PESSIMISTIC (Worst case): Murphy's law
#
# Example: "Add user authentication"
# Optimistic: 3 days (no blockers, library works, no edge cases)
# Realistic: 5 days (some debugging, a few edge cases)
# Pessimistic: 10 days (library bugs, security review, requirements change)
#
# PERT Formula:
# Expected time = (Optimistic + 4*Realistic + Pessimistic) / 6
# = (3 + 4*5 + 10) / 6 = 33/6 = 5.5 days
#
# Communicate as:
# "Most likely 5 days, could be as low as 3 or as high as 10"
#
# When to use: High-stakes projects, many unknowns, need to communicate risk
# When NOT: Simple tasks, tight deadlines
pass
@staticmethod
def confidence_intervals():
"""Comunicar con intervalos de confianza"""
examples = [
"High confidence: 'I'm 90% sure this is 2-3 days' (narrow range)",
"Medium confidence: 'I'm 70% sure this is 1-2 weeks' (medium range)",
"Low confidence: 'I'm 50% sure this is 2-8 weeks' (wide range)"
]
# When stakeholder pushes back:
# PM: "Can't you be more specific than '1-2 weeks'?"
#
# BAD: "Fine, 1.5 weeks" (false precision)
# GOOD: "I could say 1.5 weeks, but that would be lying.
# The uncertainty is real. If you need more certainty,
# I can spend 1 day doing a technical spike to narrow the range."
return examples
UncertaintyCommunication.estimation_templates()
¡Felicidades! 🎉
Has completado la FASE 8: Habilidades Blandas y Liderazgo del roadmap.
Lo que has aprendido:
Comunicación Técnica:
- ADRs (Architecture Decision Records) - Documentar decisiones
- RFCs (Request for Comments) - Proponer cambios
- Technical Writing - Escribir docs claras
- Presentaciones técnicas - Comunicar a diferentes audiencias
Liderazgo y Mentoría:
- Code Reviews constructivos - Feedback que ayuda
- Pair Programming - Colaboración en vivo
- Mob Programming - Todo el equipo junto
- Mentoría - Desarrollar a otros
- Delegación efectiva - Liberar tu tiempo
- Gestión de conflictos - Resolver desacuerdos técnicos
Estimación y Gestión de Proyectos:
- Story Points - Estimar complejidad
- Planning Poker - Estimación en equipo
- Velocity - Predecir entregas
- Gestión de riesgos - Identificar y mitigar
- Comunicar incertidumbre - Honestidad sin pánico
Próximos pasos:
FASE 9: Meta-aprendizaje — Aprendizaje Continuo, Adaptabilidad, Networking
Versión: 1.0 | Fecha: 2024 | Licencia: Uso educativo