999 lines
37 KiB
YAML
999 lines
37 KiB
YAML
# TRACEABILITY-MGN-003.yaml
|
|
# Matriz de Trazabilidad - MGN-003: Catálogos Maestros
|
|
# Fecha: 2025-11-24
|
|
# Versión: 1.0
|
|
|
|
module:
|
|
id: MGN-003
|
|
name: "Catálogos Maestros"
|
|
description: "Partners universales, países, monedas, unidades de medida, categorías y términos de pago"
|
|
priority: P0
|
|
story_points: 29
|
|
status: Diseñado
|
|
|
|
metadata:
|
|
total_rf: 6
|
|
total_et_backend: 6
|
|
total_et_frontend: 6
|
|
total_tables: 9
|
|
total_tests: 120
|
|
coverage: 100%
|
|
|
|
requirements:
|
|
- rf_id: RF-MGN-003-001
|
|
rf_title: "Gestión de Partners Universales"
|
|
rf_file: "requerimientos-funcionales/mgn-003/RF-MGN-003-001-gestion-partners.md"
|
|
priority: P0
|
|
story_points: 8
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-003/ET-BACKEND-MGN-003-001-gestión-de-partners-universales.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/partners
|
|
description: "Crear partner (cliente, proveedor o ambos)"
|
|
- method: GET
|
|
path: /api/v1/partners
|
|
description: "Listar partners con filtros"
|
|
- method: GET
|
|
path: /api/v1/partners/:id
|
|
description: "Obtener partner por ID"
|
|
- method: PUT
|
|
path: /api/v1/partners/:id
|
|
description: "Actualizar partner"
|
|
- method: DELETE
|
|
path: /api/v1/partners/:id
|
|
description: "Desactivar partner (soft delete)"
|
|
services:
|
|
- name: "PartnerService"
|
|
file: "src/modules/partners/services/partner.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- findOne
|
|
- update
|
|
- remove
|
|
controllers:
|
|
- name: "PartnerController"
|
|
file: "src/modules/partners/controllers/partner.controller.ts"
|
|
dtos:
|
|
- name: "CreatePartnerDto"
|
|
file: "src/modules/partners/dto/create-partner.dto.ts"
|
|
- name: "UpdatePartnerDto"
|
|
file: "src/modules/partners/dto/update-partner.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-003/ET-FRONTEND-MGN-003-001-gestión-de-partners-universales.md"
|
|
routes:
|
|
- path: "/partners"
|
|
component: "PartnersPage"
|
|
- path: "/partners/create"
|
|
component: "CreatePartnerPage"
|
|
- path: "/partners/:id/edit"
|
|
component: "EditPartnerPage"
|
|
- path: "/partners/:id"
|
|
component: "ViewPartnerPage"
|
|
components:
|
|
- name: "PartnersTable"
|
|
file: "src/widgets/partners-table/ui/PartnersTable.tsx"
|
|
type: widget
|
|
- name: "CreatePartnerForm"
|
|
file: "src/features/create-partner/ui/CreatePartnerForm.tsx"
|
|
type: feature
|
|
- name: "PartnerCard"
|
|
file: "src/entities/partner/ui/PartnerCard.tsx"
|
|
type: entity
|
|
api_client:
|
|
- name: "partnerApi"
|
|
file: "src/entities/partner/api/partner.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- create
|
|
- update
|
|
- delete
|
|
state_management:
|
|
- name: "usePartnerStore"
|
|
file: "src/entities/partner/model/partner.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: core
|
|
table: partners
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE (soft)
|
|
indices:
|
|
- idx_partners_tenant_id
|
|
- idx_partners_company_id
|
|
- idx_partners_tax_id
|
|
- idx_partners_type
|
|
rls_policy: tenant_isolation_partners
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/partners/services/partner.service.spec.ts"
|
|
test_cases:
|
|
- "should create partner with valid data"
|
|
- "should create customer partner"
|
|
- "should create supplier partner"
|
|
- "should create partner as both customer and supplier"
|
|
- "should throw error when tax_id already exists"
|
|
- "should find all partners for tenant"
|
|
- "should update partner successfully"
|
|
- "should soft delete partner"
|
|
integration_tests:
|
|
- file: "test/partners/partner.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/partners should create partner"
|
|
- "GET /api/v1/partners should return all partners"
|
|
- "GET /api/v1/partners/:id should return partner"
|
|
- "PUT /api/v1/partners/:id should update partner"
|
|
- "DELETE /api/v1/partners/:id should soft delete partner"
|
|
- "should filter partners by type (customer/supplier)"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/partners-table/ui/PartnersTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with partners"
|
|
- "should handle pagination"
|
|
- "should filter by type"
|
|
- file: "src/features/create-partner/ui/CreatePartnerForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should validate tax_id format"
|
|
- "should submit valid form"
|
|
e2e_tests:
|
|
- file: "e2e/partners/partners.spec.ts"
|
|
test_cases:
|
|
- "should create partner successfully"
|
|
- "should edit partner successfully"
|
|
- "should delete partner with confirmation"
|
|
- "should filter partners by type"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear partners como cliente, proveedor o ambos"
|
|
status: Pending
|
|
test_reference: "test/partners/partner.controller.e2e-spec.ts:28"
|
|
- id: AC-002
|
|
description: "Sistema valida que tax_id sea único por tenant"
|
|
status: Pending
|
|
test_reference: "src/modules/partners/services/partner.service.spec.ts:65"
|
|
- id: AC-003
|
|
description: "Partner puede tener múltiples direcciones y contactos"
|
|
status: Pending
|
|
test_reference: "test/partners/partner.controller.e2e-spec.ts:95"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Partner puede ser cliente, proveedor o ambos (is_customer, is_supplier)"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:partners table"
|
|
test_reference: "src/modules/partners/services/partner.service.spec.ts:48"
|
|
- id: RN-002
|
|
description: "Tax ID debe ser único por tenant (no globalmente)"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:CONSTRAINT uq_partners_tax_id_tenant"
|
|
test_reference: "src/modules/partners/services/partner.service.spec.ts:78"
|
|
- id: RN-003
|
|
description: "Empresa también es partner (is_company=true)"
|
|
implementation: "src/modules/companies/services/company.service.ts:create()"
|
|
test_reference: "src/modules/partners/services/partner.service.spec.ts:105"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-001-001
|
|
- RF-MGN-003-002
|
|
module_dependencies:
|
|
- MGN-001
|
|
external_dependencies: []
|
|
|
|
- rf_id: RF-MGN-003-002
|
|
rf_title: "Gestión de Países y Regiones"
|
|
rf_file: "requerimientos-funcionales/mgn-003/RF-MGN-003-002-gestión-de-países-y-regiones.md"
|
|
priority: P0
|
|
story_points: 3
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-003/ET-BACKEND-MGN-003-002-gestión-de-países-y-regiones.md"
|
|
endpoints:
|
|
- method: GET
|
|
path: /api/v1/countries
|
|
description: "Listar países"
|
|
- method: GET
|
|
path: /api/v1/countries/:id
|
|
description: "Obtener país por ID"
|
|
- method: GET
|
|
path: /api/v1/countries/:id/states
|
|
description: "Obtener estados/provincias de país"
|
|
services:
|
|
- name: "CountryService"
|
|
file: "src/modules/catalogs/services/country.service.ts"
|
|
methods:
|
|
- findAll
|
|
- findOne
|
|
- findStates
|
|
controllers:
|
|
- name: "CountryController"
|
|
file: "src/modules/catalogs/controllers/country.controller.ts"
|
|
dtos: []
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-003/ET-FRONTEND-MGN-003-002-gestión-de-países-y-regiones.md"
|
|
routes:
|
|
- path: "/catalogs/countries"
|
|
component: "CountriesPage"
|
|
components:
|
|
- name: "CountriesTable"
|
|
file: "src/widgets/countries-table/ui/CountriesTable.tsx"
|
|
type: widget
|
|
- name: "CountrySelector"
|
|
file: "src/shared/ui/CountrySelector.tsx"
|
|
type: shared
|
|
api_client:
|
|
- name: "countryApi"
|
|
file: "src/entities/country/api/country.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- getStates
|
|
state_management:
|
|
- name: "useCountryStore"
|
|
file: "src/entities/country/model/country.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: core
|
|
table: countries
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
indices:
|
|
- idx_countries_code
|
|
rls_policy: null
|
|
- schema: core
|
|
table: states
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
indices:
|
|
- idx_states_country_id
|
|
rls_policy: null
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/catalogs/services/country.service.spec.ts"
|
|
test_cases:
|
|
- "should find all countries"
|
|
- "should find country by id"
|
|
- "should find states by country"
|
|
integration_tests:
|
|
- file: "test/catalogs/country.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "GET /api/v1/countries should return all countries"
|
|
- "GET /api/v1/countries/:id should return country"
|
|
- "GET /api/v1/countries/:id/states should return states"
|
|
- "should not require authentication for read"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/countries-table/ui/CountriesTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with countries"
|
|
- "should show country details"
|
|
e2e_tests:
|
|
- file: "e2e/catalogs/countries.spec.ts"
|
|
test_cases:
|
|
- "should list countries successfully"
|
|
- "should select country in selector"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Sistema provee catálogo de países ISO 3166"
|
|
status: Pending
|
|
test_reference: "test/catalogs/country.controller.e2e-spec.ts:22"
|
|
- id: AC-002
|
|
description: "Sistema provee estados/provincias por país"
|
|
status: Pending
|
|
test_reference: "test/catalogs/country.controller.e2e-spec.ts:48"
|
|
- id: AC-003
|
|
description: "Catálogo es precargado en base de datos"
|
|
status: Pending
|
|
test_reference: "database-design/schemas/core-schema-ddl.sql:countries seed"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Catálogo de países es global (no por tenant)"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:countries table"
|
|
test_reference: "src/modules/catalogs/services/country.service.spec.ts:38"
|
|
- id: RN-002
|
|
description: "Código de país sigue ISO 3166-1 alpha-2"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:countries.code"
|
|
test_reference: "src/modules/catalogs/services/country.service.spec.ts:52"
|
|
- id: RN-003
|
|
description: "Estados son opcionales (no todos los países tienen estados)"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:states table"
|
|
test_reference: "src/modules/catalogs/services/country.service.spec.ts:68"
|
|
|
|
dependencies:
|
|
rf_dependencies: []
|
|
module_dependencies: []
|
|
external_dependencies: []
|
|
|
|
- rf_id: RF-MGN-003-003
|
|
rf_title: "Gestión de Monedas y Tasas de Cambio"
|
|
rf_file: "requerimientos-funcionales/mgn-003/RF-MGN-003-003-gestión-de-monedas-y-tasas-de-cambio.md"
|
|
priority: P0
|
|
story_points: 5
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-003/ET-BACKEND-MGN-003-003-gestión-de-monedas-y-tasas-de-cambio.md"
|
|
endpoints:
|
|
- method: GET
|
|
path: /api/v1/currencies
|
|
description: "Listar monedas"
|
|
- method: GET
|
|
path: /api/v1/currencies/:id
|
|
description: "Obtener moneda por ID"
|
|
- method: POST
|
|
path: /api/v1/exchange-rates
|
|
description: "Crear tasa de cambio"
|
|
- method: GET
|
|
path: /api/v1/exchange-rates
|
|
description: "Listar tasas de cambio"
|
|
- method: PUT
|
|
path: /api/v1/exchange-rates/:id
|
|
description: "Actualizar tasa de cambio"
|
|
services:
|
|
- name: "CurrencyService"
|
|
file: "src/modules/catalogs/services/currency.service.ts"
|
|
methods:
|
|
- findAll
|
|
- findOne
|
|
- name: "ExchangeRateService"
|
|
file: "src/modules/catalogs/services/exchange-rate.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- update
|
|
- getRate
|
|
controllers:
|
|
- name: "CurrencyController"
|
|
file: "src/modules/catalogs/controllers/currency.controller.ts"
|
|
- name: "ExchangeRateController"
|
|
file: "src/modules/catalogs/controllers/exchange-rate.controller.ts"
|
|
dtos:
|
|
- name: "CreateExchangeRateDto"
|
|
file: "src/modules/catalogs/dto/create-exchange-rate.dto.ts"
|
|
- name: "UpdateExchangeRateDto"
|
|
file: "src/modules/catalogs/dto/update-exchange-rate.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-003/ET-FRONTEND-MGN-003-003-gestión-de-monedas-y-tasas-de-cambio.md"
|
|
routes:
|
|
- path: "/catalogs/currencies"
|
|
component: "CurrenciesPage"
|
|
- path: "/catalogs/exchange-rates"
|
|
component: "ExchangeRatesPage"
|
|
components:
|
|
- name: "CurrenciesTable"
|
|
file: "src/widgets/currencies-table/ui/CurrenciesTable.tsx"
|
|
type: widget
|
|
- name: "ExchangeRatesTable"
|
|
file: "src/widgets/exchange-rates-table/ui/ExchangeRatesTable.tsx"
|
|
type: widget
|
|
- name: "CreateExchangeRateForm"
|
|
file: "src/features/create-exchange-rate/ui/CreateExchangeRateForm.tsx"
|
|
type: feature
|
|
api_client:
|
|
- name: "currencyApi"
|
|
file: "src/entities/currency/api/currency.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- name: "exchangeRateApi"
|
|
file: "src/entities/exchange-rate/api/exchange-rate.api.ts"
|
|
methods:
|
|
- getAll
|
|
- create
|
|
- update
|
|
state_management:
|
|
- name: "useCurrencyStore"
|
|
file: "src/entities/currency/model/currency.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: core
|
|
table: currencies
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
indices:
|
|
- idx_currencies_code
|
|
rls_policy: null
|
|
- schema: core
|
|
table: exchange_rates
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
indices:
|
|
- idx_exchange_rates_from_to_date
|
|
- idx_exchange_rates_company_id
|
|
rls_policy: tenant_isolation_exchange_rates
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/catalogs/services/exchange-rate.service.spec.ts"
|
|
test_cases:
|
|
- "should create exchange rate"
|
|
- "should get rate for date"
|
|
- "should use latest rate if no date specified"
|
|
- "should calculate conversion"
|
|
- "should update exchange rate"
|
|
integration_tests:
|
|
- file: "test/catalogs/exchange-rate.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/exchange-rates should create rate"
|
|
- "GET /api/v1/exchange-rates should return all rates"
|
|
- "PUT /api/v1/exchange-rates/:id should update rate"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/exchange-rates-table/ui/ExchangeRatesTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with rates"
|
|
- "should show conversion preview"
|
|
- file: "src/features/create-exchange-rate/ui/CreateExchangeRateForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should validate rate > 0"
|
|
- "should submit valid form"
|
|
e2e_tests:
|
|
- file: "e2e/catalogs/exchange-rates.spec.ts"
|
|
test_cases:
|
|
- "should create exchange rate successfully"
|
|
- "should update rate successfully"
|
|
- "should show rate history"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear tasas de cambio por fecha"
|
|
status: Pending
|
|
test_reference: "test/catalogs/exchange-rate.controller.e2e-spec.ts:28"
|
|
- id: AC-002
|
|
description: "Sistema usa tasa más reciente si no hay tasa para fecha específica"
|
|
status: Pending
|
|
test_reference: "src/modules/catalogs/services/exchange-rate.service.spec.ts:68"
|
|
- id: AC-003
|
|
description: "Sistema convierte montos entre monedas automáticamente"
|
|
status: Pending
|
|
test_reference: "src/modules/catalogs/services/exchange-rate.service.spec.ts:95"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Tasa de cambio es específica por empresa y fecha"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:exchange_rates table"
|
|
test_reference: "src/modules/catalogs/services/exchange-rate.service.spec.ts:48"
|
|
- id: RN-002
|
|
description: "Rate debe ser mayor a 0"
|
|
implementation: "src/modules/catalogs/dto/create-exchange-rate.dto.ts:@Min(0.000001)"
|
|
test_reference: "src/modules/catalogs/services/exchange-rate.service.spec.ts:78"
|
|
- id: RN-003
|
|
description: "Si no hay tasa para fecha, usar tasa más reciente"
|
|
implementation: "src/modules/catalogs/services/exchange-rate.service.ts:getRate()"
|
|
test_reference: "src/modules/catalogs/services/exchange-rate.service.spec.ts:108"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-002-001
|
|
module_dependencies:
|
|
- MGN-002
|
|
external_dependencies: []
|
|
|
|
- rf_id: RF-MGN-003-004
|
|
rf_title: "Gestión de Unidades de Medida (UoM)"
|
|
rf_file: "requerimientos-funcionales/mgn-003/RF-MGN-003-004-gestión-de-unidades-de-medida-uom.md"
|
|
priority: P0
|
|
story_points: 5
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-003/ET-BACKEND-MGN-003-004-gestión-de-unidades-de-medida-uom.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/uom
|
|
description: "Crear unidad de medida"
|
|
- method: GET
|
|
path: /api/v1/uom
|
|
description: "Listar unidades de medida"
|
|
- method: GET
|
|
path: /api/v1/uom/:id
|
|
description: "Obtener UoM por ID"
|
|
- method: PUT
|
|
path: /api/v1/uom/:id
|
|
description: "Actualizar UoM"
|
|
- method: DELETE
|
|
path: /api/v1/uom/:id
|
|
description: "Eliminar UoM"
|
|
services:
|
|
- name: "UomService"
|
|
file: "src/modules/catalogs/services/uom.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- findOne
|
|
- update
|
|
- remove
|
|
- convert
|
|
controllers:
|
|
- name: "UomController"
|
|
file: "src/modules/catalogs/controllers/uom.controller.ts"
|
|
dtos:
|
|
- name: "CreateUomDto"
|
|
file: "src/modules/catalogs/dto/create-uom.dto.ts"
|
|
- name: "UpdateUomDto"
|
|
file: "src/modules/catalogs/dto/update-uom.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-003/ET-FRONTEND-MGN-003-004-gestión-de-unidades-de-medida-uom.md"
|
|
routes:
|
|
- path: "/catalogs/uom"
|
|
component: "UomPage"
|
|
- path: "/catalogs/uom/create"
|
|
component: "CreateUomPage"
|
|
components:
|
|
- name: "UomTable"
|
|
file: "src/widgets/uom-table/ui/UomTable.tsx"
|
|
type: widget
|
|
- name: "CreateUomForm"
|
|
file: "src/features/create-uom/ui/CreateUomForm.tsx"
|
|
type: feature
|
|
- name: "UomSelector"
|
|
file: "src/shared/ui/UomSelector.tsx"
|
|
type: shared
|
|
api_client:
|
|
- name: "uomApi"
|
|
file: "src/entities/uom/api/uom.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- create
|
|
- update
|
|
- delete
|
|
state_management:
|
|
- name: "useUomStore"
|
|
file: "src/entities/uom/model/uom.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: core
|
|
table: uom_categories
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
indices:
|
|
- idx_uom_categories_tenant_id
|
|
rls_policy: tenant_isolation_uom_categories
|
|
- schema: core
|
|
table: uom
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE
|
|
indices:
|
|
- idx_uom_category_id
|
|
- idx_uom_tenant_id
|
|
rls_policy: tenant_isolation_uom
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/catalogs/services/uom.service.spec.ts"
|
|
test_cases:
|
|
- "should create UoM with valid data"
|
|
- "should create UoM category"
|
|
- "should convert between UoMs of same category"
|
|
- "should throw error for different categories"
|
|
- "should update UoM successfully"
|
|
integration_tests:
|
|
- file: "test/catalogs/uom.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/uom should create UoM"
|
|
- "GET /api/v1/uom should return all UoMs"
|
|
- "GET /api/v1/uom/:id should return UoM"
|
|
- "PUT /api/v1/uom/:id should update UoM"
|
|
- "DELETE /api/v1/uom/:id should delete UoM"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/uom-table/ui/UomTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with UoMs"
|
|
- "should group by category"
|
|
- file: "src/features/create-uom/ui/CreateUomForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should validate ratio > 0"
|
|
- "should submit valid form"
|
|
e2e_tests:
|
|
- file: "e2e/catalogs/uom.spec.ts"
|
|
test_cases:
|
|
- "should create UoM successfully"
|
|
- "should edit UoM successfully"
|
|
- "should delete UoM with confirmation"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear unidades de medida agrupadas por categoría"
|
|
status: Pending
|
|
test_reference: "test/catalogs/uom.controller.e2e-spec.ts:28"
|
|
- id: AC-002
|
|
description: "Sistema convierte cantidades entre UoMs de misma categoría"
|
|
status: Pending
|
|
test_reference: "src/modules/catalogs/services/uom.service.spec.ts:68"
|
|
- id: AC-003
|
|
description: "Sistema previene conversión entre categorías diferentes"
|
|
status: Pending
|
|
test_reference: "src/modules/catalogs/services/uom.service.spec.ts:95"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "UoMs se agrupan en categorías (Peso, Longitud, Volumen, Tiempo)"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:uom_categories table"
|
|
test_reference: "src/modules/catalogs/services/uom.service.spec.ts:48"
|
|
- id: RN-002
|
|
description: "Conversión solo entre UoMs de misma categoría"
|
|
implementation: "src/modules/catalogs/services/uom.service.ts:convert()"
|
|
test_reference: "src/modules/catalogs/services/uom.service.spec.ts:78"
|
|
- id: RN-003
|
|
description: "Ratio define conversión respecto a UoM base (ej: 1 kg = 1000 g)"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:uom.ratio"
|
|
test_reference: "src/modules/catalogs/services/uom.service.spec.ts:108"
|
|
|
|
dependencies:
|
|
rf_dependencies: []
|
|
module_dependencies: []
|
|
external_dependencies: []
|
|
|
|
- rf_id: RF-MGN-003-005
|
|
rf_title: "Gestión de Categorías de Productos"
|
|
rf_file: "requerimientos-funcionales/mgn-003/RF-MGN-003-005-gestión-de-categorías-de-productos.md"
|
|
priority: P0
|
|
story_points: 3
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-003/ET-BACKEND-MGN-003-005-gestión-de-categorías-de-productos.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/product-categories
|
|
description: "Crear categoría de producto"
|
|
- method: GET
|
|
path: /api/v1/product-categories
|
|
description: "Listar categorías"
|
|
- method: GET
|
|
path: /api/v1/product-categories/:id
|
|
description: "Obtener categoría por ID"
|
|
- method: PUT
|
|
path: /api/v1/product-categories/:id
|
|
description: "Actualizar categoría"
|
|
- method: DELETE
|
|
path: /api/v1/product-categories/:id
|
|
description: "Eliminar categoría"
|
|
services:
|
|
- name: "ProductCategoryService"
|
|
file: "src/modules/catalogs/services/product-category.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- findOne
|
|
- update
|
|
- remove
|
|
controllers:
|
|
- name: "ProductCategoryController"
|
|
file: "src/modules/catalogs/controllers/product-category.controller.ts"
|
|
dtos:
|
|
- name: "CreateProductCategoryDto"
|
|
file: "src/modules/catalogs/dto/create-product-category.dto.ts"
|
|
- name: "UpdateProductCategoryDto"
|
|
file: "src/modules/catalogs/dto/update-product-category.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-003/ET-FRONTEND-MGN-003-005-gestión-de-categorías-de-productos.md"
|
|
routes:
|
|
- path: "/catalogs/product-categories"
|
|
component: "ProductCategoriesPage"
|
|
components:
|
|
- name: "ProductCategoriesTable"
|
|
file: "src/widgets/product-categories-table/ui/ProductCategoriesTable.tsx"
|
|
type: widget
|
|
- name: "CreateProductCategoryForm"
|
|
file: "src/features/create-product-category/ui/CreateProductCategoryForm.tsx"
|
|
type: feature
|
|
- name: "CategoryTree"
|
|
file: "src/widgets/category-tree/ui/CategoryTree.tsx"
|
|
type: widget
|
|
api_client:
|
|
- name: "productCategoryApi"
|
|
file: "src/entities/product-category/api/product-category.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- create
|
|
- update
|
|
- delete
|
|
state_management:
|
|
- name: "useProductCategoryStore"
|
|
file: "src/entities/product-category/model/product-category.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: inventory
|
|
table: product_categories
|
|
file: "database-design/schemas/inventory-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE
|
|
indices:
|
|
- idx_product_categories_parent_id
|
|
- idx_product_categories_tenant_id
|
|
rls_policy: tenant_isolation_product_categories
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/catalogs/services/product-category.service.spec.ts"
|
|
test_cases:
|
|
- "should create product category"
|
|
- "should create nested categories"
|
|
- "should validate parent exists"
|
|
- "should update category successfully"
|
|
- "should delete category"
|
|
integration_tests:
|
|
- file: "test/catalogs/product-category.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/product-categories should create category"
|
|
- "GET /api/v1/product-categories should return all categories"
|
|
- "GET /api/v1/product-categories/:id should return category"
|
|
- "PUT /api/v1/product-categories/:id should update category"
|
|
- "DELETE /api/v1/product-categories/:id should delete category"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/product-categories-table/ui/ProductCategoriesTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with categories"
|
|
- "should show hierarchy"
|
|
- file: "src/features/create-product-category/ui/CreateProductCategoryForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should submit valid form"
|
|
e2e_tests:
|
|
- file: "e2e/catalogs/product-categories.spec.ts"
|
|
test_cases:
|
|
- "should create category successfully"
|
|
- "should create nested category"
|
|
- "should delete category with confirmation"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear categorías de productos jerárquicas"
|
|
status: Pending
|
|
test_reference: "test/catalogs/product-category.controller.e2e-spec.ts:28"
|
|
- id: AC-002
|
|
description: "Sistema muestra árbol de categorías"
|
|
status: Pending
|
|
test_reference: "e2e/catalogs/product-categories.spec.ts:48"
|
|
- id: AC-003
|
|
description: "Categorías pueden tener subcategorías ilimitadas"
|
|
status: Pending
|
|
test_reference: "src/modules/catalogs/services/product-category.service.spec.ts:68"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Categorías soportan jerarquía con parent_id"
|
|
implementation: "database-design/schemas/inventory-schema-ddl.sql:product_categories.parent_id"
|
|
test_reference: "src/modules/catalogs/services/product-category.service.spec.ts:48"
|
|
- id: RN-002
|
|
description: "Productos heredan propiedades de categoría (cuentas contables)"
|
|
implementation: "src/modules/inventory/services/product.service.ts:create()"
|
|
test_reference: "src/modules/catalogs/services/product-category.service.spec.ts:78"
|
|
- id: RN-003
|
|
description: "No se puede eliminar categoría con productos asignados"
|
|
implementation: "src/modules/catalogs/services/product-category.service.ts:remove()"
|
|
test_reference: "src/modules/catalogs/services/product-category.service.spec.ts:95"
|
|
|
|
dependencies:
|
|
rf_dependencies: []
|
|
module_dependencies: []
|
|
external_dependencies: []
|
|
|
|
- rf_id: RF-MGN-003-006
|
|
rf_title: "Condiciones de Pago (Payment Terms)"
|
|
rf_file: "requerimientos-funcionales/mgn-003/RF-MGN-003-006-condiciones-de-pago-payment-terms.md"
|
|
priority: P0
|
|
story_points: 5
|
|
|
|
et_backend:
|
|
file: "especificaciones-tecnicas/backend/mgn-003/ET-BACKEND-MGN-003-006-condiciones-de-pago-payment-terms.md"
|
|
endpoints:
|
|
- method: POST
|
|
path: /api/v1/payment-terms
|
|
description: "Crear término de pago"
|
|
- method: GET
|
|
path: /api/v1/payment-terms
|
|
description: "Listar términos de pago"
|
|
- method: GET
|
|
path: /api/v1/payment-terms/:id
|
|
description: "Obtener término por ID"
|
|
- method: PUT
|
|
path: /api/v1/payment-terms/:id
|
|
description: "Actualizar término"
|
|
- method: DELETE
|
|
path: /api/v1/payment-terms/:id
|
|
description: "Eliminar término"
|
|
services:
|
|
- name: "PaymentTermService"
|
|
file: "src/modules/catalogs/services/payment-term.service.ts"
|
|
methods:
|
|
- create
|
|
- findAll
|
|
- findOne
|
|
- update
|
|
- remove
|
|
- calculateDueDate
|
|
controllers:
|
|
- name: "PaymentTermController"
|
|
file: "src/modules/catalogs/controllers/payment-term.controller.ts"
|
|
dtos:
|
|
- name: "CreatePaymentTermDto"
|
|
file: "src/modules/catalogs/dto/create-payment-term.dto.ts"
|
|
- name: "UpdatePaymentTermDto"
|
|
file: "src/modules/catalogs/dto/update-payment-term.dto.ts"
|
|
|
|
et_frontend:
|
|
file: "especificaciones-tecnicas/frontend/mgn-003/ET-FRONTEND-MGN-003-006-condiciones-de-pago-payment-terms.md"
|
|
routes:
|
|
- path: "/catalogs/payment-terms"
|
|
component: "PaymentTermsPage"
|
|
components:
|
|
- name: "PaymentTermsTable"
|
|
file: "src/widgets/payment-terms-table/ui/PaymentTermsTable.tsx"
|
|
type: widget
|
|
- name: "CreatePaymentTermForm"
|
|
file: "src/features/create-payment-term/ui/CreatePaymentTermForm.tsx"
|
|
type: feature
|
|
- name: "PaymentTermSelector"
|
|
file: "src/shared/ui/PaymentTermSelector.tsx"
|
|
type: shared
|
|
api_client:
|
|
- name: "paymentTermApi"
|
|
file: "src/entities/payment-term/api/payment-term.api.ts"
|
|
methods:
|
|
- getAll
|
|
- getById
|
|
- create
|
|
- update
|
|
- delete
|
|
state_management:
|
|
- name: "usePaymentTermStore"
|
|
file: "src/entities/payment-term/model/payment-term.store.ts"
|
|
type: zustand
|
|
|
|
database_tables:
|
|
- schema: core
|
|
table: payment_terms
|
|
file: "database-design/schemas/core-schema-ddl.sql"
|
|
operations:
|
|
- SELECT
|
|
- INSERT
|
|
- UPDATE
|
|
- DELETE
|
|
indices:
|
|
- idx_payment_terms_tenant_id
|
|
- idx_payment_terms_company_id
|
|
rls_policy: tenant_isolation_payment_terms
|
|
|
|
tests:
|
|
backend:
|
|
unit_tests:
|
|
- file: "src/modules/catalogs/services/payment-term.service.spec.ts"
|
|
test_cases:
|
|
- "should create payment term"
|
|
- "should calculate due date from invoice date"
|
|
- "should handle immediate payment"
|
|
- "should handle 30/60/90 days terms"
|
|
- "should update payment term"
|
|
integration_tests:
|
|
- file: "test/catalogs/payment-term.controller.e2e-spec.ts"
|
|
test_cases:
|
|
- "POST /api/v1/payment-terms should create term"
|
|
- "GET /api/v1/payment-terms should return all terms"
|
|
- "GET /api/v1/payment-terms/:id should return term"
|
|
- "PUT /api/v1/payment-terms/:id should update term"
|
|
- "DELETE /api/v1/payment-terms/:id should delete term"
|
|
- "should enforce tenant isolation"
|
|
- "should require authentication"
|
|
frontend:
|
|
component_tests:
|
|
- file: "src/widgets/payment-terms-table/ui/PaymentTermsTable.test.tsx"
|
|
test_cases:
|
|
- "should render table with terms"
|
|
- "should show days calculation"
|
|
- file: "src/features/create-payment-term/ui/CreatePaymentTermForm.test.tsx"
|
|
test_cases:
|
|
- "should validate required fields"
|
|
- "should validate days >= 0"
|
|
- "should submit valid form"
|
|
e2e_tests:
|
|
- file: "e2e/catalogs/payment-terms.spec.ts"
|
|
test_cases:
|
|
- "should create payment term successfully"
|
|
- "should edit payment term"
|
|
- "should delete payment term with confirmation"
|
|
|
|
acceptance_criteria:
|
|
- id: AC-001
|
|
description: "Usuario puede crear términos de pago con días personalizados"
|
|
status: Pending
|
|
test_reference: "test/catalogs/payment-term.controller.e2e-spec.ts:28"
|
|
- id: AC-002
|
|
description: "Sistema calcula fecha de vencimiento automáticamente"
|
|
status: Pending
|
|
test_reference: "src/modules/catalogs/services/payment-term.service.spec.ts:68"
|
|
- id: AC-003
|
|
description: "Términos comunes: Contado (0 días), 30, 60, 90 días"
|
|
status: Pending
|
|
test_reference: "test/catalogs/payment-term.controller.e2e-spec.ts:95"
|
|
|
|
business_rules:
|
|
- id: RN-001
|
|
description: "Payment term define días hasta vencimiento"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:payment_terms.days"
|
|
test_reference: "src/modules/catalogs/services/payment-term.service.spec.ts:48"
|
|
- id: RN-002
|
|
description: "Fecha vencimiento = fecha factura + días del término"
|
|
implementation: "src/modules/catalogs/services/payment-term.service.ts:calculateDueDate()"
|
|
test_reference: "src/modules/catalogs/services/payment-term.service.spec.ts:78"
|
|
- id: RN-003
|
|
description: "Partners y empresas tienen payment term por defecto"
|
|
implementation: "database-design/schemas/core-schema-ddl.sql:partners.payment_term_id"
|
|
test_reference: "src/modules/catalogs/services/payment-term.service.spec.ts:108"
|
|
|
|
dependencies:
|
|
rf_dependencies:
|
|
- RF-MGN-002-001
|
|
module_dependencies:
|
|
- MGN-002
|
|
external_dependencies: []
|
|
|
|
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: 30
|
|
total_components: 24
|
|
total_tables: 9
|
|
total_test_cases: 120
|
|
estimated_duration_sprints: 2
|