erp-core/docs/03-fase-vertical/MGN-012-purchases/especificaciones/ET-PURCHASES-BACKEND.md
rckrdmrd 0086695b4c
Some checks failed
ERP Core CI / Backend Lint (push) Has been cancelled
ERP Core CI / Backend Unit Tests (push) Has been cancelled
ERP Core CI / Backend Integration Tests (push) Has been cancelled
ERP Core CI / Frontend Lint (push) Has been cancelled
ERP Core CI / Frontend Unit Tests (push) Has been cancelled
ERP Core CI / Frontend E2E Tests (push) Has been cancelled
ERP Core CI / Database DDL Validation (push) Has been cancelled
ERP Core CI / Backend Build (push) Has been cancelled
ERP Core CI / Frontend Build (push) Has been cancelled
ERP Core CI / CI Success (push) Has been cancelled
Performance Tests / Lighthouse CI (push) Has been cancelled
Performance Tests / Bundle Size Analysis (push) Has been cancelled
Performance Tests / k6 Load Tests (push) Has been cancelled
Performance Tests / Performance Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0 + cambios backend
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones en modulos CRM y OpenAPI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:05 -06:00

21 KiB

Especificacion Tecnica Backend - Modulo Purchases (MGN-012)

METADATOS

Campo Valor
Modulo MGN-012
Nombre Purchases (Compras)
Version 1.0.0
Fecha 2026-01-10
Estado Implementado
Backend Path backend/src/modules/purchases/
Schema BD purchase

SERVICIOS

1. PurchasesService

Archivo: purchases.service.ts

Descripcion: Servicio para gestion de ordenes de compra (Purchase Orders).

Metodos

Metodo Parametros Retorno Descripcion
findAll tenantId: string, filters: PurchaseOrderFilters Promise<{ data: PurchaseOrder[]; total: number }> Lista ordenes de compra con paginacion y filtros
findById id: string, tenantId: string Promise<PurchaseOrder> Obtiene una orden por ID con sus lineas
create dto: CreatePurchaseOrderDto, tenantId: string, userId: string Promise<PurchaseOrder> Crea nueva orden de compra con lineas (transaccion)
update id: string, dto: UpdatePurchaseOrderDto, tenantId: string, userId: string Promise<PurchaseOrder> Actualiza orden en estado draft
confirm id: string, tenantId: string, userId: string Promise<PurchaseOrder> Confirma orden (draft -> confirmed)
cancel id: string, tenantId: string, userId: string Promise<PurchaseOrder> Cancela orden (no aplica a done)
delete id: string, tenantId: string Promise<void> Elimina orden en estado draft

Tipos de Estado (OrderStatus)

type OrderStatus = 'draft' | 'sent' | 'confirmed' | 'done' | 'cancelled';

Filtros Disponibles (PurchaseOrderFilters)

interface PurchaseOrderFilters {
  company_id?: string;
  partner_id?: string;
  status?: OrderStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;     // default: 1
  limit?: number;    // default: 20
}

2. RfqsService

Archivo: rfqs.service.ts

Descripcion: Servicio para gestion de Solicitudes de Cotizacion (Request for Quotation - RFQ).

Metodos

Metodo Parametros Retorno Descripcion
findAll tenantId: string, filters: RfqFilters Promise<{ data: Rfq[]; total: number }> Lista RFQs con paginacion y filtros
findById id: string, tenantId: string Promise<Rfq> Obtiene RFQ por ID con lineas y partners
create dto: CreateRfqDto, tenantId: string, userId: string Promise<Rfq> Crea nueva RFQ con lineas (transaccion)
update id: string, dto: UpdateRfqDto, tenantId: string, userId: string Promise<Rfq> Actualiza RFQ en estado draft
addLine rfqId: string, dto: CreateRfqLineDto, tenantId: string Promise<RfqLine> Agrega linea a RFQ en draft
updateLine rfqId: string, lineId: string, dto: UpdateRfqLineDto, tenantId: string Promise<RfqLine> Actualiza linea de RFQ en draft
removeLine rfqId: string, lineId: string, tenantId: string Promise<void> Elimina linea (minimo 1 linea requerida)
send id: string, tenantId: string, userId: string Promise<Rfq> Envia RFQ (draft -> sent)
markResponded id: string, tenantId: string, userId: string Promise<Rfq> Marca como respondida (sent -> responded)
accept id: string, tenantId: string, userId: string Promise<Rfq> Acepta RFQ (sent/responded -> accepted)
reject id: string, tenantId: string, userId: string Promise<Rfq> Rechaza RFQ (sent/responded -> rejected)
cancel id: string, tenantId: string, userId: string Promise<Rfq> Cancela RFQ (no aplica a accepted)
delete id: string, tenantId: string Promise<void> Elimina RFQ en estado draft

Tipos de Estado (RfqStatus)

type RfqStatus = 'draft' | 'sent' | 'responded' | 'accepted' | 'rejected' | 'cancelled';

Filtros Disponibles (RfqFilters)

interface RfqFilters {
  company_id?: string;
  status?: RfqStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;     // default: 1
  limit?: number;    // default: 20
}

ENTIDADES

Schema: purchase

Tabla: purchase_orders

Columna Tipo Nullable Default Descripcion
id UUID NO gen_random_uuid() Clave primaria
tenant_id UUID NO - FK a auth.tenants
company_id UUID NO - FK a auth.companies
name VARCHAR(100) NO - Numero de orden (unico por company)
ref VARCHAR(100) SI - Referencia del proveedor
partner_id UUID NO - FK a core.partners (proveedor)
order_date DATE NO - Fecha de la orden
expected_date DATE SI - Fecha esperada de recepcion
effective_date DATE SI - Fecha efectiva de recepcion
currency_id UUID NO - FK a core.currencies
payment_term_id UUID SI - FK a financial.payment_terms
amount_untaxed DECIMAL(15,2) NO 0 Monto sin impuestos
amount_tax DECIMAL(15,2) NO 0 Monto de impuestos
amount_total DECIMAL(15,2) NO 0 Monto total
status order_status NO 'draft' Estado de la orden
receipt_status VARCHAR(20) SI 'pending' Estado de recepcion
invoice_status VARCHAR(20) SI 'pending' Estado de facturacion
picking_id UUID SI - FK a inventory.pickings
invoice_id UUID SI - FK a financial.invoices
notes TEXT SI - Notas adicionales
dest_address_id UUID SI - Direccion de envio (dropship)
locked BOOLEAN SI FALSE Bloqueo de orden
approval_required BOOLEAN SI FALSE Requiere aprobacion
amount_approval_threshold DECIMAL(15,2) SI - Umbral de aprobacion
created_at TIMESTAMP NO CURRENT_TIMESTAMP Fecha creacion
created_by UUID SI - Usuario creador
updated_at TIMESTAMP SI - Fecha actualizacion
updated_by UUID SI - Usuario actualizacion
confirmed_at TIMESTAMP SI - Fecha confirmacion
confirmed_by UUID SI - Usuario confirmacion
approved_at TIMESTAMP SI - Fecha aprobacion
approved_by UUID SI - Usuario aprobacion
cancelled_at TIMESTAMP SI - Fecha cancelacion
cancelled_by UUID SI - Usuario cancelacion

Indices:

  • idx_purchase_orders_tenant_id
  • idx_purchase_orders_company_id
  • idx_purchase_orders_partner_id
  • idx_purchase_orders_name
  • idx_purchase_orders_status
  • idx_purchase_orders_order_date
  • idx_purchase_orders_expected_date

Constraint UNIQUE: (company_id, name)


Tabla: purchase_order_lines

Columna Tipo Nullable Default Descripcion
id UUID NO gen_random_uuid() Clave primaria
tenant_id UUID NO - FK a auth.tenants
order_id UUID NO - FK a purchase.purchase_orders
product_id UUID NO - FK a inventory.products
description TEXT NO - Descripcion del producto
quantity DECIMAL(12,4) NO - Cantidad solicitada
qty_received DECIMAL(12,4) SI 0 Cantidad recibida
qty_invoiced DECIMAL(12,4) SI 0 Cantidad facturada
uom_id UUID NO - FK a core.uom
price_unit DECIMAL(15,4) NO - Precio unitario
discount DECIMAL(5,2) SI 0 Porcentaje descuento
tax_ids UUID[] SI '{}' Array de impuestos
amount_untaxed DECIMAL(15,2) NO - Subtotal sin impuestos
amount_tax DECIMAL(15,2) NO - Monto impuestos
amount_total DECIMAL(15,2) NO - Total linea
expected_date DATE SI - Fecha esperada
analytic_account_id UUID SI - FK a analytics.analytic_accounts
created_at TIMESTAMP NO CURRENT_TIMESTAMP Fecha creacion
updated_at TIMESTAMP SI - Fecha actualizacion

Constraints:

  • chk_purchase_order_lines_quantity: quantity > 0
  • chk_purchase_order_lines_discount: discount >= 0 AND discount <= 100

Tabla: rfqs

Columna Tipo Nullable Default Descripcion
id UUID NO gen_random_uuid() Clave primaria
tenant_id UUID NO - FK a auth.tenants
company_id UUID NO - FK a auth.companies
name VARCHAR(100) NO - Numero RFQ (ej: RFQ-000001)
partner_ids UUID[] NO - Array de proveedores
request_date DATE NO - Fecha de solicitud
deadline_date DATE SI - Fecha limite respuesta
response_date DATE SI - Fecha de respuesta
status rfq_status NO 'draft' Estado del RFQ
description TEXT SI - Descripcion
notes TEXT SI - Notas adicionales
created_at TIMESTAMP NO CURRENT_TIMESTAMP Fecha creacion
created_by UUID SI - Usuario creador
updated_at TIMESTAMP SI - Fecha actualizacion
updated_by UUID SI - Usuario actualizacion

Constraint UNIQUE: (company_id, name)


Tabla: rfq_lines

Columna Tipo Nullable Default Descripcion
id UUID NO gen_random_uuid() Clave primaria
tenant_id UUID NO - FK a auth.tenants
rfq_id UUID NO - FK a purchase.rfqs
product_id UUID SI - FK a inventory.products
description TEXT NO - Descripcion del producto
quantity DECIMAL(12,4) NO - Cantidad solicitada
uom_id UUID NO - FK a core.uom
created_at TIMESTAMP NO CURRENT_TIMESTAMP Fecha creacion

Constraint: chk_rfq_lines_quantity: quantity > 0


DTOs

Purchase Orders

CreatePurchaseOrderDto

interface CreatePurchaseOrderDto {
  company_id: string;           // UUID, requerido
  name: string;                 // min: 1, max: 100
  ref?: string;                 // max: 100
  partner_id: string;           // UUID, requerido
  order_date: string;           // formato: YYYY-MM-DD
  expected_date?: string;       // formato: YYYY-MM-DD
  currency_id: string;          // UUID, requerido
  payment_term_id?: string;     // UUID
  notes?: string;
  lines: PurchaseOrderLineDto[]; // min: 1 linea
}

interface PurchaseOrderLineDto {
  product_id: string;           // UUID, requerido
  description: string;          // min: 1
  quantity: number;             // positive
  uom_id: string;               // UUID, requerido
  price_unit: number;           // min: 0
  discount?: number;            // 0-100, default: 0
  amount_untaxed: number;       // min: 0
}

UpdatePurchaseOrderDto

interface UpdatePurchaseOrderDto {
  ref?: string | null;
  partner_id?: string;
  order_date?: string;
  expected_date?: string | null;
  currency_id?: string;
  payment_term_id?: string | null;
  notes?: string | null;
  lines?: PurchaseOrderLineDto[]; // min: 1 si se proporciona
}

RFQs

CreateRfqDto

interface CreateRfqDto {
  company_id: string;           // UUID, requerido
  partner_ids: string[];        // Array UUID, min: 1
  request_date?: string;        // YYYY-MM-DD, default: hoy
  deadline_date?: string;       // YYYY-MM-DD
  description?: string;
  notes?: string;
  lines: CreateRfqLineDto[];    // min: 1 linea
}

interface CreateRfqLineDto {
  product_id?: string;          // UUID, opcional
  description: string;          // min: 1
  quantity: number;             // positive
  uom_id: string;               // UUID, requerido
}

UpdateRfqDto

interface UpdateRfqDto {
  partner_ids?: string[];       // Array UUID, min: 1 si se proporciona
  deadline_date?: string | null;
  description?: string | null;
  notes?: string | null;
}

UpdateRfqLineDto

interface UpdateRfqLineDto {
  product_id?: string | null;
  description?: string;
  quantity?: number;
  uom_id?: string;
}

ENDPOINTS

Base Path: /api/purchases

Purchase Orders

Metodo Ruta Roles Permitidos Descripcion
GET / admin, manager, warehouse, accountant, super_admin Listar ordenes de compra
GET /:id admin, manager, warehouse, accountant, super_admin Obtener orden por ID
POST / admin, manager, warehouse, super_admin Crear orden de compra
PUT /:id admin, manager, warehouse, super_admin Actualizar orden
POST /:id/confirm admin, manager, super_admin Confirmar orden
POST /:id/cancel admin, manager, super_admin Cancelar orden
DELETE /:id admin, super_admin Eliminar orden (solo draft)

RFQs (Request for Quotation)

Metodo Ruta Roles Permitidos Descripcion
GET /rfqs admin, manager, warehouse, super_admin Listar RFQs
GET /rfqs/:id admin, manager, warehouse, super_admin Obtener RFQ por ID
POST /rfqs admin, manager, warehouse, super_admin Crear RFQ
PUT /rfqs/:id admin, manager, warehouse, super_admin Actualizar RFQ
DELETE /rfqs/:id admin, super_admin Eliminar RFQ (solo draft)

RFQ Lines

Metodo Ruta Roles Permitidos Descripcion
POST /rfqs/:id/lines admin, manager, warehouse, super_admin Agregar linea a RFQ
PUT /rfqs/:id/lines/:lineId admin, manager, warehouse, super_admin Actualizar linea
DELETE /rfqs/:id/lines/:lineId admin, manager, warehouse, super_admin Eliminar linea

RFQ Workflow

Metodo Ruta Roles Permitidos Descripcion
POST /rfqs/:id/send admin, manager, super_admin Enviar RFQ a proveedores
POST /rfqs/:id/responded admin, manager, super_admin Marcar como respondida
POST /rfqs/:id/accept admin, manager, super_admin Aceptar RFQ
POST /rfqs/:id/reject admin, manager, super_admin Rechazar RFQ
POST /rfqs/:id/cancel admin, manager, super_admin Cancelar RFQ

Formato de Respuesta

Exito (Listado)

{
  "success": true,
  "data": [...],
  "meta": {
    "total": 100,
    "page": 1,
    "limit": 20,
    "totalPages": 5
  }
}

Exito (Entidad)

{
  "success": true,
  "data": { ... },
  "message": "Operacion exitosa"
}

Error

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Descripcion del error",
    "details": [...]
  }
}

TESTS

Archivo: __tests__/purchases.service.spec.ts

Casos de Prueba - PurchasesService

Suite Test Case Estado
findAll should return paginated orders for a tenant PASS
findAll should enforce tenant isolation PASS
findAll should apply pagination correctly PASS
findById should return order with lines by id PASS
findById should throw NotFoundError when order does not exist PASS
findById should enforce tenant isolation PASS
create should create order with lines in transaction PASS
create should rollback transaction on error PASS
update should update draft order successfully PASS
update should throw ValidationError when updating confirmed order PASS
delete should delete draft order successfully PASS
delete should throw ValidationError when deleting confirmed order PASS
confirm should confirm draft order successfully PASS
confirm should throw ValidationError when confirming order without lines PASS
confirm should throw ValidationError when confirming non-draft order PASS
cancel should cancel draft order successfully PASS
cancel should throw ValidationError when cancelling done order PASS
cancel should throw ValidationError when cancelling already cancelled order PASS

Archivo: __tests__/rfqs.service.spec.ts

Casos de Prueba - RfqsService

Suite Test Case Estado
findAll should return paginated RFQs for a tenant PASS
findAll should enforce tenant isolation PASS
findAll should apply pagination correctly PASS
findAll should filter by company_id, status, date range, search PASS
findById should return RFQ with lines by id PASS
findById should return partner names when partner_ids exist PASS
findById should throw NotFoundError when RFQ does not exist PASS
create should create RFQ with lines in transaction PASS
create should generate sequential RFQ name PASS
create should rollback transaction on error PASS
create should throw ValidationError when lines or partner_ids empty PASS
update should update draft RFQ successfully PASS
update should throw ValidationError when updating non-draft RFQ PASS
delete should delete draft RFQ successfully PASS
delete should throw ValidationError when deleting non-draft RFQ PASS
addLine should add line to draft RFQ successfully PASS
updateLine should update line in draft RFQ successfully PASS
removeLine should remove line from draft RFQ (min 1 required) PASS
send should send draft RFQ successfully PASS
markResponded should mark sent RFQ as responded PASS
accept should accept responded/sent RFQ PASS
reject should reject responded/sent RFQ PASS
cancel should cancel draft/sent/responded/rejected RFQ PASS
Status Transitions Validates all valid/invalid state transitions PASS
Tenant Isolation should not access RFQs from different tenant PASS
Error Handling should propagate database errors PASS

DEPENDENCIAS

Internas (Modulos del Sistema)

Modulo Uso
config/database Conexion a BD (query, queryOne, getClient)
shared/errors NotFoundError, ConflictError, ValidationError
shared/middleware/auth.middleware authenticate, requireRoles, AuthenticatedRequest

Externas (npm)

Paquete Uso
express Router, Request, Response, NextFunction
zod Validacion de DTOs y schemas
pg Cliente PostgreSQL (via config/database)

Tablas Relacionadas

Schema Tabla Relacion
auth tenants tenant_id (multi-tenant)
auth companies company_id
auth users created_by, updated_by, confirmed_by
core partners partner_id (proveedores)
core currencies currency_id
core uom uom_id (unidades de medida)
inventory products product_id
inventory pickings picking_id (recepciones)
financial payment_terms payment_term_id
financial invoices invoice_id
analytics analytic_accounts analytic_account_id

DIAGRAMAS

Flujo de Estados - Purchase Order

                    +--------+
                    | draft  |
                    +---+----+
                        |
         +--------------+---------------+
         |              |               |
         v              v               v
     +-------+     +---------+    +-----------+
     | sent  | --> | confirmed|    | cancelled |
     +-------+     +----+----+    +-----------+
                        |
                        v
                   +--------+
                   |  done  |
                   +--------+

Flujo de Estados - RFQ

                    +--------+
                    | draft  |
                    +---+----+
                        |
                        v
                    +-------+
                    | sent  |
                    +---+---+
                        |
         +--------------+---------------+
         |              |               |
         v              v               v
   +-----------+  +----------+   +-----------+
   | responded |  | accepted |   | rejected  |
   +-----+-----+  +----+-----+   +-----+-----+
         |             |               |
         v             |               |
   +-----------+       |               |
   | accepted  |<------+               |
   +-----------+                       |
         |                             |
         v                             v
   +-----------+                 +-----------+
   |   (end)   |                 | cancelled |
   +-----------+                 +-----------+

NOTAS ADICIONALES

Seguridad

  1. Multi-tenant: Todas las consultas incluyen filtro por tenant_id
  2. RLS (Row Level Security): Habilitado en todas las tablas del schema purchase
  3. Autenticacion: Todos los endpoints requieren autenticacion JWT
  4. Autorizacion: Roles especificos por endpoint

Validaciones de Negocio

  1. Ordenes de Compra:

    • Solo se pueden modificar/eliminar ordenes en estado draft
    • Minimo 1 linea requerida para confirmar
    • No se puede cancelar una orden done
  2. RFQs:

    • Solo se pueden modificar/eliminar RFQs en estado draft
    • Minimo 1 proveedor (partner_id) requerido
    • Minimo 1 linea requerida
    • No se puede cancelar un RFQ accepted

Transacciones

  • Creacion de ordenes/RFQs con lineas utiliza transacciones (BEGIN/COMMIT/ROLLBACK)
  • Rollback automatico en caso de error

CHANGELOG

Version Fecha Cambios
1.0.0 2026-01-10 Version inicial - PurchasesService y RfqsService implementados