932 lines
34 KiB
YAML
932 lines
34 KiB
YAML
# TRACEABILITY-MGN-009.yaml
|
|
# Matriz de Trazabilidad - MGN-009: CRM Básico
|
|
# Fecha: 2025-11-24
|
|
# Versión: 1.0
|
|
|
|
module:
|
|
id: MGN-009
|
|
name: "CRM Básico"
|
|
description: "Sistema de gestión de relaciones con clientes: leads, oportunidades, pipeline, actividades y conversión"
|
|
priority: P1
|
|
story_points: 31
|
|
status: Diseñado
|
|
|
|
metadata:
|
|
total_rf: 5
|
|
total_et_backend: 5
|
|
total_et_frontend: 5
|
|
total_tables: 5
|
|
total_tests: 100
|
|
coverage: 100%
|
|
|
|
requirements:
|
|
- rf_id: RF-MGN-009-001
|
|
rf_title: "Gestión de Leads y Oportunidades"
|
|
rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-001-gestión-de-leads-y-oportunidades.md"
|
|
priority: P1
|
|
story_points: 8
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-001-gestión-de-leads-y-oportunidades.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/crm/leads
|
|
description: "Crear nuevo lead"
|
|
- method: GET
|
|
path: /api/v1/crm/leads
|
|
description: "Listar todos los leads"
|
|
- method: GET
|
|
path: /api/v1/crm/leads/:id
|
|
description: "Obtener lead por ID"
|
|
- method: PUT
|
|
path: /api/v1/crm/leads/:id
|
|
description: "Actualizar lead"
|
|
- method: DELETE
|
|
path: /api/v1/crm/leads/:id
|
|
description: "Eliminar lead (soft delete)"
|
|
services:
|
|
- name: "LeadService"
|
|
file: "src/modules/crm/services/lead.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- findOne
|
|
- update
|
|
- remove
|
|
- validateBusinessRules
|
|
controllers:
|
|
- name: "LeadController"
|
|
file: "src/modules/crm/controllers/lead.controller.ts"
|
|
dtos:
|
|
- name: "CreateLeadDto"
|
|
file: "src/modules/crm/dto/create-lead.dto.ts"
|
|
- name: "UpdateLeadDto"
|
|
file: "src/modules/crm/dto/update-lead.dto.ts"
|
|
- name: "LeadResponseDto"
|
|
file: "src/modules/crm/dto/lead-response.dto.ts"
|
|
- name: "FilterLeadDto"
|
|
file: "src/modules/crm/dto/filter-lead.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-001-gestión-de-leads-y-oportunidades.md"
|
|
routes:
|
|
- path: "/crm/leads"
|
|
component: "LeadsPage"
|
|
- path: "/crm/leads/create"
|
|
component: "CreateLeadPage"
|
|
- path: "/crm/leads/:id/edit"
|
|
component: "EditLeadPage"
|
|
- path: "/crm/leads/:id"
|
|
component: "ViewLeadPage"
|
|
components:
|
|
- name: "LeadsTable"
|
|
file: "src/widgets/leads-table/ui/LeadsTable.tsx"
|
|
type: widget
|
|
- name: "CreateLeadForm"
|
|
file: "src/features/create-lead/ui/CreateLeadForm.tsx"
|
|
type: feature
|
|
- name: "LeadCard"
|
|
file: "src/entities/lead/ui/LeadCard.tsx"
|
|
type: entity
|
|
- name: "LeadPage"
|
|
file: "src/pages/crm/LeadPage.tsx"
|
|
type: page
|
|
api_client:
|
|
- name: "leadApi"
|
|
file: "src/entities/lead/api/lead.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- create
|
|
- update
|
|
- delete
|
|
state_management:
|
|
- name: "useLeadStore"
|
|
file: "src/entities/lead/model/lead.store.ts"
|
|
type: zustand
|
|
- name: "useLeads"
|
|
file: "src/entities/lead/api/lead.queries.ts"
|
|
type: react-query
|
|
|
|
database_tables:
|
|
- schema: sales
|
|
table: leads
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE (soft)
|
|
indices:
|
|
- idx_leads_tenant_id
|
|
- idx_leads_stage_id
|
|
- idx_leads_user_id
|
|
- idx_leads_partner_id
|
|
rls_policy: tenant_isolation_leads
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/crm/services/lead.service.spec.ts"
|
|
test_cases:
|
|
- "should create lead with valid data"
|
|
- "should assign lead to user automatically"
|
|
- "should validate email format"
|
|
- "should find all leads for tenant"
|
|
- "should update lead successfully"
|
|
integration_tests:
|
|
- file: "test/crm/lead.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/crm/leads should create lead"
|
|
- "GET /api/v1/crm/leads should return all leads"
|
|
- "GET /api/v1/crm/leads/:id should return lead"
|
|
- "PUT /api/v1/crm/leads/:id should update lead"
|
|
- "DELETE /api/v1/crm/leads/:id should soft delete lead"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
- "should check permissions"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/leads-table/ui/LeadsTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with leads"
|
|
- "should handle pagination"
|
|
- "should filter by stage"
|
|
- file: "src/features/create-lead/ui/CreateLeadForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should submit valid form"
|
|
- "should show error messages"
|
|
e2e_tests:
|
|
- file: "e2e/crm/leads.spec.ts"
|
|
test_cases:
|
|
- "should create lead successfully"
|
|
- "should edit lead successfully"
|
|
- "should delete lead with confirmation"
|
|
- "should enforce permissions"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear leads con información de contacto"
|
|
status: Pending
|
|
test_reference: "test/crm/lead.controller.e2e-spec.ts:35"
|
|
- id: AC-002
|
|
description: "Leads se asignan automáticamente a usuarios según reglas"
|
|
status: Pending
|
|
test_reference: "src/modules/crm/services/lead.service.spec.ts:72"
|
|
- id: AC-003
|
|
description: "Sistema detecta y previene leads duplicados por email"
|
|
status: Pending
|
|
test_reference: "test/crm/lead.controller.e2e-spec.ts:108"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Email debe ser único por tenant (detectar duplicados)"
|
|
implementation: "src/modules/crm/services/lead.service.ts:validateEmail()"
|
|
test_reference: "src/modules/crm/services/lead.service.spec.ts:95"
|
|
- id: RN-002
|
|
description: "Lead debe tener al menos nombre y email o teléfono"
|
|
implementation: "src/modules/crm/dto/create-lead.dto.ts:@IsNotEmpty()"
|
|
test_reference: "src/modules/crm/services/lead.service.spec.ts:122"
|
|
- id: RN-003
|
|
description: "Lead sin asignar debe tener usuario por defecto (round-robin)"
|
|
implementation: "src/modules/crm/services/lead.service.ts:assignUser()"
|
|
test_reference: "src/modules/crm/services/lead.service.spec.ts:148"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-003-001
|
|
module_dependencies:
|
|
- MGN-001
|
|
- MGN-003
|
|
external_dependencies:
|
|
- name: "@nestjs/common"
|
|
version: "^10.0.0"
|
|
- name: "ant-design"
|
|
version: "^5.0.0"
|
|
|
|
- rf_id: RF-MGN-009-002
|
|
rf_title: "Pipeline de Ventas (Kanban)"
|
|
rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-002-pipeline-de-ventas-kanban.md"
|
|
priority: P1
|
|
story_points: 8
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-002-pipeline-de-ventas-kanban.md"
|
|
endpoints:
|
|
- method: GET
|
|
path: /api/v1/crm/pipeline
|
|
description: "Obtener pipeline con stages y oportunidades"
|
|
- method: POST
|
|
path: /api/v1/crm/stages
|
|
description: "Crear nuevo stage"
|
|
- method: PUT
|
|
path: /api/v1/crm/stages/:id
|
|
description: "Actualizar stage"
|
|
- method: POST
|
|
path: /api/v1/crm/opportunities/:id/move
|
|
description: "Mover oportunidad a otro stage"
|
|
- method: GET
|
|
path: /api/v1/crm/opportunities
|
|
description: "Listar oportunidades"
|
|
services:
|
|
- name: "PipelineService"
|
|
file: "src/modules/crm/services/pipeline.service.ts"
|
|
methods:
|
|
- getPipeline
|
|
- createStage
|
|
- updateStage
|
|
- moveOpportunity
|
|
- calculateMetrics
|
|
controllers:
|
|
- name: "PipelineController"
|
|
file: "src/modules/crm/controllers/pipeline.controller.ts"
|
|
dtos:
|
|
- name: "CreateStageDto"
|
|
file: "src/modules/crm/dto/create-stage.dto.ts"
|
|
- name: "MoveOpportunityDto"
|
|
file: "src/modules/crm/dto/move-opportunity.dto.ts"
|
|
- name: "PipelineResponseDto"
|
|
file: "src/modules/crm/dto/pipeline-response.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-002-pipeline-de-ventas-kanban.md"
|
|
routes:
|
|
- path: "/crm/pipeline"
|
|
component: "PipelinePage"
|
|
- path: "/crm/opportunities/:id"
|
|
component: "OpportunityPage"
|
|
components:
|
|
- name: "PipelineKanban"
|
|
file: "src/widgets/pipeline-kanban/ui/PipelineKanban.tsx"
|
|
type: widget
|
|
- name: "OpportunityCard"
|
|
file: "src/entities/opportunity/ui/OpportunityCard.tsx"
|
|
type: entity
|
|
- name: "StageColumn"
|
|
file: "src/features/pipeline-stage/ui/StageColumn.tsx"
|
|
type: feature
|
|
- name: "PipelinePage"
|
|
file: "src/pages/crm/PipelinePage.tsx"
|
|
type: page
|
|
api_client:
|
|
- name: "pipelineApi"
|
|
file: "src/entities/pipeline/api/pipeline.api.ts"
|
|
methods:
|
|
- getPipeline
|
|
- createStage
|
|
- updateStage
|
|
- moveOpportunity
|
|
state_management:
|
|
- name: "usePipelineStore"
|
|
file: "src/entities/pipeline/model/pipeline.store.ts"
|
|
type: zustand
|
|
- name: "usePipeline"
|
|
file: "src/entities/pipeline/api/pipeline.queries.ts"
|
|
type: react-query
|
|
|
|
database_tables:
|
|
- schema: sales
|
|
table: pipeline_stages
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE (soft)
|
|
indices:
|
|
- idx_pipeline_stages_tenant_id
|
|
- idx_pipeline_stages_sequence
|
|
rls_policy: tenant_isolation_pipeline_stages
|
|
- schema: sales
|
|
table: opportunities
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
indices:
|
|
- idx_opportunities_tenant_id
|
|
- idx_opportunities_stage_id
|
|
- idx_opportunities_user_id
|
|
rls_policy: tenant_isolation_opportunities
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/crm/services/pipeline.service.spec.ts"
|
|
test_cases:
|
|
- "should get pipeline with stages and opportunities"
|
|
- "should create new stage with sequence"
|
|
- "should move opportunity between stages"
|
|
- "should calculate pipeline metrics"
|
|
- "should update stage order"
|
|
integration_tests:
|
|
- file: "test/crm/pipeline.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "GET /api/v1/crm/pipeline should return pipeline"
|
|
- "POST /api/v1/crm/stages should create stage"
|
|
- "PUT /api/v1/crm/stages/:id should update stage"
|
|
- "POST /api/v1/crm/opportunities/:id/move should move opportunity"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
- "should check permissions"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/pipeline-kanban/ui/PipelineKanban.test.tsx"
|
|
test_cases:
|
|
- "should render kanban board with stages"
|
|
- "should drag and drop opportunities"
|
|
- "should show stage metrics"
|
|
- file: "src/features/pipeline-stage/ui/StageColumn.test.tsx"
|
|
test_cases:
|
|
- "should render stage column"
|
|
- "should handle drop event"
|
|
- "should show opportunities count"
|
|
e2e_tests:
|
|
- file: "e2e/crm/pipeline.spec.ts"
|
|
test_cases:
|
|
- "should display pipeline kanban"
|
|
- "should drag opportunity to new stage"
|
|
- "should create new stage"
|
|
- "should enforce permissions"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede visualizar pipeline en formato Kanban"
|
|
status: Pending
|
|
test_reference: "test/crm/pipeline.controller.e2e-spec.ts:42"
|
|
- id: AC-002
|
|
description: "Oportunidades se mueven entre stages con drag & drop"
|
|
status: Pending
|
|
test_reference: "src/widgets/pipeline-kanban/ui/PipelineKanban.test.tsx:78"
|
|
- id: AC-003
|
|
description: "Sistema calcula métricas por stage (cantidad, valor total)"
|
|
status: Pending
|
|
test_reference: "src/modules/crm/services/pipeline.service.spec.ts:115"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Stages deben tener secuencia única y ordenada"
|
|
implementation: "src/modules/crm/services/pipeline.service.ts:validateSequence()"
|
|
test_reference: "src/modules/crm/services/pipeline.service.spec.ts:138"
|
|
- id: RN-002
|
|
description: "Al mover oportunidad, se registra actividad automáticamente"
|
|
implementation: "src/modules/crm/services/pipeline.service.ts:moveOpportunity()"
|
|
test_reference: "src/modules/crm/services/pipeline.service.spec.ts:165"
|
|
- id: RN-003
|
|
description: "Pipeline calcula tasa de conversión entre stages"
|
|
implementation: "src/modules/crm/services/pipeline.service.ts:calculateMetrics()"
|
|
test_reference: "src/modules/crm/services/pipeline.service.spec.ts:192"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-009-001
|
|
module_dependencies:
|
|
- MGN-001
|
|
external_dependencies:
|
|
- name: "@dnd-kit/core"
|
|
version: "^6.0.0"
|
|
- name: "@dnd-kit/sortable"
|
|
version: "^7.0.0"
|
|
|
|
- rf_id: RF-MGN-009-003
|
|
rf_title: "Actividades y Seguimiento"
|
|
rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-003-actividades-y-seguimiento.md"
|
|
priority: P1
|
|
story_points: 5
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-003-actividades-y-seguimiento.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/crm/activities
|
|
description: "Crear nueva actividad"
|
|
- method: GET
|
|
path: /api/v1/crm/activities
|
|
description: "Listar todas las actividades"
|
|
- method: GET
|
|
path: /api/v1/crm/activities/:id
|
|
description: "Obtener actividad por ID"
|
|
- method: PUT
|
|
path: /api/v1/crm/activities/:id
|
|
description: "Actualizar actividad"
|
|
- method: POST
|
|
path: /api/v1/crm/activities/:id/complete
|
|
description: "Marcar actividad como completada"
|
|
services:
|
|
- name: "ActivityService"
|
|
file: "src/modules/crm/services/activity.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- findOne
|
|
- update
|
|
- complete
|
|
- validateBusinessRules
|
|
controllers:
|
|
- name: "ActivityController"
|
|
file: "src/modules/crm/controllers/activity.controller.ts"
|
|
dtos:
|
|
- name: "CreateActivityDto"
|
|
file: "src/modules/crm/dto/create-activity.dto.ts"
|
|
- name: "UpdateActivityDto"
|
|
file: "src/modules/crm/dto/update-activity.dto.ts"
|
|
- name: "ActivityResponseDto"
|
|
file: "src/modules/crm/dto/activity-response.dto.ts"
|
|
- name: "FilterActivityDto"
|
|
file: "src/modules/crm/dto/filter-activity.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-003-actividades-y-seguimiento.md"
|
|
routes:
|
|
- path: "/crm/activities"
|
|
component: "ActivitiesPage"
|
|
- path: "/crm/activities/create"
|
|
component: "CreateActivityPage"
|
|
- path: "/crm/activities/:id"
|
|
component: "ViewActivityPage"
|
|
components:
|
|
- name: "ActivitiesTable"
|
|
file: "src/widgets/activities-table/ui/ActivitiesTable.tsx"
|
|
type: widget
|
|
- name: "CreateActivityForm"
|
|
file: "src/features/create-activity/ui/CreateActivityForm.tsx"
|
|
type: feature
|
|
- name: "ActivityCard"
|
|
file: "src/entities/activity/ui/ActivityCard.tsx"
|
|
type: entity
|
|
- name: "ActivityPage"
|
|
file: "src/pages/crm/ActivityPage.tsx"
|
|
type: page
|
|
api_client:
|
|
- name: "activityApi"
|
|
file: "src/entities/activity/api/activity.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- create
|
|
- update
|
|
- complete
|
|
state_management:
|
|
- name: "useActivityStore"
|
|
file: "src/entities/activity/model/activity.store.ts"
|
|
type: zustand
|
|
- name: "useActivities"
|
|
file: "src/entities/activity/api/activity.queries.ts"
|
|
type: react-query
|
|
|
|
database_tables:
|
|
- schema: sales
|
|
table: activities
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE (soft)
|
|
indices:
|
|
- idx_activities_tenant_id
|
|
- idx_activities_lead_id
|
|
- idx_activities_opportunity_id
|
|
- idx_activities_user_id
|
|
- idx_activities_due_date
|
|
rls_policy: tenant_isolation_activities
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/crm/services/activity.service.spec.ts"
|
|
test_cases:
|
|
- "should create activity with valid data"
|
|
- "should send notification on activity assignment"
|
|
- "should complete activity successfully"
|
|
- "should find overdue activities"
|
|
- "should update activity successfully"
|
|
integration_tests:
|
|
- file: "test/crm/activity.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/crm/activities should create activity"
|
|
- "GET /api/v1/crm/activities should return all activities"
|
|
- "GET /api/v1/crm/activities/:id should return activity"
|
|
- "PUT /api/v1/crm/activities/:id should update activity"
|
|
- "POST /api/v1/crm/activities/:id/complete should mark complete"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
- "should check permissions"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/activities-table/ui/ActivitiesTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with activities"
|
|
- "should filter by status"
|
|
- "should show overdue activities"
|
|
- file: "src/features/create-activity/ui/CreateActivityForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should submit valid form"
|
|
- "should show error messages"
|
|
e2e_tests:
|
|
- file: "e2e/crm/activities.spec.ts"
|
|
test_cases:
|
|
- "should create activity successfully"
|
|
- "should complete activity successfully"
|
|
- "should show overdue notifications"
|
|
- "should enforce permissions"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear actividades para leads y oportunidades"
|
|
status: Pending
|
|
test_reference: "test/crm/activity.controller.e2e-spec.ts:38"
|
|
- id: AC-002
|
|
description: "Sistema envía notificaciones de actividades pendientes"
|
|
status: Pending
|
|
test_reference: "src/modules/crm/services/activity.service.spec.ts:75"
|
|
- id: AC-003
|
|
description: "Actividades vencidas se resaltan en dashboard"
|
|
status: Pending
|
|
test_reference: "e2e/crm/activities.spec.ts:112"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Actividad debe estar asociada a lead u oportunidad"
|
|
implementation: "src/modules/crm/services/activity.service.ts:validateRelation()"
|
|
test_reference: "src/modules/crm/services/activity.service.spec.ts:98"
|
|
- id: RN-002
|
|
description: "Actividad vencida genera notificación automática"
|
|
implementation: "src/modules/crm/services/activity.service.ts:checkOverdue()"
|
|
test_reference: "src/modules/crm/services/activity.service.spec.ts:125"
|
|
- id: RN-003
|
|
description: "Al completar actividad, se puede sugerir siguiente acción"
|
|
implementation: "src/modules/crm/services/activity.service.ts:complete()"
|
|
test_reference: "src/modules/crm/services/activity.service.spec.ts:152"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-009-001
|
|
- RF-MGN-014-002
|
|
module_dependencies:
|
|
- MGN-001
|
|
- MGN-014
|
|
external_dependencies:
|
|
- name: "@nestjs/schedule"
|
|
version: "^4.0.0"
|
|
|
|
- rf_id: RF-MGN-009-004
|
|
rf_title: "Lead Scoring y Calificación"
|
|
rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-004-lead-scoring-y-calificación.md"
|
|
priority: P2
|
|
story_points: 5
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-004-lead-scoring-y-calificación.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/crm/scoring-rules
|
|
description: "Crear regla de scoring"
|
|
- method: GET
|
|
path: /api/v1/crm/scoring-rules
|
|
description: "Listar reglas de scoring"
|
|
- method: PUT
|
|
path: /api/v1/crm/scoring-rules/:id
|
|
description: "Actualizar regla"
|
|
- method: POST
|
|
path: /api/v1/crm/leads/:id/calculate-score
|
|
description: "Calcular score de lead"
|
|
- method: POST
|
|
path: /api/v1/crm/leads/:id/qualify
|
|
description: "Calificar lead"
|
|
services:
|
|
- name: "LeadScoringService"
|
|
file: "src/modules/crm/services/lead-scoring.service.ts"
|
|
methods:
|
|
- createRule
|
|
- updateRule
|
|
- calculateScore
|
|
- qualifyLead
|
|
- autoAssignByScore
|
|
controllers:
|
|
- name: "LeadScoringController"
|
|
file: "src/modules/crm/controllers/lead-scoring.controller.ts"
|
|
dtos:
|
|
- name: "CreateScoringRuleDto"
|
|
file: "src/modules/crm/dto/create-scoring-rule.dto.ts"
|
|
- name: "QualifyLeadDto"
|
|
file: "src/modules/crm/dto/qualify-lead.dto.ts"
|
|
- name: "LeadScoreResponseDto"
|
|
file: "src/modules/crm/dto/lead-score-response.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-004-lead-scoring-y-calificación.md"
|
|
routes:
|
|
- path: "/crm/scoring-rules"
|
|
component: "ScoringRulesPage"
|
|
- path: "/crm/leads/:id/scoring"
|
|
component: "LeadScoringPage"
|
|
components:
|
|
- name: "ScoringRulesTable"
|
|
file: "src/widgets/scoring-rules-table/ui/ScoringRulesTable.tsx"
|
|
type: widget
|
|
- name: "LeadScoreCard"
|
|
file: "src/features/lead-scoring/ui/LeadScoreCard.tsx"
|
|
type: feature
|
|
- name: "QualifyLeadButton"
|
|
file: "src/entities/lead/ui/QualifyLeadButton.tsx"
|
|
type: entity
|
|
- name: "ScoringRulesPage"
|
|
file: "src/pages/crm/ScoringRulesPage.tsx"
|
|
type: page
|
|
api_client:
|
|
- name: "leadScoringApi"
|
|
file: "src/entities/lead-scoring/api/lead-scoring.api.ts"
|
|
methods:
|
|
- getRules
|
|
- createRule
|
|
- updateRule
|
|
- calculateScore
|
|
- qualify
|
|
state_management:
|
|
- name: "useLeadScoringStore"
|
|
file: "src/entities/lead-scoring/model/lead-scoring.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: sales
|
|
table: leads
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- UPDATE
|
|
indices:
|
|
- idx_leads_score
|
|
- idx_leads_qualified
|
|
rls_policy: tenant_isolation_leads
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/crm/services/lead-scoring.service.spec.ts"
|
|
test_cases:
|
|
- "should calculate lead score based on rules"
|
|
- "should qualify lead with high score"
|
|
- "should auto-assign leads by score threshold"
|
|
- "should create scoring rule"
|
|
- "should update score on lead changes"
|
|
integration_tests:
|
|
- file: "test/crm/lead-scoring.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/crm/scoring-rules should create rule"
|
|
- "GET /api/v1/crm/scoring-rules should return rules"
|
|
- "POST /api/v1/crm/leads/:id/calculate-score should calculate"
|
|
- "POST /api/v1/crm/leads/:id/qualify should qualify lead"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
- "should check permissions"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/scoring-rules-table/ui/ScoringRulesTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with rules"
|
|
- "should handle rule activation"
|
|
- "should show score breakdown"
|
|
- file: "src/features/lead-scoring/ui/LeadScoreCard.test.tsx"
|
|
test_cases:
|
|
- "should display score with visual indicator"
|
|
- "should show scoring factors"
|
|
- "should suggest qualification"
|
|
e2e_tests:
|
|
- file: "e2e/crm/lead-scoring.spec.ts"
|
|
test_cases:
|
|
- "should calculate lead score"
|
|
- "should qualify lead manually"
|
|
- "should auto-qualify by threshold"
|
|
- "should enforce permissions"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Sistema calcula score automáticamente basado en reglas"
|
|
status: Pending
|
|
test_reference: "test/crm/lead-scoring.controller.e2e-spec.ts:45"
|
|
- id: AC-002
|
|
description: "Leads con score alto se califican automáticamente"
|
|
status: Pending
|
|
test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:82"
|
|
- id: AC-003
|
|
description: "Usuarios pueden ver breakdown de score por factor"
|
|
status: Pending
|
|
test_reference: "src/features/lead-scoring/ui/LeadScoreCard.test.tsx:108"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Score se calcula sumando puntos de reglas activas"
|
|
implementation: "src/modules/crm/services/lead-scoring.service.ts:calculateScore()"
|
|
test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:105"
|
|
- id: RN-002
|
|
description: "Lead con score >= 70 se califica como 'hot'"
|
|
implementation: "src/modules/crm/services/lead-scoring.service.ts:qualifyByScore()"
|
|
test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:132"
|
|
- id: RN-003
|
|
description: "Score se recalcula automáticamente al actualizar lead"
|
|
implementation: "src/modules/crm/services/lead.service.ts:update()"
|
|
test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:158"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-009-001
|
|
module_dependencies:
|
|
- MGN-001
|
|
external_dependencies:
|
|
- name: "@nestjs/common"
|
|
version: "^10.0.0"
|
|
|
|
- rf_id: RF-MGN-009-005
|
|
rf_title: "Conversión a Cotización"
|
|
rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-005-conversión-a-cotización.md"
|
|
priority: P1
|
|
story_points: 5
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-005-conversión-a-cotización.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/crm/opportunities/:id/convert
|
|
description: "Convertir oportunidad a cotización"
|
|
- method: POST
|
|
path: /api/v1/crm/leads/:id/convert
|
|
description: "Convertir lead a oportunidad"
|
|
- method: POST
|
|
path: /api/v1/crm/opportunities/:id/mark-won
|
|
description: "Marcar oportunidad como ganada"
|
|
- method: POST
|
|
path: /api/v1/crm/opportunities/:id/mark-lost
|
|
description: "Marcar oportunidad como perdida"
|
|
services:
|
|
- name: "ConversionService"
|
|
file: "src/modules/crm/services/conversion.service.ts"
|
|
methods:
|
|
- convertToOpportunity
|
|
- convertToQuotation
|
|
- markWon
|
|
- markLost
|
|
- createPartner
|
|
controllers:
|
|
- name: "ConversionController"
|
|
file: "src/modules/crm/controllers/conversion.controller.ts"
|
|
dtos:
|
|
- name: "ConvertLeadDto"
|
|
file: "src/modules/crm/dto/convert-lead.dto.ts"
|
|
- name: "ConvertOpportunityDto"
|
|
file: "src/modules/crm/dto/convert-opportunity.dto.ts"
|
|
- name: "MarkLostDto"
|
|
file: "src/modules/crm/dto/mark-lost.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-005-conversión-a-cotización.md"
|
|
routes:
|
|
- path: "/crm/leads/:id/convert"
|
|
component: "ConvertLeadPage"
|
|
- path: "/crm/opportunities/:id/convert"
|
|
component: "ConvertOpportunityPage"
|
|
components:
|
|
- name: "ConvertLeadButton"
|
|
file: "src/features/convert-lead/ui/ConvertLeadButton.tsx"
|
|
type: feature
|
|
- name: "ConvertOpportunityButton"
|
|
file: "src/features/convert-opportunity/ui/ConvertOpportunityButton.tsx"
|
|
type: feature
|
|
- name: "WonLostButtons"
|
|
file: "src/widgets/won-lost-buttons/ui/WonLostButtons.tsx"
|
|
type: widget
|
|
- name: "ConversionPage"
|
|
file: "src/pages/crm/ConversionPage.tsx"
|
|
type: page
|
|
api_client:
|
|
- name: "conversionApi"
|
|
file: "src/entities/conversion/api/conversion.api.ts"
|
|
methods:
|
|
- convertLead
|
|
- convertOpportunity
|
|
- markWon
|
|
- markLost
|
|
state_management:
|
|
- name: "useConversionStore"
|
|
file: "src/entities/conversion/model/conversion.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: sales
|
|
table: opportunities
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
indices:
|
|
- idx_opportunities_lead_id
|
|
- idx_opportunities_won_date
|
|
rls_policy: tenant_isolation_opportunities
|
|
- schema: sales
|
|
table: lost_reasons
|
|
file: "database-design/schemas/sales-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
indices:
|
|
- idx_lost_reasons_tenant_id
|
|
rls_policy: tenant_isolation_lost_reasons
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/crm/services/conversion.service.spec.ts"
|
|
test_cases:
|
|
- "should convert lead to opportunity"
|
|
- "should convert opportunity to quotation"
|
|
- "should mark opportunity as won and create quotation"
|
|
- "should mark opportunity as lost with reason"
|
|
- "should create partner from lead if not exists"
|
|
integration_tests:
|
|
- file: "test/crm/conversion.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/crm/leads/:id/convert should convert lead"
|
|
- "POST /api/v1/crm/opportunities/:id/convert should convert opportunity"
|
|
- "POST /api/v1/crm/opportunities/:id/mark-won should mark won"
|
|
- "POST /api/v1/crm/opportunities/:id/mark-lost should mark lost"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
- "should check permissions"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/features/convert-lead/ui/ConvertLeadButton.test.tsx"
|
|
test_cases:
|
|
- "should show conversion dialog"
|
|
- "should call convert API"
|
|
- "should redirect to opportunity"
|
|
- file: "src/widgets/won-lost-buttons/ui/WonLostButtons.test.tsx"
|
|
test_cases:
|
|
- "should mark opportunity as won"
|
|
- "should mark opportunity as lost with reason"
|
|
- "should show confirmation dialog"
|
|
e2e_tests:
|
|
- file: "e2e/crm/conversion.spec.ts"
|
|
test_cases:
|
|
- "should convert lead to opportunity"
|
|
- "should convert opportunity to quotation"
|
|
- "should mark won and create quotation"
|
|
- "should enforce permissions"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede convertir lead calificado a oportunidad"
|
|
status: Pending
|
|
test_reference: "test/crm/conversion.controller.e2e-spec.ts:42"
|
|
- id: AC-002
|
|
description: "Oportunidad ganada crea automáticamente cotización"
|
|
status: Pending
|
|
test_reference: "src/modules/crm/services/conversion.service.spec.ts:78"
|
|
- id: AC-003
|
|
description: "Sistema solicita razón al marcar oportunidad como perdida"
|
|
status: Pending
|
|
test_reference: "test/crm/conversion.controller.e2e-spec.ts:115"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Solo leads calificados pueden convertirse a oportunidad"
|
|
implementation: "src/modules/crm/services/conversion.service.ts:validateLeadQualified()"
|
|
test_reference: "src/modules/crm/services/conversion.service.spec.ts:102"
|
|
- id: RN-002
|
|
description: "Al marcar como ganada, se crea cotización automáticamente"
|
|
implementation: "src/modules/crm/services/conversion.service.ts:markWon()"
|
|
test_reference: "src/modules/crm/services/conversion.service.spec.ts:128"
|
|
- id: RN-003
|
|
description: "Oportunidad perdida debe tener razón registrada"
|
|
implementation: "src/modules/crm/services/conversion.service.ts:markLost()"
|
|
test_reference: "src/modules/crm/services/conversion.service.spec.ts:155"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-009-001
|
|
- RF-MGN-009-002
|
|
- RF-MGN-007-001
|
|
module_dependencies:
|
|
- MGN-001
|
|
- MGN-007
|
|
external_dependencies:
|
|
- name: "@nestjs/common"
|
|
version: "^10.0.0"
|
|
|
|
coverage:
|
|
rf_to_et_backend: 100%
|
|
rf_to_et_frontend: 100%
|
|
rf_to_database: 100%
|
|
rf_to_tests: 100%
|
|
backend_tests: 100%
|
|
frontend_tests: 100%
|
|
|
|
statistics:
|
|
total_endpoints: 25
|
|
total_components: 20
|
|
total_tables: 5
|
|
total_test_cases: 100
|
|
estimated_duration_sprints: 2
|