feat(crm): dominio backend (cuentas, contactos, prospectos, embudos, oportunidades, actividades)
- Nuevo schema `crm` con 7 tablas multi-tenant (TenantScopedMixin + soft delete) - Módulos FastAPI por dominio: models/dto/service/routes (patrón example) - Métricas del dashboard (KPIs + embudo por etapa) - Conversión de prospecto → cuenta/contacto/oportunidad (idempotente) - Movimiento de oportunidad entre etapas (Kanban) con estado/probabilidad derivados - 25 permisos registrados en PermissionRegistry - Migración Alembic con upgrade/downgrade completos - 24 tests de servicios (pytest) en verde Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
55
backend/tests/test_leads.py
Normal file
55
backend/tests/test_leads.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from api.v1.modules.crm.leads import service
|
||||
from api.v1.modules.crm.leads.dto import LeadConvert, LeadCreate
|
||||
from api.v1.modules.crm.pipelines import service as pipelines_service
|
||||
from api.v1.modules.crm.pipelines.dto import PipelineCreate, StageCreate
|
||||
|
||||
T, C = 1, 1
|
||||
|
||||
|
||||
def test_create_lead_defaults_to_new(db):
|
||||
lead = service.create_lead(db, LeadCreate(name="Prospecto X", company_name="XYZ SA"), T, C)
|
||||
assert lead.status == "new"
|
||||
|
||||
|
||||
def test_convert_lead_creates_account_contact_opportunity(db):
|
||||
pipeline = pipelines_service.create_pipeline(db, PipelineCreate(name="Ventas", is_default=True), T, C)
|
||||
stage = pipelines_service.create_stage(db, StageCreate(pipeline_id=pipeline.id, name="Prospecto"), T, C)
|
||||
|
||||
lead = service.create_lead(
|
||||
db,
|
||||
LeadCreate(
|
||||
name="Oportunidad IMMEX",
|
||||
company_name="Maquiladora del Norte",
|
||||
contact_name="María Pérez",
|
||||
email="maria@example.com",
|
||||
estimated_value=50000,
|
||||
),
|
||||
T, C,
|
||||
)
|
||||
result = service.convert_lead(
|
||||
db, lead.id, LeadConvert(create_opportunity=True, pipeline_id=pipeline.id, stage_id=stage.id), T, C
|
||||
)
|
||||
|
||||
assert result["account_id"] is not None
|
||||
assert result["contact_id"] is not None
|
||||
assert result["opportunity_id"] is not None
|
||||
assert result["lead"].status == "converted"
|
||||
assert result["lead"].converted_account_id == result["account_id"]
|
||||
|
||||
|
||||
def test_convert_is_idempotent(db):
|
||||
lead = service.create_lead(db, LeadCreate(name="P", company_name="Empresa"), T, C)
|
||||
first = service.convert_lead(db, lead.id, LeadConvert(create_opportunity=False), T, C)
|
||||
second = service.convert_lead(db, lead.id, LeadConvert(create_opportunity=False), T, C)
|
||||
assert first["account_id"] == second["account_id"]
|
||||
|
||||
|
||||
def test_convert_splits_contact_name(db):
|
||||
lead = service.create_lead(db, LeadCreate(name="P", contact_name="Juan Carlos Ramírez"), T, C)
|
||||
result = service.convert_lead(db, lead.id, LeadConvert(create_opportunity=False), T, C)
|
||||
|
||||
from api.v1.modules.crm.contacts import service as contacts_service
|
||||
|
||||
contact = contacts_service.get_contact(db, result["contact_id"], T, C)
|
||||
assert contact.first_name == "Juan"
|
||||
assert contact.last_name == "Carlos Ramírez"
|
||||
Reference in New Issue
Block a user