diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts
index 3ee1365..4a3808e 100644
--- a/src/i18n/dictionaries/en.ts
+++ b/src/i18n/dictionaries/en.ts
@@ -255,6 +255,46 @@ export const en = {
invalidUpdateStatus: "Invalid status",
},
},
+ assignments: {
+ list: {
+ title: "Assignments",
+ addLabel: "Add Assignment",
+ empty: "No assignments found.",
+ columns: {
+ recipient: "Recipient",
+ item: "Item",
+ serialNumber: "Serial Number",
+ quantity: "Quantity",
+ actions: "Actions",
+ },
+ actions: {
+ edit: "Edit assignment",
+ return: "Return assignment",
+ },
+ },
+ new: {
+ title: "New Assignment",
+ },
+ edit: {
+ title: "Edit Assignment",
+ notFound: "Assignment not found",
+ },
+ form: {
+ recipientLabel: "Recipient",
+ recipientPlaceholder: "Select a recipient",
+ itemLabel: "Item",
+ itemPlaceholder: "Select an item",
+ assetLabel: "Asset",
+ assetPlaceholder: "Select an asset",
+ quantityLabel: "Quantity",
+ quantityPlaceholder: "1",
+ createSubmit: "Create Assignment",
+ updateSubmit: "Update Assignment",
+ },
+ fallback: {
+ missingValue: "N/A",
+ },
+ },
recipients: {
list: {
title: "Recipients",
diff --git a/src/i18n/dictionaries/es.ts b/src/i18n/dictionaries/es.ts
index 0214d7c..abe45e6 100644
--- a/src/i18n/dictionaries/es.ts
+++ b/src/i18n/dictionaries/es.ts
@@ -259,6 +259,46 @@ export const es = {
invalidUpdateStatus: "Estado inválido",
},
},
+ assignments: {
+ list: {
+ title: "Asignaciones",
+ addLabel: "Agregar asignación",
+ empty: "No se encontraron asignaciones.",
+ columns: {
+ recipient: "Destinatario",
+ item: "Artículo",
+ serialNumber: "Número de serie",
+ quantity: "Cantidad",
+ actions: "Acciones",
+ },
+ actions: {
+ edit: "Editar asignación",
+ return: "Devolver asignación",
+ },
+ },
+ new: {
+ title: "Nueva asignación",
+ },
+ edit: {
+ title: "Editar asignación",
+ notFound: "Asignación no encontrada",
+ },
+ form: {
+ recipientLabel: "Destinatario",
+ recipientPlaceholder: "Selecciona un destinatario",
+ itemLabel: "Artículo",
+ itemPlaceholder: "Selecciona un artículo",
+ assetLabel: "Activo",
+ assetPlaceholder: "Selecciona un activo",
+ quantityLabel: "Cantidad",
+ quantityPlaceholder: "1",
+ createSubmit: "Crear asignación",
+ updateSubmit: "Actualizar asignación",
+ },
+ fallback: {
+ missingValue: "No disponible",
+ },
+ },
recipients: {
list: {
title: "Destinatarios",
diff --git a/tests/unit/app/assignments/assignment-form-pages.test.ts b/tests/unit/app/assignments/assignment-form-pages.test.ts
new file mode 100644
index 0000000..f2d6a41
--- /dev/null
+++ b/tests/unit/app/assignments/assignment-form-pages.test.ts
@@ -0,0 +1,177 @@
+import { renderToStaticMarkup } from "react-dom/server"
+import { beforeEach, describe, expect, it, vi } from "vitest"
+
+import { es } from "@/i18n/dictionaries/es"
+
+const mocks = vi.hoisted(() => ({
+ getI18n: vi.fn(),
+ findAllRecipients: vi.fn(),
+ findAllItemsWithStock: vi.fn(),
+ findAllAvailableAssets: vi.fn(),
+ findAllItems: vi.fn(),
+ findItemById: vi.fn(),
+ findAllAssets: vi.fn(),
+ findAssignmentById: vi.fn(),
+ createAssignment: vi.fn(),
+ updateAssignment: vi.fn(),
+ push: vi.fn(),
+ toastError: vi.fn(),
+ toastSuccess: vi.fn(),
+}))
+
+vi.mock("@/i18n/server", () => ({
+ getI18n: mocks.getI18n,
+}))
+
+vi.mock("@/services/recipient.service", () => ({
+ RecipientService: {
+ findAll: mocks.findAllRecipients,
+ },
+}))
+
+vi.mock("@/services/item.service", () => ({
+ ItemService: {
+ findAllWithStock: mocks.findAllItemsWithStock,
+ findAll: mocks.findAllItems,
+ findById: mocks.findItemById,
+ },
+}))
+
+vi.mock("@/services/asset.service", () => ({
+ AssetService: {
+ findAllAvailable: mocks.findAllAvailableAssets,
+ findAll: mocks.findAllAssets,
+ },
+}))
+
+vi.mock("@/services/assignment.service", () => ({
+ AssignmentService: {
+ findById: mocks.findAssignmentById,
+ },
+}))
+
+vi.mock("@/actions/assignment.actions", () => ({
+ createAssignment: mocks.createAssignment,
+ updateAssignment: mocks.updateAssignment,
+}))
+
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({
+ push: mocks.push,
+ }),
+}))
+
+vi.mock("sonner", () => ({
+ toast: {
+ error: mocks.toastError,
+ success: mocks.toastSuccess,
+ },
+}))
+
+describe("assignment form pages localization", () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" })
+ mocks.findAllRecipients.mockResolvedValue([
+ {
+ id: "recipient-1",
+ firstName: "Ada",
+ lastName: "Lovelace",
+ },
+ ])
+ mocks.findAllItemsWithStock.mockResolvedValue([
+ {
+ id: "item-1",
+ name: "Laptop",
+ stock: 5,
+ },
+ ])
+ mocks.findAllItems.mockResolvedValue([
+ {
+ id: "item-1",
+ name: "Laptop",
+ stock: 5,
+ },
+ ])
+ mocks.findAllAvailableAssets.mockResolvedValue([
+ {
+ id: "asset-1",
+ itemId: "item-1",
+ serialNumber: "SN-001",
+ },
+ ])
+ mocks.findAllAssets.mockResolvedValue([
+ {
+ id: "asset-1",
+ itemId: "item-1",
+ serialNumber: "SN-001",
+ },
+ ])
+ mocks.findItemById.mockResolvedValue({
+ id: "item-1",
+ name: "Laptop",
+ stock: 5,
+ })
+ })
+
+ it("renders the new assignment page with localized heading and form copy", async () => {
+ const { default: NewAssignmentPage } = await import(
+ "@/app/(dashboard)/assignments/new/page"
+ )
+
+ const html = renderToStaticMarkup(await NewAssignmentPage())
+
+ expect(html).toContain("Nueva asignación")
+ expect(html).toContain("Destinatario")
+ expect(html).toContain(
+ 'option value="">Selecciona un destinatario',
+ )
+ expect(html).toContain("Artículo")
+ expect(html).toContain('option value="">Selecciona un artículo')
+ expect(html).toContain("Cantidad")
+ expect(html).toContain('placeholder="1"')
+ expect(html).toContain("Crear asignación")
+ expect(html).toContain("Ada Lovelace")
+ expect(html).toContain("Laptop")
+ })
+
+ it("renders the edit assignment page with localized heading, not-found copy, and submit text", async () => {
+ const { default: EditAssignmentPage } = await import(
+ "@/app/(dashboard)/assignments/[assignmentId]/edit/page"
+ )
+
+ mocks.findAssignmentById.mockResolvedValue({
+ id: "assignment-1",
+ recipientId: "recipient-1",
+ itemId: "item-1",
+ assetId: "asset-1",
+ quantity: 1,
+ })
+
+ const html = renderToStaticMarkup(
+ await EditAssignmentPage({
+ params: Promise.resolve({ assignmentId: "assignment-1" }),
+ }),
+ )
+
+ expect(html).toContain("Editar asignación")
+ expect(html).toContain("Destinatario")
+ expect(html).toContain("Artículo")
+ expect(html).toContain("Activo")
+ expect(html).toContain('option value="">Selecciona un activo')
+ expect(html).toContain("Actualizar asignación")
+ expect(html).toContain("Ada Lovelace")
+ expect(html).toContain("Laptop")
+ expect(html).toContain("SN-001")
+
+ mocks.findAssignmentById.mockResolvedValueOnce(null)
+
+ const notFoundHtml = renderToStaticMarkup(
+ await EditAssignmentPage({
+ params: Promise.resolve({ assignmentId: "missing-assignment" }),
+ }),
+ )
+
+ expect(notFoundHtml).toContain("Asignación no encontrada")
+ })
+})
diff --git a/tests/unit/app/assignments/assignment-pages.test.ts b/tests/unit/app/assignments/assignment-pages.test.ts
new file mode 100644
index 0000000..0a088a5
--- /dev/null
+++ b/tests/unit/app/assignments/assignment-pages.test.ts
@@ -0,0 +1,124 @@
+import { createElement } from "react"
+import { renderToStaticMarkup } from "react-dom/server"
+import { beforeEach, describe, expect, it, vi } from "vitest"
+
+import { es } from "@/i18n/dictionaries/es"
+
+const mocks = vi.hoisted(() => ({
+ findAllWithRecipientPaginated: vi.fn(),
+ getI18n: vi.fn(),
+ refresh: vi.fn(),
+ returnAssignment: vi.fn(),
+ toastError: vi.fn(),
+ toastSuccess: vi.fn(),
+}))
+
+vi.mock("@/i18n/server", () => ({
+ getI18n: mocks.getI18n,
+}))
+
+vi.mock("@/services/assignment.service", () => ({
+ AssignmentService: {
+ findAllWithRecipientPaginated: mocks.findAllWithRecipientPaginated,
+ },
+}))
+
+vi.mock("@/actions/assignment.actions", () => ({
+ returnAssignment: mocks.returnAssignment,
+}))
+
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({
+ refresh: mocks.refresh,
+ }),
+}))
+
+vi.mock("sonner", () => ({
+ toast: {
+ error: mocks.toastError,
+ success: mocks.toastSuccess,
+ },
+}))
+
+vi.mock("@/components/common/pageheader", () => ({
+ default: ({ title, addLabel }: { title: string; addLabel?: string }) =>
+ createElement(
+ "header",
+ null,
+ [title, addLabel].filter(Boolean).join(" | "),
+ ),
+}))
+
+vi.mock("@/components/common/pagination", () => ({
+ default: ({ totalPages }: { totalPages: number }) =>
+ createElement("nav", { "aria-label": "Pagination" }, totalPages),
+}))
+
+describe("assignment pages localization", () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" })
+ })
+
+ it("renders the assignment list in Spanish with localized action labels and unchanged assignment data", async () => {
+ const { default: AssignmentsPage } = await import(
+ "@/app/(dashboard)/assignments/page"
+ )
+
+ mocks.findAllWithRecipientPaginated.mockResolvedValue({
+ data: [
+ {
+ id: "assignment-1",
+ quantity: 2,
+ recipient: {
+ id: "recipient-1",
+ firstName: "Ada",
+ lastName: "Lovelace",
+ },
+ item: {
+ id: "item-1",
+ name: "Laptop",
+ },
+ asset: {
+ serialNumber: null,
+ },
+ },
+ ],
+ totalPages: 1,
+ })
+
+ const html = renderToStaticMarkup(
+ await AssignmentsPage({ searchParams: Promise.resolve({}) }),
+ )
+
+ expect(html).toContain("Asignaciones")
+ expect(html).toContain("Agregar asignación")
+ expect(html).toContain("Destinatario")
+ expect(html).toContain("Artículo")
+ expect(html).toContain("Número de serie")
+ expect(html).toContain("Cantidad")
+ expect(html).toContain("Acciones")
+ expect(html).toContain("Ada Lovelace")
+ expect(html).toContain("Laptop")
+ expect(html).toContain("No disponible")
+ expect(html).toContain('aria-label="Editar asignación"')
+ expect(html).toContain('aria-label="Devolver asignación"')
+ })
+
+ it("renders the localized assignment empty state when no assignments exist", async () => {
+ const { default: AssignmentsPage } = await import(
+ "@/app/(dashboard)/assignments/page"
+ )
+
+ mocks.findAllWithRecipientPaginated.mockResolvedValue({
+ data: [],
+ totalPages: 0,
+ })
+
+ const html = renderToStaticMarkup(
+ await AssignmentsPage({ searchParams: Promise.resolve({}) }),
+ )
+
+ expect(html).toContain("No se encontraron asignaciones.")
+ })
+})
diff --git a/tests/unit/app/recipients/recipient-pages.test.ts b/tests/unit/app/recipients/recipient-pages.test.ts
index a0bf867..918969c 100644
--- a/tests/unit/app/recipients/recipient-pages.test.ts
+++ b/tests/unit/app/recipients/recipient-pages.test.ts
@@ -104,7 +104,7 @@ describe("recipient pages localization", () => {
expect(html).toContain("No se encontraron destinatarios.")
})
- it("renders localized recipient-owned detail labels and keeps assignments copy unchanged", async () => {
+ it("renders localized recipient-owned detail labels and localizes the embedded assignments title only", async () => {
const { default: RecipientInfoPage } = await import(
"@/app/(dashboard)/recipients/[recipientId]/page"
)
@@ -140,10 +140,10 @@ describe("recipient pages localization", () => {
expect(html).toContain("Chofer")
expect(html).toContain("ada")
expect(html).toContain("ada@example.test")
- expect(html).toContain("Assignments")
+ expect(html).toContain("Asignaciones")
expect(html).toContain("Laptop")
expect(html).not.toContain(">DRIVER<")
- expect(html).not.toContain("Asignaciones")
+ expect(html).not.toContain("Assignments")
})
it("renders a localized recipient detail not-found message", async () => {
diff --git a/tests/unit/i18n/dictionaries.test.ts b/tests/unit/i18n/dictionaries.test.ts
index d2e6b99..b05a2a5 100644
--- a/tests/unit/i18n/dictionaries.test.ts
+++ b/tests/unit/i18n/dictionaries.test.ts
@@ -408,6 +408,90 @@ describe("i18n dictionaries", () => {
})
})
+ it("provides localized assignment copy for English and Spanish", () => {
+ expect(getDictionary("en").inventory.assignments).toEqual({
+ list: {
+ title: "Assignments",
+ addLabel: "Add Assignment",
+ empty: "No assignments found.",
+ columns: {
+ recipient: "Recipient",
+ item: "Item",
+ serialNumber: "Serial Number",
+ quantity: "Quantity",
+ actions: "Actions",
+ },
+ actions: {
+ edit: "Edit assignment",
+ return: "Return assignment",
+ },
+ },
+ new: {
+ title: "New Assignment",
+ },
+ edit: {
+ title: "Edit Assignment",
+ notFound: "Assignment not found",
+ },
+ form: {
+ recipientLabel: "Recipient",
+ recipientPlaceholder: "Select a recipient",
+ itemLabel: "Item",
+ itemPlaceholder: "Select an item",
+ assetLabel: "Asset",
+ assetPlaceholder: "Select an asset",
+ quantityLabel: "Quantity",
+ quantityPlaceholder: "1",
+ createSubmit: "Create Assignment",
+ updateSubmit: "Update Assignment",
+ },
+ fallback: {
+ missingValue: "N/A",
+ },
+ })
+
+ expect(getDictionary("es").inventory.assignments).toEqual({
+ list: {
+ title: "Asignaciones",
+ addLabel: "Agregar asignación",
+ empty: "No se encontraron asignaciones.",
+ columns: {
+ recipient: "Destinatario",
+ item: "Artículo",
+ serialNumber: "Número de serie",
+ quantity: "Cantidad",
+ actions: "Acciones",
+ },
+ actions: {
+ edit: "Editar asignación",
+ return: "Devolver asignación",
+ },
+ },
+ new: {
+ title: "Nueva asignación",
+ },
+ edit: {
+ title: "Editar asignación",
+ notFound: "Asignación no encontrada",
+ },
+ form: {
+ recipientLabel: "Destinatario",
+ recipientPlaceholder: "Selecciona un destinatario",
+ itemLabel: "Artículo",
+ itemPlaceholder: "Selecciona un artículo",
+ assetLabel: "Activo",
+ assetPlaceholder: "Selecciona un activo",
+ quantityLabel: "Cantidad",
+ quantityPlaceholder: "1",
+ createSubmit: "Crear asignación",
+ updateSubmit: "Actualizar asignación",
+ },
+ fallback: {
+ missingValue: "No disponible",
+ },
+ })
+ })
+
it("provides localized inventory asset UI copy for English and Spanish", () => {
expect(getDictionary("en").inventory.assets).toEqual({
list: {