diff --git a/modules/core/src/web/components/empty-cell-value.tsx b/modules/core/src/web/components/empty-cell-value.tsx
index 181481dd..d55aac71 100644
--- a/modules/core/src/web/components/empty-cell-value.tsx
+++ b/modules/core/src/web/components/empty-cell-value.tsx
@@ -5,15 +5,6 @@ type EmptyCellValueProps = {
value?: string;
};
-export const EmptyCellValue = ({
- className,
- value = "\u2014",
-}: EmptyCellValueProps) => (
-
- {value}
-
+export const EmptyCellValue = ({ className, value = "\u2014" }: EmptyCellValueProps) => (
+ {value}
);
-
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/index.ts
new file mode 100644
index 00000000..e36cd5d3
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/index.ts
@@ -0,0 +1,4 @@
+export * from "./proforma-detail-header-actions";
+export * from "./proforma-detail-preview-card";
+export * from "./proforma-detail-sidebar";
+export * from "./proforma-detail-summary-card";
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-header-actions.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-header-actions.tsx
new file mode 100644
index 00000000..17504cf7
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-header-actions.tsx
@@ -0,0 +1,57 @@
+import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@repo/shadcn-ui/components";
+import { DownloadIcon, MoreHorizontalIcon, PencilIcon, RefreshCwIcon } from "lucide-react";
+
+type ProformaDetailHeaderActionsProps = {
+ primaryAction: {
+ label: string;
+ onClick: () => void;
+ };
+ isPrimaryLoading: boolean;
+ isArchived: boolean;
+ onDownload: () => void;
+ onDuplicate: () => void;
+ onArchiveToggle: () => void;
+};
+
+export const ProformaDetailHeaderActions = ({
+ primaryAction,
+ isPrimaryLoading,
+ isArchived,
+ onDownload,
+ onDuplicate,
+ onArchiveToggle,
+}: ProformaDetailHeaderActionsProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+ }
+ />
+
+ Duplicar proforma
+
+ {isArchived ? "Desarchivar" : "Archivar"}
+
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-preview-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-preview-card.tsx
new file mode 100644
index 00000000..68f90d98
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-preview-card.tsx
@@ -0,0 +1,39 @@
+import { PDFViewer } from "@repo/rdx-ui/components";
+import { Card, CardContent } from "@repo/shadcn-ui/components";
+
+type ProformaDetailPreviewCardProps = {
+ src: string | null;
+ isLoading: boolean;
+ error: string | null;
+ onOpen: () => void;
+ onDownload: () => void;
+ onPrint: () => void;
+};
+
+export const ProformaDetailPreviewCard = ({
+ src,
+ isLoading,
+ error,
+ onOpen,
+ onDownload,
+ onPrint,
+}: ProformaDetailPreviewCardProps) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-sidebar.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-sidebar.tsx
new file mode 100644
index 00000000..c85c52fe
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-sidebar.tsx
@@ -0,0 +1,28 @@
+import type { Proforma } from "../../../shared";
+import { ProformaDetailStatusInspectorCard } from "../components";
+
+import { ProformaDetailSummaryCard } from "./proforma-detail-summary-card";
+
+type ProformaDetailSidebarProps = {
+ proforma: Proforma;
+ paymentMethodLabel: string | null;
+ paymentTermLabel: string | null;
+ onEdit: () => void;
+};
+
+export const ProformaDetailSidebar = ({
+ proforma,
+ paymentMethodLabel,
+ paymentTermLabel,
+}: ProformaDetailSidebarProps) => {
+ return (
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-summary-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-summary-card.tsx
new file mode 100644
index 00000000..dd8cc9a3
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-summary-card.tsx
@@ -0,0 +1,159 @@
+import { FieldValueList, FormSectionCard, FormSectionGrid } from "@repo/rdx-ui/components";
+import { Separator } from "@repo/shadcn-ui/components";
+import { LandmarkIcon } from "lucide-react";
+
+import { type Proforma } from "../../../shared";
+import { formatProformaDate, formatProformaMoney } from "../components/proforma-detail-formatters";
+
+type ProformaDetailSummaryCardProps = {
+ proforma: Proforma;
+ paymentMethodLabel: string | null;
+ paymentTermLabel: string | null;
+};
+
+const SectionHeading = ({ title, description }: { title: string; description?: string }) => {
+ return (
+
+
{title}
+ {description ?
{description}
: null}
+
+ );
+};
+
+export const ProformaDetailSummaryCard = ({
+ proforma,
+ paymentMethodLabel,
+ paymentTermLabel,
+}: ProformaDetailSummaryCardProps) => {
+ const billingItems = [
+ {
+ label: "Método de pago",
+ value: paymentMethodLabel ?? "\u2014",
+ },
+ {
+ label: "Plazo",
+ value: paymentTermLabel ?? "\u2014",
+ },
+ {
+ label: "Moneda",
+ value: proforma.currencyCode,
+ },
+ ];
+
+ const datesItems = [
+ {
+ label: "Fecha proforma",
+ value: formatProformaDate(proforma.proformaDate),
+ },
+ {
+ label: "Fecha operación",
+ value: formatProformaDate(proforma.operationDate),
+ },
+ {
+ label: "Serie",
+ value: proforma.series ?? "\u2014",
+ },
+ {
+ label: "Referencia",
+ value: proforma.reference ?? "\u2014",
+ },
+ ];
+
+ const totalsItems = [
+ {
+ label: "Subtotal",
+ value: formatProformaMoney(proforma.subtotalAmount, proforma.currencyCode, proforma.languageCode),
+ },
+ {
+ label: "Descuentos",
+ value: `-${formatProformaMoney(proforma.totalDiscountAmount, proforma.currencyCode, proforma.languageCode)}`,
+ valueClassName: "text-red-600",
+ },
+ {
+ label: "IVA",
+ value: formatProformaMoney(proforma.ivaAmount, proforma.currencyCode, proforma.languageCode),
+ },
+ {
+ label: "Total",
+ value: formatProformaMoney(proforma.totalAmount, proforma.currencyCode, proforma.languageCode),
+ valueClassName: "text-lg font-semibold text-foreground",
+ },
+ ];
+
+ return (
+ }
+ title="Resumen de proforma"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Descripción
+
{proforma.description ?? "Sin descripción."}
+
+
+
+
Observaciones
+
{proforma.notes ?? "Sin notas adicionales."}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/index.ts b/modules/customer-invoices/src/web/proformas/detail/ui/components/index.ts
new file mode 100644
index 00000000..a6873af8
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/index.ts
@@ -0,0 +1,9 @@
+export * from "./proforma-detail-archived-badge";
+export * from "./proforma-detail-billing-inspector-card";
+export * from "./proforma-detail-dates-inspector-card";
+export * from "./proforma-detail-header-meta";
+export * from "./proforma-detail-inspector-card";
+export * from "./proforma-detail-notes-inspector-card";
+export * from "./proforma-detail-skeleton";
+export * from "./proforma-detail-status-inspector-card";
+export * from "./proforma-detail-totals-inspector-card";
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-archived-badge.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-archived-badge.tsx
new file mode 100644
index 00000000..e49d2dc1
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-archived-badge.tsx
@@ -0,0 +1,33 @@
+import { Tooltip, TooltipContent, TooltipTrigger } from "@repo/shadcn-ui/components";
+import { cn } from "@repo/shadcn-ui/lib/utils";
+import { ArchiveIcon } from "lucide-react";
+
+type ProformaDetailArchivedBadgeProps = {
+ className?: string;
+};
+
+export const ProformaDetailArchivedBadge = ({
+ className,
+}: ProformaDetailArchivedBadgeProps) => {
+ return (
+
+ svg]:pointer-events-none [&>svg]:size-3.5 [&>svg]:shrink-0",
+ className
+ )}
+ >
+
+ Archivada
+
+ }
+ />
+
+ Esta proforma está archivada y fuera del flujo principal.
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-billing-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-billing-inspector-card.tsx
new file mode 100644
index 00000000..a3e44268
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-billing-inspector-card.tsx
@@ -0,0 +1,40 @@
+import { FieldValueList } from "@repo/rdx-ui/components";
+
+import type { Proforma } from "../../../shared";
+
+import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card";
+
+type ProformaDetailBillingInspectorCardProps = {
+ proforma: Proforma;
+ paymentMethodLabel: string | null;
+ paymentTermLabel: string | null;
+ onEdit: () => void;
+};
+
+export const ProformaDetailBillingInspectorCard = ({
+ proforma,
+ paymentMethodLabel,
+ paymentTermLabel,
+ onEdit,
+}: ProformaDetailBillingInspectorCardProps) => {
+ const items = [
+ {
+ label: "Método de pago",
+ value: paymentMethodLabel ?? "\u2014",
+ },
+ {
+ label: "Plazo",
+ value: paymentTermLabel ?? "\u2014",
+ },
+ {
+ label: "Moneda",
+ value: proforma.currencyCode,
+ },
+ ];
+
+ return (
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-dates-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-dates-inspector-card.tsx
new file mode 100644
index 00000000..e065536d
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-dates-inspector-card.tsx
@@ -0,0 +1,41 @@
+import { FieldValueList } from "@repo/rdx-ui/components";
+
+import type { Proforma } from "../../../shared";
+
+import { formatProformaDate } from "./proforma-detail-formatters";
+import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card";
+
+type ProformaDetailDatesInspectorCardProps = {
+ proforma: Proforma;
+ onEdit: () => void;
+};
+
+export const ProformaDetailDatesInspectorCard = ({
+ proforma,
+ onEdit,
+}: ProformaDetailDatesInspectorCardProps) => {
+ const items = [
+ {
+ label: "Fecha proforma",
+ value: formatProformaDate(proforma.proformaDate),
+ },
+ {
+ label: "Fecha operación",
+ value: formatProformaDate(proforma.operationDate),
+ },
+ {
+ label: "Serie de la factura",
+ value: proforma.series ?? "\u2014",
+ },
+ {
+ label: "Referencia interna",
+ value: proforma.reference ?? "\u2014",
+ },
+ ];
+
+ return (
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-formatters.ts b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-formatters.ts
new file mode 100644
index 00000000..a280be65
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-formatters.ts
@@ -0,0 +1,17 @@
+import { DateHelper, MoneyHelper } from "@repo/rdx-utils";
+
+export const formatProformaDate = (value: string | null | undefined) => {
+ if (!value) {
+ return "\u2014";
+ }
+
+ return DateHelper.format(value);
+};
+
+export const formatProformaMoney = (
+ amount: number,
+ currencyCode: string,
+ languageCode?: string | null
+) => {
+ return MoneyHelper.formatCurrency(amount, 2, currencyCode, languageCode ?? undefined);
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-header-meta.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-header-meta.tsx
new file mode 100644
index 00000000..d270bcb6
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-header-meta.tsx
@@ -0,0 +1,31 @@
+import { useMemo } from "react";
+
+import type { Proforma } from "../../../shared";
+
+import { formatProformaDate, formatProformaMoney } from "./proforma-detail-formatters";
+
+type ProformaDetailHeaderMetaProps = {
+ proforma: Proforma;
+};
+
+export const ProformaDetailHeaderMeta = ({ proforma }: ProformaDetailHeaderMetaProps) => {
+ const items = useMemo(
+ () => [
+ proforma.recipient.name ?? "Cliente sin nombre",
+ formatProformaDate(proforma.proformaDate),
+ `Total ${formatProformaMoney(proforma.totalAmount, proforma.currencyCode, proforma.languageCode)}`,
+ ],
+ [proforma]
+ );
+
+ return (
+
+ {items.map((item, index) => (
+
+ {index > 0 ? | : null}
+ {item}
+
+ ))}
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-inspector-card.tsx
new file mode 100644
index 00000000..ec455f69
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-inspector-card.tsx
@@ -0,0 +1,29 @@
+import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components";
+import { SquarePenIcon } from "lucide-react";
+import type { ReactNode } from "react";
+
+type ProformaDetailInspectorCardProps = {
+ title: string;
+ onEdit?: () => void;
+ children: ReactNode;
+};
+
+export const ProformaDetailInspectorCard = ({
+ title,
+ onEdit,
+ children,
+}: ProformaDetailInspectorCardProps) => {
+ return (
+
+
+ {title}
+ {onEdit ? (
+
+ ) : null}
+
+ {children}
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-notes-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-notes-inspector-card.tsx
new file mode 100644
index 00000000..2ec30d06
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-notes-inspector-card.tsx
@@ -0,0 +1,31 @@
+import { Separator } from "@repo/shadcn-ui/components";
+
+import type { Proforma } from "../../../shared";
+
+import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card";
+
+type ProformaDetailNotesInspectorCardProps = {
+ proforma: Proforma;
+ onEdit: () => void;
+};
+
+export const ProformaDetailNotesInspectorCard = ({
+ proforma,
+ onEdit,
+}: ProformaDetailNotesInspectorCardProps) => {
+ return (
+
+
+
+
Descripción
+
{proforma.description ?? "Sin descripción."}
+
+
+
+
Observaciones
+
{proforma.notes ?? "Sin notas adicionales."}
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-skeleton.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-skeleton.tsx
new file mode 100644
index 00000000..7dbc714d
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-skeleton.tsx
@@ -0,0 +1,32 @@
+import { Card, CardContent, Skeleton } from "@repo/shadcn-ui/components";
+
+export const ProformaDetailSkeleton = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-status-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-status-inspector-card.tsx
new file mode 100644
index 00000000..ec08d804
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-status-inspector-card.tsx
@@ -0,0 +1,76 @@
+import { Alert, AlertDescription, AlertTitle } from "@repo/shadcn-ui/components";
+import { cn } from "@repo/shadcn-ui/lib/utils";
+import { CheckCircle2Icon } from "lucide-react";
+
+import type { Proforma } from "../../../shared";
+
+const getStatusTone = (status: Proforma["status"]) => {
+ switch (status) {
+ case "approved":
+ return "border-emerald-200 bg-emerald-50 text-emerald-900";
+ case "sent":
+ return "border-amber-200 bg-amber-50 text-amber-900";
+ case "issued":
+ return "border-sky-200 bg-sky-50 text-sky-900";
+ case "rejected":
+ return "border-red-200 bg-red-50 text-red-900";
+ default:
+ return "border-slate-200 bg-slate-50 text-slate-900";
+ }
+};
+
+const getStatusSummary = (proforma: Proforma) => {
+ if (proforma.isArchived) {
+ return {
+ title: "Proforma archivada",
+ description: "Está fuera del flujo principal y puedes recuperarla cuando lo necesites.",
+ };
+ }
+
+ switch (proforma.status) {
+ case "approved":
+ return {
+ title: "Lista para convertirse en factura",
+ description: "La proforma ya está aprobada por el cliente y puede emitirse como factura.",
+ };
+ case "sent":
+ return {
+ title: "Pendiente de respuesta del cliente",
+ description:
+ "Se ha enviado al cliente y está a la espera de aceptación o rechazo por parte del cliente.",
+ };
+ case "issued":
+ return {
+ title: "Convertida en factura",
+ description: "La proforma ya ha sido emitida y forma parte del flujo fiscal.",
+ };
+ case "rejected":
+ return {
+ title: "Requiere revisión",
+ description: "El cliente la ha rechazado y conviene revisar condiciones o importes.",
+ };
+ default:
+ return {
+ title: "Pendiente de revisión",
+ description: "Sigue en borrador y todavía puede modificarse antes de enviarla al cliente.",
+ };
+ }
+};
+
+type ProformaDetailStatusInspectorCardProps = {
+ proforma: Proforma;
+};
+
+export const ProformaDetailStatusInspectorCard = ({
+ proforma,
+}: ProformaDetailStatusInspectorCardProps) => {
+ const summary = getStatusSummary(proforma);
+
+ return (
+
+
+ {summary.title}
+ {summary.description}
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-totals-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-totals-inspector-card.tsx
new file mode 100644
index 00000000..9fa9d0f2
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-totals-inspector-card.tsx
@@ -0,0 +1,49 @@
+import { FieldValueList } from "@repo/rdx-ui/components";
+
+import type { Proforma } from "../../../shared";
+
+import { formatProformaMoney } from "./proforma-detail-formatters";
+import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card";
+
+type ProformaDetailTotalsInspectorCardProps = {
+ proforma: Proforma;
+};
+
+export const ProformaDetailTotalsInspectorCard = ({
+ proforma,
+}: ProformaDetailTotalsInspectorCardProps) => {
+ const items = [
+ {
+ label: "Subtotal",
+ value: formatProformaMoney(
+ proforma.subtotalAmount,
+ proforma.currencyCode,
+ proforma.languageCode
+ ),
+ },
+ {
+ label: "Descuentos",
+ value: `-${formatProformaMoney(proforma.totalDiscountAmount, proforma.currencyCode, proforma.languageCode)}`,
+ valueClassName: "text-red-600",
+ },
+ {
+ label: "IVA",
+ value: formatProformaMoney(proforma.ivaAmount, proforma.currencyCode, proforma.languageCode),
+ },
+ {
+ label: "Total",
+ value: formatProformaMoney(
+ proforma.totalAmount,
+ proforma.currencyCode,
+ proforma.languageCode
+ ),
+ valueClassName: "text-lg font-semibold text-foreground",
+ },
+ ];
+
+ return (
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/index.ts b/modules/customer-invoices/src/web/proformas/detail/ui/index.ts
index c4e34b27..7559bbe2 100644
--- a/modules/customer-invoices/src/web/proformas/detail/ui/index.ts
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/index.ts
@@ -1 +1,3 @@
+export * from "./blocks";
+export * from "./components";
export * from "./pages";
diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx
index d08e4110..859f3366 100644
--- a/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx
+++ b/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx
@@ -1,331 +1,20 @@
import { ErrorAlert, NotFoundCard } from "@erp/core/components";
-import { AppContent, BackHistoryButton, FieldValueList, PDFViewer } from "@repo/rdx-ui/components";
-import { MoneyHelper } from "@repo/rdx-utils";
-import {
- Alert,
- AlertDescription,
- AlertTitle,
- Button,
- Card,
- CardContent,
- CardHeader,
- CardTitle,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
- Separator,
- Skeleton,
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@repo/shadcn-ui/components";
-import { cn } from "@repo/shadcn-ui/lib/utils";
-import {
- CheckCircle2Icon,
- DownloadIcon,
- MoreHorizontalIcon,
- PencilIcon,
- RefreshCwIcon,
- SquarePenIcon,
-} from "lucide-react";
-import { type ReactNode, useMemo } from "react";
+import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
import { IssueProformaDialog } from "../../../issue-proforma";
import { ProformaStatusBadge } from "../../../list/ui/components";
-import { type Proforma, type ProformaItem, ProformaPageHeader } from "../../../shared";
+import { ProformaPageHeader } from "../../../shared";
import { useProformaDetailPageController } from "../../controllers";
-
-const ZOOM_OPTIONS = [
- { label: "Ajustar", value: "fit" },
- { label: "75%", value: "75" },
- { label: "90%", value: "90" },
- { label: "100%", value: "100" },
- { label: "125%", value: "125" },
-];
-
-const DOCUMENT_WIDTH = 794;
-const DOCUMENT_HEIGHT = 1123;
-
-const formatDate = (value: string | null | undefined) => {
- if (!value) {
- return "No informado";
- }
-
- const [year, month, day] = value.split("-");
- if (!(year && month && day)) {
- return value;
- }
-
- return `${day}/${month}/${year}`;
-};
-
-const formatMoney = (amount: number, currencyCode: string) => {
- return MoneyHelper.formatCurrency(amount, 2, currencyCode);
-};
-
-const formatQuantity = (value: number | null) => {
- if (value === null) {
- return "-";
- }
-
- return new Intl.NumberFormat("es-ES", {
- minimumFractionDigits: 0,
- maximumFractionDigits: 2,
- }).format(value);
-};
-
-const getStatusTone = (status: Proforma["status"]) => {
- switch (status) {
- case "approved":
- return "border-emerald-200 bg-emerald-50 text-emerald-900";
- case "sent":
- return "border-amber-200 bg-amber-50 text-amber-900";
- case "issued":
- return "border-sky-200 bg-sky-50 text-sky-900";
- case "rejected":
- return "border-red-200 bg-red-50 text-red-900";
- default:
- return "border-slate-200 bg-slate-50 text-slate-900";
- }
-};
-
-const getStatusSummary = (proforma: Proforma) => {
- if (proforma.isArchived) {
- return {
- title: "Proforma archivada",
- description: "Está fuera del flujo principal y puedes recuperarla cuando lo necesites.",
- };
- }
-
- switch (proforma.status) {
- case "approved":
- return {
- title: "Lista para convertirse en factura",
- description: "La proforma ya está aprobada y puede emitirse como factura.",
- };
- case "sent":
- return {
- title: "Pendiente de respuesta del cliente",
- description: "Se ha enviado y está a la espera de aceptación o rechazo.",
- };
- case "issued":
- return {
- title: "Convertida en factura",
- description: "La proforma ya ha sido emitida y forma parte del flujo fiscal.",
- };
- case "rejected":
- return {
- title: "Requiere revisión",
- description: "El cliente la ha rechazado y conviene revisar condiciones o importes.",
- };
- default:
- return {
- title: "Pendiente de revisión",
- description: "Sigue en borrador y todavía puede ajustarse antes de enviarla.",
- };
- }
-};
-
-
-const InspectorCard = ({
- title,
- onEdit,
- children,
-}: {
- title: string;
- onEdit?: () => void;
- children: ReactNode;
-}) => {
- return (
-
-
- {title}
- {onEdit ? (
-
- ) : null}
-
- {children}
-
- );
-};
-
-const StatusInspectorCard = ({ proforma }: { proforma: Proforma }) => {
- const summary = getStatusSummary(proforma);
-
- return (
-
-
- {summary.title}
- {summary.description}
-
- );
-};
-
-const BillingInspectorCard = ({
- proforma,
- paymentMethodLabel,
- paymentTermLabel,
- onEdit,
-}: {
- proforma: Proforma;
- paymentMethodLabel: string | null;
- paymentTermLabel: string | null;
- onEdit: () => void;
-}) => {
- const items = [
- {
- label: "Método de pago",
- value: paymentMethodLabel ?? "No informado",
- },
- {
- label: "Plazo",
- value: paymentTermLabel ?? "No informado",
- },
- {
- label: "Moneda",
- value: proforma.currencyCode,
- },
- ];
-
- return (
-
-
-
- );
-};
-
-const DatesInspectorCard = ({ proforma, onEdit }: { proforma: Proforma; onEdit: () => void }) => {
- const items = [
- {
- label: "Fecha proforma",
- value: formatDate(proforma.proformaDate),
- },
- {
- label: "Fecha operación",
- value: formatDate(proforma.operationDate),
- },
- {
- label: "Serie",
- value: proforma.series ?? "No informada",
- },
- {
- label: "Referencia interna",
- value: proforma.reference ?? "Sin referencia",
- },
- ];
-
- return (
-
-
-
- );
-};
-
-const NotesInspectorCard = ({ proforma, onEdit }: { proforma: Proforma; onEdit: () => void }) => {
- return (
-
-
-
-
Descripción
-
{proforma.description ?? "Sin descripción."}
-
-
-
-
Observaciones
-
{proforma.notes ?? "Sin notas adicionales."}
-
-
-
- );
-};
-
-const TotalsInspectorCard = ({ proforma }: { proforma: Proforma }) => {
- const items = [
- {
- label: "Subtotal",
- value: formatMoney(proforma.subtotalAmount, proforma.currencyCode),
- },
- {
- label: "Descuentos",
- value: `-${formatMoney(proforma.totalDiscountAmount, proforma.currencyCode)}`,
- valueClassName: "text-red-600",
- },
- {
- label: "IVA",
- value: formatMoney(proforma.ivaAmount, proforma.currencyCode),
- },
- {
- label: "Total",
- value: formatMoney(proforma.totalAmount, proforma.currencyCode),
- valueClassName: "text-lg font-semibold text-foreground",
- },
- ];
-
- return (
-
-
-
- );
-};
-
-const ProformaDetailSkeleton = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const HeaderMeta = ({ proforma }: { proforma: Proforma }) => {
- const items = useMemo(
- () => [
- proforma.recipient.name ?? "Cliente sin nombre",
- formatDate(proforma.proformaDate),
- `Total ${formatMoney(proforma.totalAmount, proforma.currencyCode)}`,
- ],
- [proforma]
- );
-
- return (
-
- {items.map((item, index) => (
-
- {index > 0 ? | : null}
- {item}
-
- ))}
-
- );
-};
+import {
+ ProformaDetailHeaderActions,
+ ProformaDetailPreviewCard,
+ ProformaDetailSidebar,
+} from "../blocks";
+import {
+ ProformaDetailArchivedBadge,
+ ProformaDetailHeaderMeta,
+ ProformaDetailSkeleton,
+} from "../components";
export const ProformaDetailPage = () => {
const ctrl = useProformaDetailPageController();
@@ -368,46 +57,14 @@ export const ProformaDetailPage = () => {
-
-
-
-
-
-
-
-
-
-
- }
- />
-
-
- Duplicar proforma
-
-
- {ctrl.proforma.isArchived ? "Desarchivar" : "Archivar"}
-
-
-
-
+
}
backLabel="Volver al listado"
onBack={ctrl.navigateBack}
@@ -416,80 +73,35 @@ export const ProformaDetailPage = () => {
-
+ {ctrl.proforma.isArchived ?
: null}
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- Líneas incluidas
-
-
-
-
-
- Concepto
- Total
-
-
-
- {ctrl.proforma.items.slice(0, 4).map((item: ProformaItem) => (
-
-
- {item.description ?? "Sin descripción"}
-
-
- {formatMoney(item.totalAmount, ctrl.proforma.currencyCode)}
-
-
- ))}
-
-
-
-
-
+