# ET-SALES-BACKEND # Especificacion Tecnica Backend - Modulo Ventas --- ## METADATOS | Campo | Valor | |-------|-------| | **Modulo** | MGN-011 | | **Version** | 1.0.0 | | **Fecha** | 2026-01-10 | | **Estado** | Documentado | | **Autor** | Claude Code | --- ## 1. SERVICIOS ### 1.1 CustomerGroupsService **Ubicacion:** `backend/src/modules/sales/customer-groups.service.ts` | Metodo | Parametros | Retorno | Descripcion | |--------|------------|---------|-------------| | `findAll` | `tenantId: string`, `filters: CustomerGroupFilters` | `Promise<{ data: CustomerGroup[]; total: number }>` | Lista grupos de clientes con paginacion y busqueda | | `findById` | `id: string`, `tenantId: string` | `Promise` | Obtiene grupo por ID con sus miembros | | `create` | `dto: CreateCustomerGroupDto`, `tenantId: string`, `userId: string` | `Promise` | Crea nuevo grupo de clientes | | `update` | `id: string`, `dto: UpdateCustomerGroupDto`, `tenantId: string` | `Promise` | Actualiza grupo existente | | `delete` | `id: string`, `tenantId: string` | `Promise` | Elimina grupo (solo si no tiene miembros) | | `addMember` | `groupId: string`, `partnerId: string`, `tenantId: string` | `Promise` | Agrega cliente al grupo | | `removeMember` | `groupId: string`, `memberId: string`, `tenantId: string` | `Promise` | Elimina cliente del grupo | --- ### 1.2 SalesTeamsService **Ubicacion:** `backend/src/modules/sales/sales-teams.service.ts` | Metodo | Parametros | Retorno | Descripcion | |--------|------------|---------|-------------| | `findAll` | `tenantId: string`, `filters: SalesTeamFilters` | `Promise<{ data: SalesTeam[]; total: number }>` | Lista equipos de ventas con filtros | | `findById` | `id: string`, `tenantId: string` | `Promise` | Obtiene equipo por ID con miembros | | `create` | `dto: CreateSalesTeamDto`, `tenantId: string`, `userId: string` | `Promise` | Crea nuevo equipo de ventas | | `update` | `id: string`, `dto: UpdateSalesTeamDto`, `tenantId: string`, `userId: string` | `Promise` | Actualiza equipo existente | | `addMember` | `teamId: string`, `userId: string`, `role: string`, `tenantId: string` | `Promise` | Agrega usuario al equipo | | `removeMember` | `teamId: string`, `memberId: string`, `tenantId: string` | `Promise` | Elimina usuario del equipo | --- ### 1.3 PricelistsService **Ubicacion:** `backend/src/modules/sales/pricelists.service.ts` | Metodo | Parametros | Retorno | Descripcion | |--------|------------|---------|-------------| | `findAll` | `tenantId: string`, `filters: PricelistFilters` | `Promise<{ data: Pricelist[]; total: number }>` | Lista listas de precios | | `findById` | `id: string`, `tenantId: string` | `Promise` | Obtiene lista de precios con items | | `create` | `dto: CreatePricelistDto`, `tenantId: string`, `userId: string` | `Promise` | Crea nueva lista de precios | | `update` | `id: string`, `dto: UpdatePricelistDto`, `tenantId: string`, `userId: string` | `Promise` | Actualiza lista de precios | | `addItem` | `pricelistId: string`, `dto: CreatePricelistItemDto`, `tenantId: string`, `userId: string` | `Promise` | Agrega item a lista de precios | | `removeItem` | `pricelistId: string`, `itemId: string`, `tenantId: string` | `Promise` | Elimina item de lista de precios | | `getProductPrice` | `productId: string`, `pricelistId: string`, `quantity: number` | `Promise` | Obtiene precio de producto segun lista y cantidad | --- ### 1.4 OrdersService **Ubicacion:** `backend/src/modules/sales/orders.service.ts` | Metodo | Parametros | Retorno | Descripcion | |--------|------------|---------|-------------| | `findAll` | `tenantId: string`, `filters: SalesOrderFilters` | `Promise<{ data: SalesOrder[]; total: number }>` | Lista ordenes de venta con filtros avanzados | | `findById` | `id: string`, `tenantId: string` | `Promise` | Obtiene orden por ID con lineas | | `create` | `dto: CreateSalesOrderDto`, `tenantId: string`, `userId: string` | `Promise` | Crea nueva orden de venta | | `update` | `id: string`, `dto: UpdateSalesOrderDto`, `tenantId: string`, `userId: string` | `Promise` | Actualiza orden (solo en estado draft) | | `delete` | `id: string`, `tenantId: string` | `Promise` | Elimina orden (solo en estado draft) | | `addLine` | `orderId: string`, `dto: CreateSalesOrderLineDto`, `tenantId: string`, `userId: string` | `Promise` | Agrega linea a orden | | `updateLine` | `orderId: string`, `lineId: string`, `dto: UpdateSalesOrderLineDto`, `tenantId: string` | `Promise` | Actualiza linea de orden | | `removeLine` | `orderId: string`, `lineId: string`, `tenantId: string` | `Promise` | Elimina linea de orden | | `confirm` | `id: string`, `tenantId: string`, `userId: string` | `Promise` | Confirma orden (draft -> sent) | | `cancel` | `id: string`, `tenantId: string`, `userId: string` | `Promise` | Cancela orden | | `createInvoice` | `id: string`, `tenantId: string`, `userId: string` | `Promise<{ orderId: string; invoiceId: string }>` | Genera factura desde orden | --- ### 1.5 QuotationsService **Ubicacion:** `backend/src/modules/sales/quotations.service.ts` | Metodo | Parametros | Retorno | Descripcion | |--------|------------|---------|-------------| | `findAll` | `tenantId: string`, `filters: QuotationFilters` | `Promise<{ data: Quotation[]; total: number }>` | Lista cotizaciones con filtros | | `findById` | `id: string`, `tenantId: string` | `Promise` | Obtiene cotizacion por ID con lineas | | `create` | `dto: CreateQuotationDto`, `tenantId: string`, `userId: string` | `Promise` | Crea nueva cotizacion | | `update` | `id: string`, `dto: UpdateQuotationDto`, `tenantId: string`, `userId: string` | `Promise` | Actualiza cotizacion (solo en draft) | | `delete` | `id: string`, `tenantId: string` | `Promise` | Elimina cotizacion (solo en draft) | | `addLine` | `quotationId: string`, `dto: CreateQuotationLineDto`, `tenantId: string`, `userId: string` | `Promise` | Agrega linea a cotizacion | | `updateLine` | `quotationId: string`, `lineId: string`, `dto: UpdateQuotationLineDto`, `tenantId: string` | `Promise` | Actualiza linea de cotizacion | | `removeLine` | `quotationId: string`, `lineId: string`, `tenantId: string` | `Promise` | Elimina linea de cotizacion | | `send` | `id: string`, `tenantId: string`, `userId: string` | `Promise` | Envia cotizacion al cliente (email) | | `confirm` | `id: string`, `tenantId: string`, `userId: string` | `Promise<{ quotation: Quotation; orderId: string }>` | Confirma cotizacion y crea orden de venta | | `cancel` | `id: string`, `tenantId: string`, `userId: string` | `Promise` | Cancela cotizacion | --- ## 2. ENTIDADES ### 2.1 CustomerGroup **Schema:** `sales.customer_groups` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `tenant_id` | UUID | NO | Tenant (multi-tenancy) | | `name` | VARCHAR(255) | NO | Nombre del grupo | | `description` | TEXT | SI | Descripcion | | `discount_percentage` | DECIMAL | NO | Porcentaje de descuento (default: 0) | | `created_by` | UUID | SI | Usuario que creo | | `created_at` | TIMESTAMP | NO | Fecha de creacion | ### 2.2 CustomerGroupMember **Schema:** `sales.customer_group_members` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `customer_group_id` | UUID | NO | FK a customer_groups | | `partner_id` | UUID | NO | FK a core.partners | | `joined_at` | TIMESTAMP | NO | Fecha de union | ### 2.3 SalesTeam **Schema:** `sales.sales_teams` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `tenant_id` | UUID | NO | Tenant | | `company_id` | UUID | NO | FK a auth.companies | | `name` | VARCHAR(255) | NO | Nombre del equipo | | `code` | VARCHAR(50) | SI | Codigo unico por empresa | | `team_leader_id` | UUID | SI | FK a auth.users (lider) | | `target_monthly` | DECIMAL | SI | Meta mensual | | `target_annual` | DECIMAL | SI | Meta anual | | `active` | BOOLEAN | NO | Estado activo (default: true) | | `created_by` | UUID | SI | Usuario que creo | | `created_at` | TIMESTAMP | NO | Fecha de creacion | | `updated_by` | UUID | SI | Usuario que actualizo | | `updated_at` | TIMESTAMP | SI | Fecha de actualizacion | ### 2.4 SalesTeamMember **Schema:** `sales.sales_team_members` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `sales_team_id` | UUID | NO | FK a sales_teams | | `user_id` | UUID | NO | FK a auth.users | | `role` | VARCHAR(100) | SI | Rol en el equipo | | `joined_at` | TIMESTAMP | NO | Fecha de union | ### 2.5 Pricelist **Schema:** `sales.pricelists` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `tenant_id` | UUID | NO | Tenant | | `company_id` | UUID | SI | FK a auth.companies | | `name` | VARCHAR(255) | NO | Nombre de la lista | | `currency_id` | UUID | NO | FK a core.currencies | | `active` | BOOLEAN | NO | Estado activo (default: true) | | `created_by` | UUID | SI | Usuario que creo | | `created_at` | TIMESTAMP | NO | Fecha de creacion | | `updated_by` | UUID | SI | Usuario que actualizo | | `updated_at` | TIMESTAMP | SI | Fecha de actualizacion | ### 2.6 PricelistItem **Schema:** `sales.pricelist_items` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `pricelist_id` | UUID | NO | FK a pricelists | | `product_id` | UUID | SI | FK a inventory.products | | `product_category_id` | UUID | SI | FK a core.product_categories | | `price` | DECIMAL | NO | Precio | | `min_quantity` | INTEGER | NO | Cantidad minima (default: 1) | | `valid_from` | DATE | SI | Fecha inicio validez | | `valid_to` | DATE | SI | Fecha fin validez | | `active` | BOOLEAN | NO | Estado activo (default: true) | | `created_by` | UUID | SI | Usuario que creo | ### 2.7 SalesOrder **Schema:** `sales.sales_orders` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `tenant_id` | UUID | NO | Tenant | | `company_id` | UUID | NO | FK a auth.companies | | `name` | VARCHAR(50) | NO | Numero de orden (SO-XXXXXX) | | `client_order_ref` | VARCHAR(100) | SI | Referencia del cliente | | `partner_id` | UUID | NO | FK a core.partners | | `order_date` | DATE | NO | Fecha de orden | | `validity_date` | DATE | SI | Fecha de validez | | `commitment_date` | DATE | SI | Fecha de compromiso | | `currency_id` | UUID | NO | FK a core.currencies | | `pricelist_id` | UUID | SI | FK a pricelists | | `payment_term_id` | UUID | SI | FK a financial.payment_terms | | `user_id` | UUID | SI | FK a auth.users (vendedor) | | `sales_team_id` | UUID | SI | FK a sales_teams | | `amount_untaxed` | DECIMAL | NO | Subtotal sin impuestos | | `amount_tax` | DECIMAL | NO | Total impuestos | | `amount_total` | DECIMAL | NO | Total | | `status` | ENUM | NO | draft, sent, sale, done, cancelled | | `invoice_status` | ENUM | NO | pending, partial, invoiced | | `delivery_status` | ENUM | NO | pending, partial, delivered | | `invoice_policy` | ENUM | NO | order, delivery | | `picking_id` | UUID | SI | FK a inventory.pickings | | `notes` | TEXT | SI | Notas internas | | `terms_conditions` | TEXT | SI | Terminos y condiciones | | `created_by` | UUID | SI | Usuario que creo | | `created_at` | TIMESTAMP | NO | Fecha de creacion | | `confirmed_at` | TIMESTAMP | SI | Fecha de confirmacion | | `confirmed_by` | UUID | SI | Usuario que confirmo | | `cancelled_at` | TIMESTAMP | SI | Fecha de cancelacion | | `cancelled_by` | UUID | SI | Usuario que cancelo | | `updated_by` | UUID | SI | Usuario que actualizo | | `updated_at` | TIMESTAMP | SI | Fecha de actualizacion | ### 2.8 SalesOrderLine **Schema:** `sales.sales_order_lines` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `order_id` | UUID | NO | FK a sales_orders | | `tenant_id` | UUID | NO | Tenant | | `product_id` | UUID | NO | FK a inventory.products | | `description` | TEXT | NO | Descripcion | | `quantity` | DECIMAL | NO | Cantidad | | `qty_delivered` | DECIMAL | NO | Cantidad entregada | | `qty_invoiced` | DECIMAL | NO | Cantidad facturada | | `uom_id` | UUID | NO | FK a core.uom | | `price_unit` | DECIMAL | NO | Precio unitario | | `discount` | DECIMAL | NO | Porcentaje descuento | | `tax_ids` | UUID[] | NO | Array de FK a financial.taxes | | `amount_untaxed` | DECIMAL | NO | Subtotal sin impuestos | | `amount_tax` | DECIMAL | NO | Total impuestos | | `amount_total` | DECIMAL | NO | Total | | `analytic_account_id` | UUID | SI | FK a financial.analytic_accounts | | `created_at` | TIMESTAMP | NO | Fecha de creacion | | `updated_at` | TIMESTAMP | SI | Fecha de actualizacion | ### 2.9 Quotation **Schema:** `sales.quotations` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `tenant_id` | UUID | NO | Tenant | | `company_id` | UUID | NO | FK a auth.companies | | `name` | VARCHAR(50) | NO | Numero de cotizacion (QUO-XXXXXX) | | `partner_id` | UUID | NO | FK a core.partners | | `quotation_date` | DATE | NO | Fecha de cotizacion | | `validity_date` | DATE | NO | Fecha de validez | | `currency_id` | UUID | NO | FK a core.currencies | | `pricelist_id` | UUID | SI | FK a pricelists | | `user_id` | UUID | SI | FK a auth.users (vendedor) | | `sales_team_id` | UUID | SI | FK a sales_teams | | `amount_untaxed` | DECIMAL | NO | Subtotal sin impuestos | | `amount_tax` | DECIMAL | NO | Total impuestos | | `amount_total` | DECIMAL | NO | Total | | `status` | ENUM | NO | draft, sent, confirmed, cancelled, expired | | `sale_order_id` | UUID | SI | FK a sales_orders (orden generada) | | `notes` | TEXT | SI | Notas internas | | `terms_conditions` | TEXT | SI | Terminos y condiciones | | `created_by` | UUID | SI | Usuario que creo | | `created_at` | TIMESTAMP | NO | Fecha de creacion | | `updated_by` | UUID | SI | Usuario que actualizo | | `updated_at` | TIMESTAMP | SI | Fecha de actualizacion | ### 2.10 QuotationLine **Schema:** `sales.quotation_lines` | Columna | Tipo | Nullable | Descripcion | |---------|------|----------|-------------| | `id` | UUID | NO | Identificador unico | | `quotation_id` | UUID | NO | FK a quotations | | `tenant_id` | UUID | NO | Tenant | | `product_id` | UUID | SI | FK a inventory.products | | `description` | TEXT | NO | Descripcion | | `quantity` | DECIMAL | NO | Cantidad | | `uom_id` | UUID | NO | FK a core.uom | | `price_unit` | DECIMAL | NO | Precio unitario | | `discount` | DECIMAL | NO | Porcentaje descuento | | `tax_ids` | UUID[] | NO | Array de FK a financial.taxes | | `amount_untaxed` | DECIMAL | NO | Subtotal sin impuestos | | `amount_tax` | DECIMAL | NO | Total impuestos | | `amount_total` | DECIMAL | NO | Total | | `created_at` | TIMESTAMP | NO | Fecha de creacion | --- ## 3. DTOs ### 3.1 CustomerGroups DTOs ```typescript interface CreateCustomerGroupDto { name: string; // Requerido, max 255 description?: string; // Opcional discount_percentage?: number; // 0-100, default: 0 } interface UpdateCustomerGroupDto { name?: string; // max 255 description?: string | null; discount_percentage?: number; // 0-100 } interface CustomerGroupFilters { search?: string; page?: number; // default: 1 limit?: number; // default: 20, max: 100 } ``` ### 3.2 SalesTeams DTOs ```typescript interface CreateSalesTeamDto { company_id: string; // UUID, requerido name: string; // Requerido, max 255 code?: string; // max 50 team_leader_id?: string; // UUID target_monthly?: number; // > 0 target_annual?: number; // > 0 } interface UpdateSalesTeamDto { name?: string; code?: string; team_leader_id?: string | null; target_monthly?: number | null; target_annual?: number | null; active?: boolean; } interface SalesTeamFilters { company_id?: string; active?: boolean; page?: number; limit?: number; } ``` ### 3.3 Pricelists DTOs ```typescript interface CreatePricelistDto { company_id?: string; // UUID name: string; // Requerido, max 255 currency_id: string; // UUID, requerido } interface UpdatePricelistDto { name?: string; currency_id?: string; active?: boolean; } interface CreatePricelistItemDto { product_id?: string; // UUID (uno de product_id o product_category_id) product_category_id?: string; // UUID price: number; // >= 0 min_quantity?: number; // > 0, default: 1 valid_from?: string; // ISO date valid_to?: string; // ISO date } interface PricelistFilters { company_id?: string; active?: boolean; page?: number; limit?: number; } ``` ### 3.4 Orders DTOs ```typescript interface CreateSalesOrderDto { company_id: string; // UUID, requerido partner_id: string; // UUID, requerido client_order_ref?: string; // max 100 order_date?: string; // ISO date validity_date?: string; // ISO date commitment_date?: string; // ISO date currency_id: string; // UUID, requerido pricelist_id?: string; // UUID payment_term_id?: string; // UUID sales_team_id?: string; // UUID invoice_policy?: 'order' | 'delivery'; // default: 'order' notes?: string; terms_conditions?: string; } interface UpdateSalesOrderDto { partner_id?: string; client_order_ref?: string | null; order_date?: string; validity_date?: string | null; commitment_date?: string | null; currency_id?: string; pricelist_id?: string | null; payment_term_id?: string | null; sales_team_id?: string | null; invoice_policy?: 'order' | 'delivery'; notes?: string | null; terms_conditions?: string | null; } interface CreateSalesOrderLineDto { product_id: string; // UUID, requerido description: string; // Requerido quantity: number; // > 0 uom_id: string; // UUID, requerido price_unit: number; // >= 0 discount?: number; // 0-100, default: 0 tax_ids?: string[]; // UUID[] analytic_account_id?: string; // UUID } interface UpdateSalesOrderLineDto { description?: string; quantity?: number; uom_id?: string; price_unit?: number; discount?: number; tax_ids?: string[]; analytic_account_id?: string | null; } interface SalesOrderFilters { company_id?: string; partner_id?: string; status?: 'draft' | 'sent' | 'sale' | 'done' | 'cancelled'; invoice_status?: 'pending' | 'partial' | 'invoiced'; delivery_status?: 'pending' | 'partial' | 'delivered'; date_from?: string; date_to?: string; search?: string; page?: number; limit?: number; } ``` ### 3.5 Quotations DTOs ```typescript interface CreateQuotationDto { company_id: string; // UUID, requerido partner_id: string; // UUID, requerido quotation_date?: string; // ISO date validity_date: string; // ISO date, requerido currency_id: string; // UUID, requerido pricelist_id?: string; // UUID sales_team_id?: string; // UUID notes?: string; terms_conditions?: string; } interface UpdateQuotationDto { partner_id?: string; quotation_date?: string; validity_date?: string; currency_id?: string; pricelist_id?: string | null; sales_team_id?: string | null; notes?: string | null; terms_conditions?: string | null; } interface CreateQuotationLineDto { product_id?: string; // UUID description: string; // Requerido quantity: number; // > 0 uom_id: string; // UUID, requerido price_unit: number; // >= 0 discount?: number; // 0-100, default: 0 tax_ids?: string[]; // UUID[] } interface UpdateQuotationLineDto { description?: string; quantity?: number; uom_id?: string; price_unit?: number; discount?: number; tax_ids?: string[]; } interface QuotationFilters { company_id?: string; partner_id?: string; status?: 'draft' | 'sent' | 'confirmed' | 'cancelled' | 'expired'; date_from?: string; date_to?: string; search?: string; page?: number; limit?: number; } ``` --- ## 4. ENDPOINTS ### 4.1 Customer Groups | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/sales/customer-groups` | Listar grupos de clientes | | GET | `/api/sales/customer-groups/:id` | Obtener grupo por ID | | POST | `/api/sales/customer-groups` | Crear grupo de clientes | | PATCH | `/api/sales/customer-groups/:id` | Actualizar grupo | | DELETE | `/api/sales/customer-groups/:id` | Eliminar grupo | | POST | `/api/sales/customer-groups/:id/members` | Agregar miembro | | DELETE | `/api/sales/customer-groups/:id/members/:memberId` | Eliminar miembro | ### 4.2 Sales Teams | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/sales/teams` | Listar equipos de ventas | | GET | `/api/sales/teams/:id` | Obtener equipo por ID | | POST | `/api/sales/teams` | Crear equipo de ventas | | PATCH | `/api/sales/teams/:id` | Actualizar equipo | | POST | `/api/sales/teams/:id/members` | Agregar miembro | | DELETE | `/api/sales/teams/:id/members/:memberId` | Eliminar miembro | ### 4.3 Pricelists | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/sales/pricelists` | Listar listas de precios | | GET | `/api/sales/pricelists/:id` | Obtener lista por ID | | POST | `/api/sales/pricelists` | Crear lista de precios | | PATCH | `/api/sales/pricelists/:id` | Actualizar lista | | POST | `/api/sales/pricelists/:id/items` | Agregar item | | DELETE | `/api/sales/pricelists/:id/items/:itemId` | Eliminar item | ### 4.4 Sales Orders | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/sales/orders` | Listar ordenes de venta | | GET | `/api/sales/orders/:id` | Obtener orden por ID | | POST | `/api/sales/orders` | Crear orden de venta | | PATCH | `/api/sales/orders/:id` | Actualizar orden | | DELETE | `/api/sales/orders/:id` | Eliminar orden | | POST | `/api/sales/orders/:id/lines` | Agregar linea | | PATCH | `/api/sales/orders/:id/lines/:lineId` | Actualizar linea | | DELETE | `/api/sales/orders/:id/lines/:lineId` | Eliminar linea | | POST | `/api/sales/orders/:id/confirm` | Confirmar orden | | POST | `/api/sales/orders/:id/cancel` | Cancelar orden | | POST | `/api/sales/orders/:id/invoice` | Crear factura | ### 4.5 Quotations | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/sales/quotations` | Listar cotizaciones | | GET | `/api/sales/quotations/:id` | Obtener cotizacion por ID | | POST | `/api/sales/quotations` | Crear cotizacion | | PATCH | `/api/sales/quotations/:id` | Actualizar cotizacion | | DELETE | `/api/sales/quotations/:id` | Eliminar cotizacion | | POST | `/api/sales/quotations/:id/lines` | Agregar linea | | PATCH | `/api/sales/quotations/:id/lines/:lineId` | Actualizar linea | | DELETE | `/api/sales/quotations/:id/lines/:lineId` | Eliminar linea | | POST | `/api/sales/quotations/:id/send` | Enviar cotizacion | | POST | `/api/sales/quotations/:id/confirm` | Confirmar y crear orden | | POST | `/api/sales/quotations/:id/cancel` | Cancelar cotizacion | --- ## 5. TESTS ### 5.1 Estado de Tests | Servicio | Archivo | Estado | |----------|---------|--------| | CustomerGroupsService | `__tests__/customer-groups.service.spec.ts` | Implementado | | SalesTeamsService | `__tests__/sales-teams.service.spec.ts` | Implementado | | PricelistsService | `__tests__/pricelists.service.spec.ts` | Implementado | | OrdersService | `__tests__/orders.service.spec.ts` | Implementado | | QuotationsService | `__tests__/quotations.service.spec.ts` | Implementado | ### 5.2 Cobertura Todos los servicios cuentan con tests unitarios que cubren: - CRUD basico - Validaciones de negocio - Manejo de errores - Flujos de estado (confirmar, cancelar, etc.) --- ## 6. DEPENDENCIAS ### 6.1 Modulos Internos | Modulo | Uso | |--------|-----| | `auth` | Usuarios, empresas, autenticacion | | `core` | Partners, currencies, UoM, product_categories, sequences | | `inventory` | Products | | `financial` | Taxes (calculo impuestos), invoices, payment_terms, analytic_accounts | ### 6.2 Servicios Externos | Servicio | Uso | |----------|-----| | `taxesService` | Calculo de impuestos en lineas de orden/cotizacion | | `sequencesService` | Generacion de numeros de secuencia (SO-XXXXXX) | | `emailService` | Envio de cotizaciones por email | ### 6.3 Dependencias de Base de Datos ``` sales.customer_groups └── core.partners (via customer_group_members) sales.sales_teams ├── auth.companies └── auth.users (team_leader, members) sales.pricelists ├── auth.companies ├── core.currencies ├── inventory.products (items) └── core.product_categories (items) sales.sales_orders ├── auth.companies ├── core.partners ├── core.currencies ├── sales.pricelists ├── sales.sales_teams ├── auth.users ├── financial.payment_terms ├── inventory.products (lines) ├── core.uom (lines) └── financial.taxes (lines) sales.quotations ├── (mismas dependencias que sales_orders) └── sales.sales_orders (sale_order_id) ``` --- ## 7. FLUJOS DE NEGOCIO ### 7.1 Flujo de Cotizacion ``` draft -> sent -> confirmed -> [crea sales_order] -> cancelled -> expired ``` ### 7.2 Flujo de Orden de Venta ``` draft -> sent -> sale -> done -> cancelled invoice_status: pending -> partial -> invoiced delivery_status: pending -> partial -> delivered ``` ### 7.3 Reglas de Negocio 1. **Ordenes/Cotizaciones**: Solo se pueden editar/eliminar en estado `draft` 2. **Grupos de clientes**: No se pueden eliminar si tienen miembros 3. **Equipos de ventas**: Codigo unico por empresa 4. **Listas de precios**: Nombre unico por tenant 5. **Facturacion**: Segun `invoice_policy` (order: al confirmar, delivery: al entregar) 6. **Impuestos**: Calculados automaticamente usando `taxesService` --- ## 8. VALIDACIONES ### 8.1 Validaciones con Zod Todas las validaciones de entrada se realizan usando Zod en el controlador: - Tipos de datos - Campos requeridos - Rangos (min, max) - Formatos (UUID, email, fechas) - Valores permitidos (enums) ### 8.2 Validaciones de Negocio Las validaciones de negocio se realizan en los servicios: - Unicidad de nombres/codigos - Estados validos para operaciones - Existencia de entidades relacionadas - Restricciones de eliminacion