erp-core/docs/04-modelado/trazabilidad/TRACEABILITY-MGN-009.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