Gestión de errores en módulos de servidor

This commit is contained in:
David Arranz 2025-11-06 17:17:00 +01:00
parent 55983f0295
commit 2c84dc26bd
17 changed files with 101 additions and 25 deletions

View File

@ -1,2 +1,2 @@
export * from "./customer-invoice-id-already-exits-error"; export * from "./customer-invoice-id-already-exits-error";
export * from "./invalid-proforma-status-error"; export * from "./proforma-cannot-be-converted-to-invoice-error";

View File

@ -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;

View File

@ -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;

View File

@ -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 { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto";
import { CreateCustomerInvoiceUseCase } from "../../../application"; import { CreateCustomerInvoiceUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class CreateCustomerInvoiceController extends ExpressController { export class CreateCustomerInvoiceController extends ExpressController {
public constructor(private readonly useCase: CreateCustomerInvoiceUseCase) { public constructor(private readonly useCase: CreateCustomerInvoiceUseCase) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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 { DeleteCustomerInvoiceUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class DeleteCustomerInvoiceController extends ExpressController { export class DeleteCustomerInvoiceController extends ExpressController {
public constructor( public constructor(
@ -7,6 +8,8 @@ export class DeleteCustomerInvoiceController extends ExpressController {
/* private readonly presenter: any */ /* private readonly presenter: any */
) { ) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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 { GetCustomerInvoiceUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class GetCustomerInvoiceController extends ExpressController { export class GetCustomerInvoiceController extends ExpressController {
public constructor(private readonly useCase: GetCustomerInvoiceUseCase) { public constructor(private readonly useCase: GetCustomerInvoiceUseCase) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -1,9 +1,12 @@
import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
import { IssueCustomerInvoiceUseCase } from "../../../application"; import { IssueCustomerInvoiceUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class IssueCustomerInvoiceController extends ExpressController { export class IssueCustomerInvoiceController extends ExpressController {
public constructor(private readonly useCase: IssueCustomerInvoiceUseCase) { public constructor(private readonly useCase: IssueCustomerInvoiceUseCase) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -1,10 +1,13 @@
import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { ListCustomerInvoicesUseCase } from "../../../application"; import { ListCustomerInvoicesUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class ListCustomerInvoicesController extends ExpressController { export class ListCustomerInvoicesController extends ExpressController {
public constructor(private readonly useCase: ListCustomerInvoicesUseCase) { public constructor(private readonly useCase: ListCustomerInvoicesUseCase) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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 { ReportCustomerInvoiceUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class ReportCustomerInvoiceController extends ExpressController { export class ReportCustomerInvoiceController extends ExpressController {
public constructor(private readonly useCase: ReportCustomerInvoiceUseCase) { public constructor(private readonly useCase: ReportCustomerInvoiceUseCase) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -1,10 +1,13 @@
import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
import { UpdateCustomerInvoiceByIdRequestDTO } from "../../../../common/dto"; import { UpdateCustomerInvoiceByIdRequestDTO } from "../../../../common/dto";
import { UpdateCustomerInvoiceUseCase } from "../../../application"; import { UpdateCustomerInvoiceUseCase } from "../../../application";
import { customerInvoicesApiErrorMapper } from "../customer-invoices-api-error-mapper";
export class UpdateCustomerInvoiceController extends ExpressController { export class UpdateCustomerInvoiceController extends ExpressController {
public constructor(private readonly useCase: UpdateCustomerInvoiceUseCase) { public constructor(private readonly useCase: UpdateCustomerInvoiceUseCase) {
super(); super();
this.errorMapper = customerInvoicesApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -1,10 +1,17 @@
// Ejemplo: regla específica para Billing → InvoiceIdAlreadyExistsError // Ejemplo: regla específica para Billing → InvoiceIdAlreadyExistsError
// (si defines un error más ubicuo dentro del BC con su propia clase) // (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 { import {
CustomerInvoiceIdAlreadyExistsError, CustomerInvoiceIdAlreadyExistsError,
isCustomerInvoiceIdAlreadyExistsError, isCustomerInvoiceIdAlreadyExistsError,
isProformaCannotBeConvertedToInvoiceError,
ProformaCannotBeConvertedToInvoiceError,
} from "../../domain"; } from "../../domain";
// Crea una regla específica (prioridad alta para sobreescribir mensajes) // 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 // Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
export const customerInvoicesApiErrorMapper: ApiErrorMapper = export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
ApiErrorMapper.default().register(invoiceDuplicateRule); .register(invoiceDuplicateRule)
.register(proformaConversionRule);

View File

@ -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 { CreateCustomerRequestDTO } from "../../../../common/dto";
import { CreateCustomerUseCase } from "../../../application"; import { CreateCustomerUseCase } from "../../../application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
export class CreateCustomerController extends ExpressController { export class CreateCustomerController extends ExpressController {
public constructor(private readonly useCase: CreateCustomerUseCase) { public constructor(private readonly useCase: CreateCustomerUseCase) {
super(); super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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 { DeleteCustomerUseCase } from "../../../application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
export class DeleteCustomerController extends ExpressController { export class DeleteCustomerController extends ExpressController {
public constructor(private readonly useCase: DeleteCustomerUseCase) { public constructor(private readonly useCase: DeleteCustomerUseCase) {
super(); super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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 { GetCustomerUseCase } from "../../../application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
export class GetCustomerController extends ExpressController { export class GetCustomerController extends ExpressController {
public constructor(private readonly useCase: GetCustomerUseCase) { public constructor(private readonly useCase: GetCustomerUseCase) {
super(); super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -1,10 +1,13 @@
import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import { authGuard, ExpressController, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { ListCustomersUseCase } from "../../../application"; import { ListCustomersUseCase } from "../../../application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
export class ListCustomersController extends ExpressController { export class ListCustomersController extends ExpressController {
public constructor(private readonly listCustomers: ListCustomersUseCase) { public constructor(private readonly listCustomers: ListCustomersUseCase) {
super(); super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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 { UpdateCustomerByIdRequestDTO } from "../../../../common/dto";
import { UpdateCustomerUseCase } from "../../../application"; import { UpdateCustomerUseCase } from "../../../application";
import { customersApiErrorMapper } from "../customer-api-error-mapper";
export class UpdateCustomerController extends ExpressController { export class UpdateCustomerController extends ExpressController {
public constructor(private readonly useCase: UpdateCustomerUseCase) { public constructor(private readonly useCase: UpdateCustomerUseCase) {
super(); super();
this.errorMapper = customersApiErrorMapper;
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); this.registerGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
} }

View File

@ -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"; import { CustomerNotFoundError, isCustomerNotFoundError } from "../../domain";
// Crea una regla específica (prioridad alta para sobreescribir mensajes) // Crea una regla específica (prioridad alta para sobreescribir mensajes)
@ -6,7 +6,7 @@ const customerNotFoundRule: ErrorToApiRule = {
priority: 120, priority: 120,
matches: (e) => isCustomerNotFoundError(e), matches: (e) => isCustomerNotFoundError(e),
build: (e) => build: (e) =>
new ConflictApiError( new NotFoundApiError(
(e as CustomerNotFoundError).message || "Customer with the provided id not exists." (e as CustomerNotFoundError).message || "Customer with the provided id not exists."
), ),
}; };