Spec-Driven Development · Fase 1 completa

Spec: LeadPulse — Dashboard de Leads & Scoring Automático

Especificación estructurada generada antes de escribir cualquier línea de código  ·  v1.0.0  ·  15 jun 2026

Cliente: LeadPulse SaaS
Equipo: 2 devs + 1 PM
Plazo: 6 semanas
Sprint actual: 1 / 6
Estado: ✓ Spec aprobada
① SPECIFY
✓ Completado
──▶
② PLAN
Pendiente revisión
──▶
③ TASKS
En progreso
──▶
④ IMPLEMENT
Bloqueado hasta ③

⚠ Supuestos que estoy asumiendo — Confirma antes de continuar

  1. El backend FastAPI existente se extiende; no se migra a otro framework.
  2. El scoring de leads usa reglas heurísticas (no ML) en esta primera versión.
  3. La asignación automática es round-robin ponderado por carga del comercial.
  4. No hay SSO; autenticación por JWT con refresh token (30 días).
  5. Los leads se importan desde CSV; la integración con CRM externo es fuera de scope.

→ Corrígeme ahora o procederé con estos supuestos.

1
Objetivo
Qué construimos, para quién y cómo medir el éxito
Producto
Dashboard de Leads
Panel web B2B para equipos de ventas
Usuario principal
Comercial / SDR
También: Manager de ventas (rol admin)

Queremos que un comercial pueda ver sus leads asignados, su temperatura (scoring 0–100) y tomar acción (contactar, descartar, reabrir) sin salir del dashboard. El manager puede ver el estado de todo el equipo y reasignar manualmente.

Requisito original vago: "Panel para que los comerciales vean cuántos leads han generado y si están calientes o no."

Reencuadrado como criterios medibles:

  • Dashboard carga en < 2 s (LCP) en conexión 4G
  • Score visible por lead con etiqueta Frío / Tibio / Caliente / Urgente
  • Asignación automática procesa nuevos leads en < 5 s tras importación
  • Ningún lead queda sin asignar cuando hay comerciales activos
2
Tech Stack
Frameworks, lenguajes y dependencias clave
Python 3.11 FastAPI 0.111 SQLAlchemy 2.0 Alembic PostgreSQL 14 React 18 TypeScript 5.4 Vite 5 TanStack Query v5 Tailwind CSS 3 Recharts 2 Pytest 8 Vitest Docker Compose GitHub Actions
3
Comandos
Comandos completos con flags, no solo nombres de herramientas
shell
# ── Backend (Python / FastAPI) ───────────────────────────────── # Instalar dependencias pip install -r requirements.txt # Desarrollo (hot-reload) uvicorn app.main:app --reload --port 8000 # Tests con cobertura pytest tests/ -v --cov=app --cov-report=html --cov-fail-under=80 # Lint + format ruff check . --fix && ruff format . # Migraciones alembic upgrade head # ── Frontend (React / TypeScript) ────────────────────────────── # Instalar npm install # Desarrollo npm run dev # Build producción npm run build && npm run preview # Tests unitarios npm run test -- --coverage --reporter=verbose # Lint npm run lint --fix # ── Docker ───────────────────────────────────────────────────── docker compose up -d --build docker compose -f docker-compose.test.yml run --rm api pytest
4
Estructura del Proyecto
Dónde vive cada tipo de archivo
leadpulse/ ├── backend/ │ ├── app/ │ │ ├── api/ ← routers FastAPI (leads, users, scoring) │ │ ├── core/ ← config, auth, deps │ │ ├── models/ ← modelos SQLAlchemy (Lead, User, Assignment) │ │ ├── schemas/ ← Pydantic v2 schemas (LeadCreate, LeadRead…) │ │ ├── services/ ← lógica de negocio (scoring_service, assign_service) │ │ └── main.py ← entrypoint FastAPI │ ├── alembic/ ← migraciones de BD │ ├── tests/ │ │ ├── unit/ ← tests de servicios aislados │ │ └── integration/ ← tests contra BD de test │ └── requirements.txt ├── frontend/ │ ├── src/ │ │ ├── components/ ← LeadCard, ScoreBadge, AssignModal… │ │ ├── pages/ ← Dashboard, LeadDetail, Settings │ │ ├── api/ ← hooks TanStack Query │ │ └── types/ ← tipos TypeScript compartidos │ └── package.json ├── docs/ │ └── SPEC.md ← este documento (en control de versiones) └── docker-compose.yml
5
Estilo de Código
Un snippet real vale más que tres párrafos descriptivos
python — ejemplo: scoring_service.py
from dataclasses import dataclass from app.models.lead import Lead # ✓ Constantes en SCREAMING_SNAKE_CASE y con comentario de unidad SCORE_MAX = 100 WEIGHT_RECENCY_DAYS = 0.4 # 40 % del score WEIGHT_PAGE_VIEWS = 0.35 # 35 % WEIGHT_EMAIL_OPENS = 0.25 # 25 % @dataclass class ScoringResult: score: int # 0–100 label: str # Frío / Tibio / Caliente / Urgente breakdown: dict[str, float] def score_lead(lead: Lead) -> ScoringResult: """Devuelve ScoringResult para un Lead dado. Raises: ValueError: si lead.id es None (lead sin persistir). """ if lead.id is None: raise ValueError("Lead debe estar persistido antes de calcular score") recency = _recency_score(lead.last_activity_at) pageview = min(lead.page_views / 20, 1.0) # normaliza a 20 visitas email = min(lead.email_opens / 5, 1.0) # normaliza a 5 aperturas raw = (recency * WEIGHT_RECENCY_DAYS + pageview * WEIGHT_PAGE_VIEWS + email * WEIGHT_EMAIL_OPENS) score = round(raw * SCORE_MAX) return ScoringResult( score=score, label=_label(score), breakdown={"recency": recency, "pageview": pageview, "email": email}, )

Convenciones: snake_case para Python, PascalCase para clases/componentes React, camelCase para variables JS/TS. Imports ordenados por ruff. Sin any en TypeScript salvo excepciones documentadas.

6
Estrategia de Testing
Framework, ubicación, cobertura y niveles
Backend
Pytest 8 + HTTPX
Cobertura mínima: 80 % líneas
Frontend
Vitest + Testing Library
Cobertura mínima: 70 % ramas
python — ejemplo de test unitario del scoring
def test_score_lead_devuelve_urgente_cuando_actividad_reciente(lead_fixture): lead_fixture.last_activity_at = datetime.utcnow() # actividad HOY lead_fixture.page_views = 25 lead_fixture.email_opens = 6 resultado = score_lead(lead_fixture) assert resultado.score >= 80 assert resultado.label == "Urgente"

Pirámide: 60 % unitarios (lógica de scoring, asignación) → 30 % integración (API endpoints + BD) → 10 % E2E con Playwright (flujo crítico: importar CSV → ver leads asignados → cambiar estado).

7
Límites
Sistema de tres niveles para evitar decisiones unilaterales

Siempre

  • Ejecutar tests antes de cada commit
  • Validar inputs con Pydantic en la capa de schemas
  • Seguir convenciones de naming definidas
  • Documentar parámetros en funciones públicas
  • Actualizar SPEC.md si cambia decisión

Preguntar primero

  • Cambios en el esquema de BD (nueva migración)
  • Añadir dependencias externas nuevas
  • Cambiar la fórmula de scoring
  • Modificar la política de asignación
  • Tocar configuración de GitHub Actions

Nunca

  • Hacer commit de secretos o .env
  • Eliminar o saltarse tests fallidos
  • Editar directorios vendor o node_modules
  • Desplegar en producción sin aprobación
  • Cambiar roles de usuario sin revisión de seguridad
8
Criterios de Éxito
Condiciones específicas y testables que definen "terminado"
  • Dashboard lista los leads asignados al comercial autenticado con score y etiqueta visible E2E test
  • LCP del dashboard en conexión 4G simulada < 2.5 s
  • Importación de CSV de 1.000 leads procesada y asignada < 10 s
  • Score calculado correctamente para los 4 casos de la suite de scoring 100 % pytest
  • Ningún lead activo queda sin asignar cuando hay ≥1 comercial activo Integration test
  • Manager puede reasignar lead manualmente y comercial lo ve en < 2 s (WebSocket o polling) Manual QA
  • CI pasa en verde con cobertura ≥ 80 % backend y ≥ 70 % frontend GitHub Actions
9
Tareas — Fase 3 (extracto)
Cada tarea es completable en una sesión, con criterios de aceptación explícitos
  • T-01 · Modelo Lead y migración inicial
    Sprint 1
    Aceptación:alembic upgrade head sin errores; Lead creado en BD con todos los campos
    Verificar:pytest tests/integration/test_lead_model.py -v
    Archivos:app/models/lead.py · alembic/versions/001_leads.py
  • T-02 · Servicio de scoring (heurístico)
    Sprint 1
    Aceptación:4 casos de test pasan; score entre 0 y 100; etiquetas correctas
    Verificar:pytest tests/unit/test_scoring_service.py -v --cov=app/services/scoring
    Archivos:app/services/scoring_service.py · tests/unit/test_scoring_service.py
  • T-03 · Endpoint GET /leads (paginado + filtros)
    Sprint 2
    Aceptación:Responde 200 con cursor pagination; filtra por score_label y assigned_to
    Verificar:pytest tests/integration/test_leads_api.py -v
    Archivos:app/api/leads.py · app/schemas/lead.py · tests/integration/test_leads_api.py
  • T-04 · Componente LeadCard + ScoreBadge
    Sprint 2
    Aceptación:Renderiza score, etiqueta con color correcto y nombre del lead; accesible (role="article")
    Verificar:npm test -- LeadCard.test.tsx
    Archivos:src/components/LeadCard.tsx · src/components/ScoreBadge.tsx · LeadCard.test.tsx
10
Preguntas Abiertas
Necesitan respuesta humana antes de implementar las tareas afectadas
  • 1
    ¿Qué ocurre si todos los comerciales tienen carga máxima? — ¿El lead queda en cola de espera o se asigna igualmente al de menor carga? Afecta a T-05 (servicio de asignación).
  • 2
    ¿Se notifica al comercial por email cuando recibe un lead nuevo? — Necesitaría integración con servicio de correo (fuera de scope actual o dentro?).
  • 3
    ¿Cuántos días sin actividad definen un lead como "Frío"? — Actualmente asumimos 30 días. ¿Es correcto para el modelo de negocio de LeadPulse?