diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 1aa333fd..c520d919 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -2,6 +2,7 @@ import { logger } from "@/lib/logger"; import { DateTime } from "luxon"; import http from "node:http"; import os from "node:os"; +import * as z from "zod/v4"; import { createApp } from "./app"; import { ENV } from "./config"; import { tryConnectToDatabase } from "./config/database"; @@ -12,6 +13,8 @@ import { registerModules } from "./register-modules"; const API_BASE_PATH = "/api/v1"; +z.config(z.locales.es()); + // Guardamos información del estado del servidor export const currentState = { launchedAt: DateTime.now(), diff --git a/modules/core/src/api/domain/errors/duplicate-entity-error.ts b/modules/core/src/api/domain/errors/duplicate-entity-error.ts index d7072859..698447b0 100644 --- a/modules/core/src/api/domain/errors/duplicate-entity-error.ts +++ b/modules/core/src/api/domain/errors/duplicate-entity-error.ts @@ -1,4 +1,4 @@ -import { DomainError } from "./domain-error"; +import { DomainError } from "@repo/rdx-ddd"; export class DuplicateEntityError extends DomainError { constructor(entity: string, field: string, value: string, options?: ErrorOptions) { diff --git a/modules/core/src/api/domain/errors/entity-not-found-error.ts b/modules/core/src/api/domain/errors/entity-not-found-error.ts index 53782206..4800bd0e 100644 --- a/modules/core/src/api/domain/errors/entity-not-found-error.ts +++ b/modules/core/src/api/domain/errors/entity-not-found-error.ts @@ -1,4 +1,4 @@ -import { DomainError } from "./domain-error"; +import { DomainError } from "@repo/rdx-ddd"; export class EntityNotFoundError extends DomainError { constructor(entity: string, field: string, value: any, options?: ErrorOptions) { diff --git a/modules/core/src/api/domain/errors/index.ts b/modules/core/src/api/domain/errors/index.ts index 349cb2d5..b14b930c 100644 --- a/modules/core/src/api/domain/errors/index.ts +++ b/modules/core/src/api/domain/errors/index.ts @@ -1,5 +1,2 @@ -export * from "./domain-error"; -export * from "./domain-validation-error"; export * from "./duplicate-entity-error"; export * from "./entity-not-found-error"; -export * from "./validation-error-collection"; diff --git a/modules/core/src/api/helpers/index.ts b/modules/core/src/api/helpers/index.ts index 349bd8b5..8b137891 100644 --- a/modules/core/src/api/helpers/index.ts +++ b/modules/core/src/api/helpers/index.ts @@ -1 +1 @@ -export * from "./extract-or-push-error"; + diff --git a/modules/core/src/api/infrastructure/database/transaction-manager.ts b/modules/core/src/api/infrastructure/database/transaction-manager.ts index cab7fe54..1fb108da 100644 --- a/modules/core/src/api/infrastructure/database/transaction-manager.ts +++ b/modules/core/src/api/infrastructure/database/transaction-manager.ts @@ -78,7 +78,7 @@ export abstract class TransactionManager implements ITransactionManager { await this.rollback(); const error = err as Error; this.logger.error(`❌ Transaction rolled back due to error: ${error.message}`, { - stack: error.stack, + //stack: error.stack, label: "TransactionManager.start", }); throw error; diff --git a/modules/core/src/api/infrastructure/express/api-error-mapper.ts b/modules/core/src/api/infrastructure/express/api-error-mapper.ts index 04931d43..24e00cda 100644 --- a/modules/core/src/api/infrastructure/express/api-error-mapper.ts +++ b/modules/core/src/api/infrastructure/express/api-error-mapper.ts @@ -13,13 +13,16 @@ import { DomainValidationError, - DuplicateEntityError, - EntityNotFoundError, ValidationErrorCollection, isDomainValidationError, + isValidationErrorCollection, +} from "@repo/rdx-ddd"; + +import { + DuplicateEntityError, + EntityNotFoundError, isDuplicateEntityError, isEntityNotFoundError, - isValidationErrorCollection, } from "../../domain"; import { isInfrastructureRepositoryError, isInfrastructureUnavailableError } from "../errors"; import { diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts b/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts index c5385bea..c272947d 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts +++ b/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts @@ -1,3 +1,4 @@ +import { DomainValidationError, ValidationErrorCollection } from "@repo/rdx-ddd"; import { ConnectionError, DatabaseError, @@ -5,12 +6,7 @@ import { ValidationError as SequelizeValidationError, UniqueConstraintError, } from "sequelize"; -import { - DomainValidationError, - DuplicateEntityError, - EntityNotFoundError, - ValidationErrorCollection, -} from "../../domain"; +import { DuplicateEntityError, EntityNotFoundError } from "../../domain"; import { InfrastructureRepositoryError } from "../errors/infrastructure-repository-error"; import { InfrastructureUnavailableError } from "../errors/infrastructure-unavailable-error"; diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts b/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts index 915f4a94..d36ccf87 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts +++ b/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts @@ -69,7 +69,7 @@ export class SequelizeTransactionManager extends TransactionManager { } catch (err) { const error = err as Error; this.logger.error(`❌ Transaction rolled back due to error: ${error.message}`, { - stack: error.stack, + //stack: error.stack, label: "SequelizeTransactionManager.complete", }); throw error; diff --git a/modules/core/tsconfig.json b/modules/core/tsconfig.json index 00dfcb5e..d197d298 100644 --- a/modules/core/tsconfig.json +++ b/modules/core/tsconfig.json @@ -28,6 +28,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src"], + "include": ["src", "../../packages/rdx-ddd/src/helpers/extract-or-push-error.ts"], "exclude": ["node_modules"] } diff --git a/modules/customer-invoices/src/api/application/helpers/extract-or-push-error.ts b/modules/customer-invoices/src/api/application/helpers/extract-or-push-error.ts deleted file mode 100644 index 809eb671..00000000 --- a/modules/customer-invoices/src/api/application/helpers/extract-or-push-error.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { DomainValidationError, ValidationErrorDetail } from "@erp/core/api"; -import { Result } from "@repo/rdx-utils"; - -/** - * Extrae un valor de un Result si es válido. - * Si es un fallo, agrega un ValidationErrorDetail al array proporcionado. - * @param result - El resultado a evaluar. - * @param path - La ruta del error para el detalle de validación. - * @param errors - El array donde se agregarán los errores de validación. - * @returns El valor extraído si el resultado es exitoso, o undefined si es un fallo. - * @template T - El tipo de dato esperado en el resultado exitoso. - * @throws {Error} Si el resultado es un fallo y no es una instancia de DomainValidationError. - * @example - * const result = Result.ok(42); - * const value = extractOrPushError(result, 'some.path', []); - * console.log(value); // 42 - * const errorResult = Result.fail(new Error('Something went wrong')); - * const value = extractOrPushError(errorResult, 'some.path', []); - * console.log(value); // undefined - * // errors will contain [{ path: 'some.path', message: 'Something went wrong' }] - * - * @see Result - * @see DomainValidationError - * @see ValidationErrorDetail - - */ -export function extractOrPushError( - result: Result, - path: string, - errors: ValidationErrorDetail[] -): T | undefined { - if (result.isFailure) { - const error = result.error; - - if (error instanceof DomainValidationError) { - errors.push({ path, message: error.detail }); - } else { - errors.push({ path, message: error.message }); - } - - return undefined; - } - - return result.data; -} diff --git a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts index 4c588720..e0369f3b 100644 --- a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts +++ b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts @@ -1,10 +1,8 @@ -import { ValidationErrorCollection, ValidationErrorDetail } from "@erp/core/api"; -import { CreateCustomerInvoiceCommandDTO } from "@erp/customer-invoices/common/dto"; +import { ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { CustomerInvoiceItem, CustomerInvoiceItemDescription, - CustomerInvoiceItemQuantity, ItemAmount, ItemDiscount, } from "../../domain"; diff --git a/modules/customer-invoices/src/api/application/use-cases/create/map-dto-to-create-customer-invoice-props.ts b/modules/customer-invoices/src/api/application/use-cases/create/map-dto-to-create-customer-invoice-props.ts index 988184ea..f35a81bc 100644 --- a/modules/customer-invoices/src/api/application/use-cases/create/map-dto-to-create-customer-invoice-props.ts +++ b/modules/customer-invoices/src/api/application/use-cases/create/map-dto-to-create-customer-invoice-props.ts @@ -1,19 +1,16 @@ import { JsonTaxCatalogProvider } from "@erp/core"; -import { - DomainError, - Tax, - Taxes, - ValidationErrorCollection, - ValidationErrorDetail, - extractOrPushError, -} from "@erp/core/api"; +import { Tax, Taxes } from "@erp/core/api"; import { CurrencyCode, + DomainError, LanguageCode, Percentage, TextValue, UniqueID, UtcDate, + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, maybeFromNullableVO, } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; diff --git a/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts b/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts index fc9f9e03..f744a99a 100644 --- a/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts +++ b/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts @@ -1,7 +1,7 @@ // Ejemplo: regla específica para Billing → InvoiceIdAlreadyExistsError // (si defines un error más ubicuo dentro del BC con su propia clase) -import { DomainError } from "@erp/core/api"; +import { DomainError } from "@repo/rdx-ddd"; // Suponemos que existe esta clase en tu dominio de Billing: export class CustomerInvoiceIdAlreadyExistsError extends DomainError { diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice-item.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice-item.mapper.ts index 946e1fe5..b18737e1 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice-item.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice-item.mapper.ts @@ -1,12 +1,11 @@ +import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { - ISequelizeDomainMapper, - MapperParamsType, - SequelizeDomainMapper, + UniqueID, ValidationErrorCollection, ValidationErrorDetail, extractOrPushError, -} from "@erp/core/api"; -import { UniqueID, maybeFromNullableVO } from "@repo/rdx-ddd"; + maybeFromNullableVO, +} from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { InferCreationAttributes } from "sequelize"; import { diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts index db7d70c1..a72f6ef7 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts @@ -1,11 +1,4 @@ -import { - ISequelizeDomainMapper, - MapperParamsType, - SequelizeDomainMapper, - ValidationErrorCollection, - ValidationErrorDetail, - extractOrPushError, -} from "@erp/core/api"; +import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { CurrencyCode, LanguageCode, @@ -13,6 +6,9 @@ import { TextValue, UniqueID, UtcDate, + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, maybeFromNullableVO, } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts index fb244660..baa0f955 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts @@ -6,15 +6,13 @@ import { Province, Street, TINNumber, - maybeFromNullableVO, -} from "@repo/rdx-ddd"; - -import { - MapperParamsType, ValidationErrorCollection, ValidationErrorDetail, extractOrPushError, -} from "@erp/core/api"; + maybeFromNullableVO, +} from "@repo/rdx-ddd"; + +import { MapperParamsType } from "@erp/core/api"; import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerInvoiceProps, InvoiceRecipient } from "../../../domain"; import { CustomerInvoiceModel } from "../../sequelize"; diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/item-taxes.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/item-taxes.mapper.ts index ba3c1829..e68fbc09 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/item-taxes.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/item-taxes.mapper.ts @@ -1,12 +1,10 @@ import { JsonTaxCatalogProvider } from "@erp/core"; +import { MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api"; import { - MapperParamsType, - SequelizeDomainMapper, - Tax, ValidationErrorCollection, ValidationErrorDetail, extractOrPushError, -} from "@erp/core/api"; +} from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { ItemTax } from "../../../domain"; import { diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/taxes.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/taxes.mapper.ts index 9e9b10a9..69dd472e 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/taxes.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/taxes.mapper.ts @@ -1,12 +1,11 @@ import { JsonTaxCatalogProvider } from "@erp/core"; +import { MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api"; import { - MapperParamsType, - SequelizeDomainMapper, - Tax, ValidationErrorCollection, ValidationErrorDetail, extractOrPushError, -} from "@erp/core/api"; +} from "@repo/rdx-ddd"; + import { Result } from "@repo/rdx-utils"; import { CustomerInvoiceProps } from "../../../domain"; import { InvoiceTax } from "../../../domain/entities/invoice-taxes"; diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/queries/customer-invoice.list.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/queries/customer-invoice.list.mapper.ts index d89ad78f..8263cd26 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/queries/customer-invoice.list.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/queries/customer-invoice.list.mapper.ts @@ -1,11 +1,11 @@ +import { ISequelizeQueryMapper, MapperParamsType, SequelizeQueryMapper } from "@erp/core/api"; + import { - ISequelizeQueryMapper, - MapperParamsType, - SequelizeQueryMapper, ValidationErrorCollection, ValidationErrorDetail, extractOrPushError, -} from "@erp/core/api"; +} from "@repo/rdx-ddd"; + import { CurrencyCode, LanguageCode, diff --git a/modules/customers/src/api/application/use-cases/create/map-dto-to-create-customer-props.ts b/modules/customers/src/api/application/use-cases/create/map-dto-to-create-customer-props.ts index 76e81e61..59edf331 100644 --- a/modules/customers/src/api/application/use-cases/create/map-dto-to-create-customer-props.ts +++ b/modules/customers/src/api/application/use-cases/create/map-dto-to-create-customer-props.ts @@ -1,13 +1,8 @@ -import { - DomainError, - ValidationErrorCollection, - ValidationErrorDetail, - extractOrPushError, -} from "@erp/core/api"; import { City, Country, CurrencyCode, + DomainError, EmailAddress, LanguageCode, Name, @@ -21,6 +16,9 @@ import { TextValue, URLAddress, UniqueID, + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, maybeFromNullableVO, } from "@repo/rdx-ddd"; import { Collection, Result, isNullishOrEmpty } from "@repo/rdx-utils"; diff --git a/modules/customers/src/api/application/use-cases/update/map-dto-to-update-customer-props.ts b/modules/customers/src/api/application/use-cases/update/map-dto-to-update-customer-props.ts index 79fd2adb..294fe828 100644 --- a/modules/customers/src/api/application/use-cases/update/map-dto-to-update-customer-props.ts +++ b/modules/customers/src/api/application/use-cases/update/map-dto-to-update-customer-props.ts @@ -1,9 +1,5 @@ -import { - DomainError, - ValidationErrorCollection, - ValidationErrorDetail, - extractOrPushError, -} from "@erp/core/api"; +import { DomainError, ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd"; + import { City, Country, @@ -23,8 +19,6 @@ import { maybeFromNullableVO, } from "@repo/rdx-ddd"; import { Collection, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; -import { UpdateCustomerRequestDTO } from "../../../common/dto"; -import { CustomerPatchProps } from "../../domain"; /** * mapDTOToUpdateCustomerPatchProps diff --git a/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts b/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts index 785416ef..c1abdf32 100644 --- a/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts +++ b/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts @@ -1,11 +1,6 @@ -import { - ISequelizeDomainMapper, - MapperParamsType, - SequelizeDomainMapper, - ValidationErrorCollection, - ValidationErrorDetail, - extractOrPushError, -} from "@erp/core/api"; +import { ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd"; + +import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { City, Country, @@ -23,6 +18,7 @@ import { TextValue, URLAddress, UniqueID, + extractOrPushError, maybeFromNullableVO, toNullable, } from "@repo/rdx-ddd"; diff --git a/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts b/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts index 46b70466..de48b466 100644 --- a/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts +++ b/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts @@ -1,3 +1,5 @@ +import { ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd"; + import { City, Country, @@ -14,17 +16,11 @@ import { TextValue, URLAddress, UniqueID, + extractOrPushError, maybeFromNullableVO, } from "@repo/rdx-ddd"; -import { - ISequelizeQueryMapper, - MapperParamsType, - SequelizeQueryMapper, - ValidationErrorCollection, - ValidationErrorDetail, - extractOrPushError, -} from "@erp/core/api"; +import { ISequelizeQueryMapper, MapperParamsType, SequelizeQueryMapper } from "@erp/core/api"; import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerStatus } from "../../../domain"; diff --git a/modules/customers/tsconfig.json b/modules/customers/tsconfig.json index 5f79ccf1..ef8f21af 100644 --- a/modules/customers/tsconfig.json +++ b/modules/customers/tsconfig.json @@ -28,6 +28,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src", "../core/src/api/helpers/extract-or-push-error.ts"], + "include": ["src", "../../packages/rdx-ddd/src/helpers/extract-or-push-error.ts"], "exclude": ["node_modules"] } diff --git a/modules/core/src/api/domain/errors/domain-error.ts b/packages/rdx-ddd/src/errors/domain-error.ts similarity index 100% rename from modules/core/src/api/domain/errors/domain-error.ts rename to packages/rdx-ddd/src/errors/domain-error.ts diff --git a/modules/core/src/api/domain/errors/domain-validation-error.ts b/packages/rdx-ddd/src/errors/domain-validation-error.ts similarity index 82% rename from modules/core/src/api/domain/errors/domain-validation-error.ts rename to packages/rdx-ddd/src/errors/domain-validation-error.ts index c233b2ba..5631888c 100644 --- a/modules/core/src/api/domain/errors/domain-validation-error.ts +++ b/packages/rdx-ddd/src/errors/domain-validation-error.ts @@ -35,12 +35,20 @@ export class DomainValidationError extends DomainError { } // Constructores rápidos - static required(field: string, options?: ErrorOptions) { - return new DomainValidationError("REQUIRED", field, "cannot be empty", options); + static requiredValue(field: string, options?: ErrorOptions) { + return new DomainValidationError("REQUIRED_VALUE", field, "cannot be empty", options); } static invalidFormat(field: string, detail = "invalid format", options?: ErrorOptions) { return new DomainValidationError("INVALID_FORMAT", field, detail, options); } + static invalidValue( + field: string, + value: unknown, + detail = "invalid value", + options?: ErrorOptions + ) { + return new DomainValidationError("INVALID_VALUE", field, detail, { ...options, cause: value }); + } // Proyección útil para Problem+JSON o colecciones toDetail() { diff --git a/packages/rdx-ddd/src/errors/index.ts b/packages/rdx-ddd/src/errors/index.ts new file mode 100644 index 00000000..93ab6a81 --- /dev/null +++ b/packages/rdx-ddd/src/errors/index.ts @@ -0,0 +1,3 @@ +export * from "./domain-error"; +export * from "./domain-validation-error"; +export * from "./validation-error-collection"; diff --git a/modules/core/src/api/domain/errors/validation-error-collection.ts b/packages/rdx-ddd/src/errors/validation-error-collection.ts similarity index 93% rename from modules/core/src/api/domain/errors/validation-error-collection.ts rename to packages/rdx-ddd/src/errors/validation-error-collection.ts index 67f7fe31..aa341fd1 100644 --- a/modules/core/src/api/domain/errors/validation-error-collection.ts +++ b/packages/rdx-ddd/src/errors/validation-error-collection.ts @@ -18,8 +18,9 @@ import { DomainError } from "./domain-error"; export interface ValidationErrorDetail { - path: string; // ejemplo: "lines[1].unitPrice.amount" + path?: string; // ejemplo: "lines[1].unitPrice.amount" message: string; // ejemplo: "Amount must be a positive number", + value?: unknown; // valor inválido opcional } /** diff --git a/modules/core/src/api/helpers/extract-or-push-error.ts b/packages/rdx-ddd/src/helpers/extract-or-push-error.ts similarity index 76% rename from modules/core/src/api/helpers/extract-or-push-error.ts rename to packages/rdx-ddd/src/helpers/extract-or-push-error.ts index 809eb671..c2979c1b 100644 --- a/modules/core/src/api/helpers/extract-or-push-error.ts +++ b/packages/rdx-ddd/src/helpers/extract-or-push-error.ts @@ -1,5 +1,10 @@ -import { DomainValidationError, ValidationErrorDetail } from "@erp/core/api"; import { Result } from "@repo/rdx-utils"; +import { + DomainValidationError, + ValidationErrorDetail, + isDomainValidationError, + isValidationErrorCollection, +} from "../errors"; /** * Extrae un valor de un Result si es válido. @@ -32,8 +37,11 @@ export function extractOrPushError( if (result.isFailure) { const error = result.error; - if (error instanceof DomainValidationError) { - errors.push({ path, message: error.detail }); + if (isValidationErrorCollection(error)) { + // Agrega todos los detalles de error al array proporcionado + errors.push(...error.details); + } else if (isDomainValidationError(error)) { + errors.push({ path, message: error.detail, value: error.cause?.toString() }); } else { errors.push({ path, message: error.message }); } diff --git a/packages/rdx-ddd/src/helpers/index.ts b/packages/rdx-ddd/src/helpers/index.ts index c9873891..730afc6f 100644 --- a/packages/rdx-ddd/src/helpers/index.ts +++ b/packages/rdx-ddd/src/helpers/index.ts @@ -1 +1,3 @@ +export * from "./extract-or-push-error"; export * from "./normalizers"; +export * from "./zod-validator-error-traslator"; diff --git a/packages/rdx-ddd/src/helpers/zod-validator-error-traslator.ts b/packages/rdx-ddd/src/helpers/zod-validator-error-traslator.ts new file mode 100644 index 00000000..f7f1a665 --- /dev/null +++ b/packages/rdx-ddd/src/helpers/zod-validator-error-traslator.ts @@ -0,0 +1,18 @@ +import { ZodError } from "zod/v4"; +import { ValidationErrorCollection, ValidationErrorDetail } from "../errors"; + +export function translateZodValidationError( + message: string, + zodError: ZodError, + errorValue?: unknown +) { + const errors: ValidationErrorDetail[] = []; + for (const issue of zodError.issues) { + errors.push({ + message: issue.message, + path: issue.path.join("."), + value: errorValue ?? issue.input, + }); + } + return new ValidationErrorCollection(message, errors); +} diff --git a/packages/rdx-ddd/src/index.ts b/packages/rdx-ddd/src/index.ts index 030636e4..b49b0cae 100644 --- a/packages/rdx-ddd/src/index.ts +++ b/packages/rdx-ddd/src/index.ts @@ -1,6 +1,7 @@ export * from "./aggregate-root"; export * from "./aggregate-root-repository.interface"; export * from "./domain-entity"; +export * from "./errors"; export * from "./events/domain-event.interface"; export * from "./helpers"; export * from "./specification"; diff --git a/packages/rdx-ddd/src/value-objects/city.ts b/packages/rdx-ddd/src/value-objects/city.ts index d5202c8a..9991eaf6 100644 --- a/packages/rdx-ddd/src/value-objects/city.ts +++ b/packages/rdx-ddd/src/value-objects/city.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface CityProps { @@ -23,7 +24,7 @@ export class City extends ValueObject { const valueIsValid = City.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail(translateZodValidationError("City creation failed", valueIsValid.error)); } return Result.ok(new City({ value })); } diff --git a/packages/rdx-ddd/src/value-objects/country.ts b/packages/rdx-ddd/src/value-objects/country.ts index de1a11bf..fcdd95f0 100644 --- a/packages/rdx-ddd/src/value-objects/country.ts +++ b/packages/rdx-ddd/src/value-objects/country.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface CountryProps { @@ -23,7 +24,9 @@ export class Country extends ValueObject { const valueIsValid = Country.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("Country creation failed", valueIsValid.error) + ); } return Result.ok(new Country({ value })); } diff --git a/packages/rdx-ddd/src/value-objects/currency-code.ts b/packages/rdx-ddd/src/value-objects/currency-code.ts index 434de088..c07e4375 100644 --- a/packages/rdx-ddd/src/value-objects/currency-code.ts +++ b/packages/rdx-ddd/src/value-objects/currency-code.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface CurrencyCodeProps { @@ -33,7 +34,9 @@ export class CurrencyCode extends ValueObject { const valueIsValid = CurrencyCode.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("CurrencyCode creation failed", valueIsValid.error) + ); } return Result.ok(new CurrencyCode({ value: valueIsValid.data })); } diff --git a/packages/rdx-ddd/src/value-objects/email-address.ts b/packages/rdx-ddd/src/value-objects/email-address.ts index c25f91ba..79806b40 100644 --- a/packages/rdx-ddd/src/value-objects/email-address.ts +++ b/packages/rdx-ddd/src/value-objects/email-address.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface EmailAddressProps { @@ -11,7 +12,9 @@ export class EmailAddress extends ValueObject { const valueIsValid = EmailAddress.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("EmailAddress creation failed", valueIsValid.error) + ); } return Result.ok(new EmailAddress({ value: valueIsValid.data })); diff --git a/packages/rdx-ddd/src/value-objects/language-code.ts b/packages/rdx-ddd/src/value-objects/language-code.ts index d5da947e..0a7d651c 100644 --- a/packages/rdx-ddd/src/value-objects/language-code.ts +++ b/packages/rdx-ddd/src/value-objects/language-code.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface LanguageCodeProps { @@ -33,7 +34,9 @@ export class LanguageCode extends ValueObject { const valueIsValid = LanguageCode.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("LanguageCode creation failed", valueIsValid.error) + ); } return Result.ok(new LanguageCode({ value: valueIsValid.data })); } diff --git a/packages/rdx-ddd/src/value-objects/name.ts b/packages/rdx-ddd/src/value-objects/name.ts index a94b328a..5b978b64 100644 --- a/packages/rdx-ddd/src/value-objects/name.ts +++ b/packages/rdx-ddd/src/value-objects/name.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface NameProps { @@ -21,7 +22,7 @@ export class Name extends ValueObject { const valueIsValid = Name.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail(translateZodValidationError("Name creation failed", valueIsValid.error)); } return Result.ok(new Name({ value })); } diff --git a/packages/rdx-ddd/src/value-objects/percentage.ts b/packages/rdx-ddd/src/value-objects/percentage.ts index 18cca879..5ef67113 100644 --- a/packages/rdx-ddd/src/value-objects/percentage.ts +++ b/packages/rdx-ddd/src/value-objects/percentage.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; const DEFAULT_SCALE = 2; @@ -42,9 +43,12 @@ export class Percentage extends ValueObject { static create(props: { value: number; scale?: number }): Result { const { value, scale = Percentage.DEFAULT_SCALE } = props; - const validationResult = Percentage.validate({ value, scale }); - if (!validationResult.success) { - return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", "))); + const valueIsValid = Percentage.validate({ value, scale }); + + if (!valueIsValid.success) { + return Result.fail( + translateZodValidationError("Percentage creation failed", valueIsValid.error) + ); } // Cálculo del valor real del porcentaje diff --git a/packages/rdx-ddd/src/value-objects/phone-number.ts b/packages/rdx-ddd/src/value-objects/phone-number.ts index 6eed5174..a0f67d6b 100644 --- a/packages/rdx-ddd/src/value-objects/phone-number.ts +++ b/packages/rdx-ddd/src/value-objects/phone-number.ts @@ -1,6 +1,7 @@ import { Result } from "@repo/rdx-utils"; import { isPossiblePhoneNumber, parsePhoneNumberWithError } from "libphonenumber-js"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface PhoneNumberProps { @@ -38,14 +39,18 @@ export class PhoneNumber extends ValueObject { } })*/ - return schema.safeParse(value); + return schema.safeParse(value, { + reportInput: true, + }); } static create(value: string): Result { const valueIsValid = PhoneNumber.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("PhoneNumber creation failed", valueIsValid.error) + ); } return Result.ok(new PhoneNumber({ value: valueIsValid.data })); diff --git a/packages/rdx-ddd/src/value-objects/postal-code.ts b/packages/rdx-ddd/src/value-objects/postal-code.ts index 3043144e..1fef267c 100644 --- a/packages/rdx-ddd/src/value-objects/postal-code.ts +++ b/packages/rdx-ddd/src/value-objects/postal-code.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface PostalCodeProps { @@ -31,7 +32,9 @@ export class PostalCode extends ValueObject { const valueIsValid = PostalCode.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("PostalCode creation failed", valueIsValid.error) + ); } return Result.ok(new PostalCode({ value: valueIsValid.data })); } diff --git a/packages/rdx-ddd/src/value-objects/province.ts b/packages/rdx-ddd/src/value-objects/province.ts index f963e1a9..6f5b53ff 100644 --- a/packages/rdx-ddd/src/value-objects/province.ts +++ b/packages/rdx-ddd/src/value-objects/province.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface ProvinceProps { @@ -23,7 +24,9 @@ export class Province extends ValueObject { const valueIsValid = Province.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("Province creation failed", valueIsValid.error) + ); } return Result.ok(new Province({ value })); } diff --git a/packages/rdx-ddd/src/value-objects/quantity.ts b/packages/rdx-ddd/src/value-objects/quantity.ts index 054b4494..27c18770 100644 --- a/packages/rdx-ddd/src/value-objects/quantity.ts +++ b/packages/rdx-ddd/src/value-objects/quantity.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; const DEFAULT_SCALE = 2; @@ -33,7 +34,7 @@ export class Quantity extends ValueObject { const checkProps = Quantity.validate(props); if (!checkProps.success) { - return Result.fail(new Error(checkProps.error.issues[0].message)); + return Result.fail(translateZodValidationError("Quantity creation failed", checkProps.error)); } return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) })); } diff --git a/packages/rdx-ddd/src/value-objects/slug.ts b/packages/rdx-ddd/src/value-objects/slug.ts index 54d4082d..28241e53 100644 --- a/packages/rdx-ddd/src/value-objects/slug.ts +++ b/packages/rdx-ddd/src/value-objects/slug.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface SlugProps { @@ -26,7 +27,7 @@ export class Slug extends ValueObject { const valueIsValid = Slug.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail(translateZodValidationError("Slug creation failed", valueIsValid.error)); } // biome-ignore lint/style/noNonNullAssertion: return Result.ok(new Slug({ value: valueIsValid.data! })); diff --git a/packages/rdx-ddd/src/value-objects/street.ts b/packages/rdx-ddd/src/value-objects/street.ts index cf6421f5..27d23605 100644 --- a/packages/rdx-ddd/src/value-objects/street.ts +++ b/packages/rdx-ddd/src/value-objects/street.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface StreetProps { @@ -23,7 +24,7 @@ export class Street extends ValueObject { const valueIsValid = Street.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail(translateZodValidationError("Street creation failed", valueIsValid.error)); } return Result.ok(new Street({ value })); } diff --git a/packages/rdx-ddd/src/value-objects/tax-code.ts b/packages/rdx-ddd/src/value-objects/tax-code.ts index 86b6f670..0229a3c0 100644 --- a/packages/rdx-ddd/src/value-objects/tax-code.ts +++ b/packages/rdx-ddd/src/value-objects/tax-code.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface TaxCodeProps { @@ -30,7 +31,9 @@ export class TaxCode extends ValueObject { const valueIsValid = TaxCode.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("TaxCode creation failed", valueIsValid.error) + ); } // biome-ignore lint/style/noNonNullAssertion: return Result.ok(new TaxCode({ value: valueIsValid.data! })); diff --git a/packages/rdx-ddd/src/value-objects/text-value.ts b/packages/rdx-ddd/src/value-objects/text-value.ts index d81da13c..c34a2360 100644 --- a/packages/rdx-ddd/src/value-objects/text-value.ts +++ b/packages/rdx-ddd/src/value-objects/text-value.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface TextValueProps { @@ -24,7 +25,9 @@ export class TextValue extends ValueObject { const valueIsValid = TextValue.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("TextValue creation failed", valueIsValid.error) + ); } return Result.ok(new TextValue({ value })); } diff --git a/packages/rdx-ddd/src/value-objects/tin-number.ts b/packages/rdx-ddd/src/value-objects/tin-number.ts index 90108e0d..a255698e 100644 --- a/packages/rdx-ddd/src/value-objects/tin-number.ts +++ b/packages/rdx-ddd/src/value-objects/tin-number.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface TINNumberProps { @@ -28,7 +29,9 @@ export class TINNumber extends ValueObject { const valueIsValid = TINNumber.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("TINNumber creation failed", valueIsValid.error) + ); } return Result.ok(new TINNumber({ value: valueIsValid.data })); } diff --git a/packages/rdx-ddd/src/value-objects/url-address.ts b/packages/rdx-ddd/src/value-objects/url-address.ts index 8ec57546..b474d41f 100644 --- a/packages/rdx-ddd/src/value-objects/url-address.ts +++ b/packages/rdx-ddd/src/value-objects/url-address.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface URLAddressProps { @@ -11,14 +12,28 @@ export class URLAddress extends ValueObject { const valueIsValid = URLAddress.validate(value); if (!valueIsValid.success) { - return Result.fail(new Error(valueIsValid.error.issues[0].message)); + return Result.fail( + translateZodValidationError("URLAddress creation failed", valueIsValid.error) + ); } return Result.ok(new URLAddress({ value: valueIsValid.data })); } private static validate(value: string) { - const schema = z.url({ message: "Invalid URL format" }); + const schema = z.string().refine((value) => { + const urlPattern = new RegExp( + "^(https?:\\/\\/)?" + // protocolo opcional + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // nombre de dominio + "((\\d{1,3}\\.){3}\\d{1,3}))" + // o dirección IP (v4) + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // puerto y ruta + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", + "i" // fragment locator + ); + return urlPattern.test(value); + }, "Invalid URL format"); + return schema.safeParse(value); } diff --git a/packages/rdx-ddd/src/value-objects/utc-date.ts b/packages/rdx-ddd/src/value-objects/utc-date.ts index 3f9983a4..8eebddb2 100644 --- a/packages/rdx-ddd/src/value-objects/utc-date.ts +++ b/packages/rdx-ddd/src/value-objects/utc-date.ts @@ -1,5 +1,6 @@ import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; +import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; interface UtcDateProps { @@ -33,8 +34,9 @@ export class UtcDate extends ValueObject { */ static createFromISO(isoDateString: string): Result { const dateIsValid = UtcDate.validate(isoDateString); + if (!dateIsValid.success) { - return Result.fail(new Error(`Invalid UTC date format: ${isoDateString}`)); + return Result.fail(translateZodValidationError("UtcDate creation failed", dateIsValid.error)); } return Result.ok(new UtcDate({ value: isoDateString }));