From 2c84dc26bd7604fe2c98f34d2f7110d12c7e1a93 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 6 Nov 2025 17:17:00 +0100 Subject: [PATCH] =?UTF-8?q?Gesti=C3=B3n=20de=20errores=20en=20m=C3=B3dulos?= =?UTF-8?q?=20de=20servidor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/domain/errors/index.ts | 2 +- .../errors/invalid-proforma-status-error.ts | 11 ------- ...ma-cannot-be-converted-to-invoice-error.ts | 33 +++++++++++++++++++ .../create-customer-invoice.controller.ts | 5 ++- .../delete-customer-invoice.controller.ts | 5 ++- .../get-customer-invoice.controller.ts | 5 ++- .../issue-customer-invoice.controller.ts | 3 ++ .../list-customer-invoices.controller.ts | 3 ++ .../report-customer-invoice.controller.ts | 5 ++- .../update-customer-invoice.controller.ts | 3 ++ .../customer-invoices-api-error-mapper.ts | 24 ++++++++++++-- .../controllers/create-customer.controller.ts | 5 ++- .../controllers/delete-customer.controller.ts | 5 ++- .../controllers/get-customer.controller.ts | 5 ++- .../controllers/list-customers.controller.ts | 3 ++ .../controllers/update-customer.controller.ts | 5 ++- .../express/customer-api-error-mapper.ts | 4 +-- 17 files changed, 101 insertions(+), 25 deletions(-) delete mode 100644 modules/customer-invoices/src/api/domain/errors/invalid-proforma-status-error.ts create mode 100644 modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-converted-to-invoice-error.ts diff --git a/modules/customer-invoices/src/api/domain/errors/index.ts b/modules/customer-invoices/src/api/domain/errors/index.ts index 45c0ddec..7997156f 100644 --- a/modules/customer-invoices/src/api/domain/errors/index.ts +++ b/modules/customer-invoices/src/api/domain/errors/index.ts @@ -1,2 +1,2 @@ export * from "./customer-invoice-id-already-exits-error"; -export * from "./invalid-proforma-status-error"; +export * from "./proforma-cannot-be-converted-to-invoice-error"; diff --git a/modules/customer-invoices/src/api/domain/errors/invalid-proforma-status-error.ts b/modules/customer-invoices/src/api/domain/errors/invalid-proforma-status-error.ts deleted file mode 100644 index 970b4af8..00000000 --- a/modules/customer-invoices/src/api/domain/errors/invalid-proforma-status-error.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DomainError } from "@repo/rdx-ddd"; - -export class InvalidProformaStatusError extends DomainError { - constructor(id: string, options?: ErrorOptions) { - super(`Error. Proforma with id '${id}' has invalid status.`, options); - this.name = "InvalidProformaStatusError"; - } -} - -export const isInvalidProformaStatusError = (e: unknown): e is InvalidProformaStatusError => - e instanceof InvalidProformaStatusError; diff --git a/modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-converted-to-invoice-error.ts b/modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-converted-to-invoice-error.ts new file mode 100644 index 00000000..3205f16a --- /dev/null +++ b/modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-converted-to-invoice-error.ts @@ -0,0 +1,33 @@ +import { DomainError } from "@repo/rdx-ddd"; + +/** + * Error de dominio que indica que una Proforma no puede convertirse a Factura (issue). + * + * @remarks + * - Se lanza cuando el flujo de emisión (issue) desde una Proforma no es válido + * + * @public + */ +export class ProformaCannotBeConvertedToInvoiceError extends DomainError { + /** + * Crea una instancia del error con el identificador de la Proforma. + * + * @param id - Identificador de la Proforma. + * @param options - Opciones nativas de Error (puedes pasar `cause`). + */ + constructor(id: string, options?: ErrorOptions) { + super(`Error. Proforma with id '${id}' cannot be converted to an Invoice.`, options); + this.name = "ProformaCannotBeConvertedToInvoiceError"; + } +} + +/** + * *Type guard* para `ProformaCannotBeConvertedToInvoiceError`. + * + * @param e - Error desconocido + * @returns `true` si `e` es `ProformaCannotBeConvertedToInvoiceError` + */ +export const isProformaCannotBeConvertedToInvoiceError = ( + e: unknown +): e is ProformaCannotBeConvertedToInvoiceError => + e instanceof ProformaCannotBeConvertedToInvoiceError; diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts index 923434d6..a76b70da 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/create-customer-invoice.controller.ts @@ -1,11 +1,14 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto"; import { CreateCustomerInvoiceUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class CreateCustomerInvoiceController extends ExpressController { public constructor(private readonly useCase: CreateCustomerInvoiceUseCase) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/delete-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/delete-customer-invoice.controller.ts index 5b76ee54..15c0a036 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/delete-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/delete-customer-invoice.controller.ts @@ -1,5 +1,6 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { DeleteCustomerInvoiceUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class DeleteCustomerInvoiceController extends ExpressController { public constructor( @@ -7,6 +8,8 @@ export class DeleteCustomerInvoiceController extends ExpressController { /* private readonly presenter: any */ ) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts index 9d2e9cc6..69af2070 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/get-customer-invoice.controller.ts @@ -1,9 +1,12 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { GetCustomerInvoiceUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class GetCustomerInvoiceController extends ExpressController { public constructor(private readonly useCase: GetCustomerInvoiceUseCase) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/issue-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/issue-customer-invoice.controller.ts index 6ba8cb9b..9fa13873 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/issue-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/issue-customer-invoice.controller.ts @@ -1,9 +1,12 @@ import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { IssueCustomerInvoiceUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class IssueCustomerInvoiceController extends ExpressController { public constructor(private readonly useCase: IssueCustomerInvoiceUseCase) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/list-customer-invoices.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/list-customer-invoices.controller.ts index dc90c4db..696c43ba 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/list-customer-invoices.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/list-customer-invoices.controller.ts @@ -1,10 +1,13 @@ import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { Criteria } from "@repo/rdx-criteria/server"; import { ListCustomerInvoicesUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class ListCustomerInvoicesController extends ExpressController { public constructor(private readonly useCase: ListCustomerInvoicesUseCase) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/report-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/report-customer-invoice.controller.ts index dae8fc88..90863b19 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/report-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/report-customer-invoice.controller.ts @@ -1,9 +1,12 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { ReportCustomerInvoiceUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class ReportCustomerInvoiceController extends ExpressController { public constructor(private readonly useCase: ReportCustomerInvoiceUseCase) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts index 6651cbb6..21576af3 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts @@ -1,10 +1,13 @@ import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { UpdateCustomerInvoiceByIdRequestDTO } from "../../../../common/dto"; import { UpdateCustomerInvoiceUseCase } from "../../../application"; +import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper"; export class UpdateCustomerInvoiceController extends ExpressController { public constructor(private readonly useCase: UpdateCustomerInvoiceUseCase) { super(); + this.errorMapper = customerInvoicesApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts index 6c379faf..70b3ca3a 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts @@ -1,10 +1,17 @@ // Ejemplo: regla específica para Billing → InvoiceIdAlreadyExistsError // (si defines un error más ubicuo dentro del BC con su propia clase) -import { ApiErrorMapper, ConflictApiError, ErrorToApiRule } from "@erp/core/api"; +import { + ApiErrorMapper, + ConflictApiError, + ErrorToApiRule, + ValidationApiError, +} from "@erp/core/api"; import { CustomerInvoiceIdAlreadyExistsError, isCustomerInvoiceIdAlreadyExistsError, + isProformaCannotBeConvertedToInvoiceError, + ProformaCannotBeConvertedToInvoiceError, } from "../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) @@ -18,6 +25,17 @@ const invoiceDuplicateRule: ErrorToApiRule = { ), }; +const proformaConversionRule: ErrorToApiRule = { + priority: 120, + matches: (e) => isProformaCannotBeConvertedToInvoiceError(e), + build: (e) => + new ValidationApiError( + (e as ProformaCannotBeConvertedToInvoiceError).message || + "Proforma cannot be converted to an Invoice." + ), +}; + // Cómo aplicarla: crea una nueva instancia del mapper con la regla extra -export const customerInvoicesApiErrorMapper: ApiErrorMapper = - ApiErrorMapper.default().register(invoiceDuplicateRule); +export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default() + .register(invoiceDuplicateRule) + .register(proformaConversionRule); diff --git a/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts index 4caa1eb9..92f26726 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts @@ -1,10 +1,13 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { CreateCustomerRequestDTO } from "../../../../common/dto"; import { CreateCustomerUseCase } from "../../../application"; +import { customersApiErrorMapper } from "../customer-api-error-mapper"; export class CreateCustomerController extends ExpressController { public constructor(private readonly useCase: CreateCustomerUseCase) { super(); + this.errorMapper = customersApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts index ac2483e9..26d98cc2 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts @@ -1,9 +1,12 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { DeleteCustomerUseCase } from "../../../application"; +import { customersApiErrorMapper } from "../customer-api-error-mapper"; export class DeleteCustomerController extends ExpressController { public constructor(private readonly useCase: DeleteCustomerUseCase) { super(); + this.errorMapper = customersApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts index a6a570ee..eb31150b 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts @@ -1,9 +1,12 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { GetCustomerUseCase } from "../../../application"; +import { customersApiErrorMapper } from "../customer-api-error-mapper"; export class GetCustomerController extends ExpressController { public constructor(private readonly useCase: GetCustomerUseCase) { super(); + this.errorMapper = customersApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts index f2159296..c4d67210 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts @@ -1,10 +1,13 @@ import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { Criteria } from "@repo/rdx-criteria/server"; import { ListCustomersUseCase } from "../../../application"; +import { customersApiErrorMapper } from "../customer-api-error-mapper"; export class ListCustomersController extends ExpressController { public constructor(private readonly listCustomers: ListCustomersUseCase) { super(); + this.errorMapper = customersApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customers/src/api/infrastructure/express/controllers/update-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/update-customer.controller.ts index 61db095c..d0bebfc5 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/update-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/update-customer.controller.ts @@ -1,10 +1,13 @@ -import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { UpdateCustomerByIdRequestDTO } from "../../../../common/dto"; import { UpdateCustomerUseCase } from "../../../application"; +import { customersApiErrorMapper } from "../customer-api-error-mapper"; export class UpdateCustomerController extends ExpressController { public constructor(private readonly useCase: UpdateCustomerUseCase) { super(); + this.errorMapper = customersApiErrorMapper; + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } diff --git a/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts b/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts index a895f66a..bf9489b9 100644 --- a/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts +++ b/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts @@ -1,4 +1,4 @@ -import { ApiErrorMapper, ConflictApiError, ErrorToApiRule } from "@erp/core/api"; +import { ApiErrorMapper, ErrorToApiRule, NotFoundApiError } from "@erp/core/api"; import { CustomerNotFoundError, isCustomerNotFoundError } from "../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) @@ -6,7 +6,7 @@ const customerNotFoundRule: ErrorToApiRule = { priority: 120, matches: (e) => isCustomerNotFoundError(e), build: (e) => - new ConflictApiError( + new NotFoundApiError( (e as CustomerNotFoundError).message || "Customer with the provided id not exists." ), };