import { Name, UniqueID } from "@repo/rdx-ddd"; import { z } from "zod/v4"; import { Ticket } from "../../domain/ticket.aggregate"; import { TicketRevision } from "../../domain/ticket-revision.entity"; import { TicketInputSchema } from "../../domain/ticket-values"; import { TicketValues } from "../../domain/value-objects/ticket-values"; const Root = z.strictObject({ id: z.uuid(), companyId: z.uuid(), reference: z.string().regex(/^T-[0-9]{6,}$/), requestKey: z.uuid(), requesterId: z.uuid().nullable(), assigneeId: z.uuid().nullable(), values: TicketInputSchema, }); const Revision = z.strictObject({ id: z.uuid(), ticketId: z.uuid(), actorId: z.uuid(), requesterId: z.uuid().nullable(), assigneeId: z.uuid().nullable(), values: TicketInputSchema, recordedAt: z.iso.datetime(), position: z.number().int().nonnegative(), }); export function ticketIdentity(value: string) { const id = UniqueID.create(value); if (!id.isSuccess) { throw new Error("Identidad de ticket inconsistente."); } return id.data; } function json(value: unknown): unknown { return typeof value === "string" ? JSON.parse(value) : value; } function values(value: unknown) { const result = TicketValues.create(TicketInputSchema.parse(json(value))); if (!result.isSuccess) { throw new Error("Valores de ticket inconsistentes."); } return result.data; } export class TicketPersistenceMapper { toDomain(root: Record, rows: readonly Record[]) { const current = Root.parse({ ...root, values: json(root.values) }); const history = rows .map((row) => Revision.parse({ ...row, values: json(row.values) })) .sort((a, b) => a.position - b.position); const reference = Name.create(current.reference, 40); if ( !(reference.isSuccess && history.length) || new Set(history.map((item) => item.id)).size !== history.length || history.some( (item, index) => item.ticketId !== current.id || item.position !== index || item.requesterId !== item.values.requesterId || item.assigneeId !== item.values.assigneeId || (index > 0 && item.recordedAt < (history[index - 1]?.recordedAt ?? "")) ) || current.requesterId !== current.values.requesterId || current.assigneeId !== current.values.assigneeId ) { throw new Error("Historial de ticket inconsistente."); } const revisions = history.map((item) => TicketRevision.rehydrate( { values: values(item.values), actorId: ticketIdentity(item.actorId), recordedAt: item.recordedAt, }, ticketIdentity(item.id) ) ); const currentValues = values(current.values); if (!revisions.at(-1)?.values.equals(currentValues)) { throw new Error("Instantánea de ticket inconsistente."); } return Ticket.rehydrate( { companyId: ticketIdentity(current.companyId), reference: reference.data, requestKey: ticketIdentity(current.requestKey), values: currentValues, history: revisions, }, ticketIdentity(current.id) ); } toPersistence(ticket: Ticket) { const input = ticket.values.toPrimitive(); return { root: { id: ticket.id.toPrimitive(), companyId: ticket.companyId.toPrimitive(), reference: ticket.reference.toPrimitive(), requestKey: ticket.requestKey.toPrimitive(), requesterId: input.requesterId, assigneeId: input.assigneeId, values: JSON.stringify(input), }, revisions: ticket.history.map((item, position) => ({ id: item.id.toPrimitive(), ticketId: ticket.id.toPrimitive(), actorId: item.actorId.toPrimitive(), requesterId: item.values.toPrimitive().requesterId, assigneeId: item.values.toPrimitive().assigneeId, values: JSON.stringify(item.values.toPrimitive()), recordedAt: item.recordedAt, position, })), }; } }