diff --git a/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts b/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts
index b11ae470..39abfe6c 100644
--- a/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts
+++ b/modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository.ts
@@ -163,6 +163,9 @@ export class SequelizePaymentMethodRepository
try {
const criteriaConverter = new CriteriaToSequelizeConverter();
const query = criteriaConverter.convert(criteria, {
+ mappings: {
+ isActive: "is_active",
+ },
searchableFields: [],
sortableFields: ["name"],
enableFullText: true,
@@ -170,6 +173,8 @@ export class SequelizePaymentMethodRepository
strictMode: true, // fuerza error si ORDER BY no permitido
});
+ console.log(query?.where);
+
query.where = {
...query.where,
company_id: companyId.toString(),
diff --git a/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts b/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts
index 7baf59a6..c5fb907f 100644
--- a/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts
+++ b/modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository.ts
@@ -117,6 +117,9 @@ export class SequelizePaymentTermRepository
const criteriaConverter = new CriteriaToSequelizeConverter();
const query = criteriaConverter.convert(criteria, {
searchableFields: [],
+ mappings: {
+ isActive: "is_active",
+ },
sortableFields: ["name"],
enableFullText: true,
database: this.database,
diff --git a/modules/core/src/common/dto/critera.dto.ts b/modules/core/src/common/dto/critera.dto.ts
index 4422c3fb..5a56147d 100644
--- a/modules/core/src/common/dto/critera.dto.ts
+++ b/modules/core/src/common/dto/critera.dto.ts
@@ -8,7 +8,16 @@ import { z } from "zod/v4";
export const FilterPrimitiveSchema = z.object({
// Campos mínimos ya normalizados por el conversor
field: z.string(),
- operator: z.string(),
+ operator: z.enum([
+ "CONTAINS",
+ "NOT_CONTAINS",
+ "NOT_EQUALS",
+ "GREATER_THAN",
+ "GREATER_THAN_OR_EQUAL",
+ "LOWER_THAN",
+ "LOWER_THAN_OR_EQUAL",
+ "EQUALS",
+ ]),
value: z.string(),
});
diff --git a/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts b/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts
index d17c6ae5..34391391 100644
--- a/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts
+++ b/modules/customer-invoices/src/web/proformas/shared/adapters/get-proforma-by-id.adapter.ts
@@ -44,6 +44,7 @@ export const GetProformaByIdAdapter = {
taxes: dto.taxes.map(mapTaxSummary),
paymentMethodId: dto.payment_method?.id ?? null,
+ paymentTermId: dto.payment_term?.id ?? null,
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
diff --git a/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts b/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts
index 127db1bb..47783818 100644
--- a/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts
+++ b/modules/customer-invoices/src/web/proformas/shared/entities/proforma.entity.ts
@@ -34,6 +34,7 @@ export interface Proforma {
taxes: ProformaTaxSummary[];
paymentMethodId: string | null;
+ paymentTermId: string | null;
subtotalAmount: number;
diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts
index d42e3b36..37ddbbe5 100644
--- a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts
+++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts
@@ -50,7 +50,8 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
hasRetentionPercentage: fiscalDefaults.defaultRetentionPercentage !== null,
retentionPercentage: fiscalDefaults.defaultRetentionPercentage,
- paymentMethodId: proforma.paymentMethodId ?? proformaDefaults.paymentMethodId,
+ paymentMethodId: proforma.paymentMethodId,
+ paymentTermId: proforma.paymentTermId,
items: proforma.items.map(mapProformaItemsToProformaItemsUpdateForm),
};
diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts
index 0f0003c7..b51e981c 100644
--- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts
+++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-payment-controller.ts
@@ -9,9 +9,29 @@ import {
import { useMemo } from "react";
export const useUpdateProformaPaymentController = () => {
- const paymentMethodsQuery = usePaymentMethodsListQuery();
+ const paymentMethodsQuery = usePaymentMethodsListQuery({
+ criteria: {
+ filters: [
+ {
+ field: "isActive",
+ operator: "EQUALS",
+ value: "true",
+ },
+ ],
+ },
+ });
- const paymentTermsQuery = usePaymentTermsListQuery();
+ const paymentTermsQuery = usePaymentTermsListQuery({
+ criteria: {
+ filters: [
+ {
+ field: "isActive",
+ operator: "EQUALS",
+ value: "true",
+ },
+ ],
+ },
+ });
const paymentMethodOptions = useMemo(() => {
return getPaymentMethodOptions(paymentMethodsQuery.data?.items ?? []);
diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts
index 279304b8..eb4d7f67 100644
--- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts
+++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-calculation.entity.ts
@@ -39,8 +39,8 @@ export interface ProformaTotals {
taxBreakdown: ProformaTaxBreakdown[];
taxTotal: number;
-
recTotal: number;
+ taxRecTotal: number;
retentionPercentage: number | null;
retentionAmount: number;
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/editor-sidebar.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/editor-sidebar.tsx
new file mode 100644
index 00000000..5bd042b6
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/editor-sidebar.tsx
@@ -0,0 +1,198 @@
+"use client";
+
+import type { CustomerSelectionOption } from "@erp/customers";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@repo/shadcn-ui/components";
+import { cn } from "@repo/shadcn-ui/lib/utils";
+import { CreditCard, Percent, Receipt, Settings, UserIcon } from "lucide-react";
+import { useState } from "react";
+
+import { useTranslation } from "../../../../i18n";
+
+import { SelectedRecipientSummary } from "./selected-recipient";
+
+interface EditorSidebarProps {
+ disabled?: boolean;
+ readOnly?: boolean;
+
+ selectedCustomer?: CustomerSelectionOption | null;
+ onChangeCustomerClick: () => void;
+ onCreateCustomerClick: () => void;
+
+ className?: string;
+}
+
+export const EditorSidebar = ({
+ disabled = false,
+ readOnly = false,
+
+ selectedCustomer,
+ onChangeCustomerClick,
+ onCreateCustomerClick,
+
+ className,
+}: EditorSidebarProps) => {
+ const [isTotalsExpanded, setIsTotalsExpanded] = useState(false);
+ const { t } = useTranslation();
+
+ const formatMoney = (value: number) => {
+ return new Intl.NumberFormat("es-ES", {
+ style: "currency",
+ currency: proforma.config.currency,
+ }).format(value);
+ };
+
+ const formatPercent = (value: number) => {
+ return new Intl.NumberFormat("es-ES", {
+ style: "percent",
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ }).format(value / 100);
+ };
+
+ return (
+
+ {/* Secciones en acordeon */}
+
+
+ {/* Cliente */}
+
+
+
+
+
+
+
+
Cliente
+ {t(
+ "form_groups.proformas.customer.description",
+ "Cliente destinatario de la proforma"
+ )}
+
+
+
+
+
+
+
+
+ {/* Impuestos */}
+
+
+
+
+
+
+
+ {/* Configuracion */}
+
+
+
+
+
+
+
+
Configuracion
+
Estado y moneda
+
+
+
+
+
+
+ {/* Condiciones de pago */}
+
+
+
+
+
+
+
+
Condiciones de pago
+
Forma y vencimiento
+
+
+
+ {""}
+
+
+
+
+ {/* Resumen de totales compacto - colapsable */}
+
{""}
+
+ );
+};
+
+interface TotalsRowProps {
+ label: string;
+ value: string;
+ className?: string;
+ small?: boolean;
+ variant?: "default" | "discount" | "tax";
+}
+
+function TotalsRow({ label, value, className, small, variant = "default" }: TotalsRowProps) {
+ const valueColorClass = {
+ default: "text-foreground",
+ discount: "text-amber-700 dark:text-amber-400",
+ tax: "text-sky-700 dark:text-sky-400",
+ };
+
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
+
+// Version compacta del sidebar para movil (solo totales)
+export function MobileTotalsBadge({
+ total,
+ currency = "EUR",
+ onClick,
+}: {
+ total: number;
+ currency?: string;
+ onClick?: () => void;
+}) {
+ const formatMoney = (value: number) => {
+ return new Intl.NumberFormat("es-ES", {
+ style: "currency",
+ currency: currency,
+ }).format(value);
+ };
+
+ return (
+
+ );
+}
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts
index 7caf7820..126b6941 100644
--- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts
@@ -1,3 +1,8 @@
+export * from "./editor-sidebar";
+export * from "./line-editor";
+export * from "./new-proforma-totals-summary";
+export * from "./proforma-compact-totals";
export * from "./proforma-line-editor";
export * from "./proforma-totals-summary";
+export * from "./proforma-update-header";
export * from "./selected-recipient";
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/new-proforma-totals-summary.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/new-proforma-totals-summary.tsx
new file mode 100644
index 00000000..5255d0b9
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/new-proforma-totals-summary.tsx
@@ -0,0 +1,284 @@
+import { MoneyHelper, PercentageHelper } from "@repo/rdx-utils";
+import { Card, CardContent, Separator } from "@repo/shadcn-ui/components";
+import { cn } from "@repo/shadcn-ui/lib/utils";
+import { Percent, Receipt, Tag } from "lucide-react";
+import type { ReactNode } from "react";
+
+import { useTranslation } from "../../../../i18n";
+import type { ProformaTotals } from "../../entities";
+
+interface NewProformaTotalsSummaryProps {
+ totals: ProformaTotals;
+ currency?: string;
+
+ showRec?: boolean;
+ showRetention?: boolean;
+
+ globalDiscountField?: ReactNode;
+
+ disabled?: boolean;
+ className?: string;
+ layout?: "vertical" | "horizontal";
+}
+
+export const NewProformaTotalsSummary = ({
+ totals,
+ currency = "EUR",
+ showRec = false,
+ showRetention = false,
+ globalDiscountField,
+ disabled = false,
+ className,
+ layout = "vertical",
+}: NewProformaTotalsSummaryProps) => {
+ const { t } = useTranslation();
+
+ const formatMoney = (value: number): string => {
+ return MoneyHelper.formatCurrency(value, 2, currency);
+ };
+
+ const formatPercent = (value: number): string => {
+ return PercentageHelper.formatPercent(value);
+ };
+
+ const retentionLabel =
+ totals.retentionPercentage === null
+ ? "Total retenciones"
+ : `Total retenciones ${formatPercent(totals.retentionPercentage)}`;
+
+ const isHorizontal = layout === "horizontal";
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
Resumen de totales
+
Desglose de la factura
+
+
+
+
+ {/* Layout horizontal: grid de 3 columnas en pantallas grandes */}
+
+ {/* Columna 1: Subtotal + Descuentos */}
+
+ {/* Subtotal */}
+
+
+
+
+ {/* Sección Descuentos */}
+
+
+
+
+ Descuentos
+
+
+
+
+
+ {globalDiscountField && (
+
+
+ {globalDiscountField}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {/* Columna 2: Base Imponible + Impuestos */}
+
+ {/* Base Imponible */}
+
+
+
+
+ {/* Sección Impuestos */}
+
+
+
+ {totals.taxBreakdown.length > 0 ? (
+ totals.taxBreakdown.map((tax) => {
+ const key = `${tax.taxPercentage}:${tax.recPercentage ?? "none"}`;
+
+ return (
+
+
+
+ {showRec && tax.recPercentage !== null && (
+
+ )}
+
+ );
+ })
+ ) : (
+
Sin impuestos aplicados
+ )}
+
+
+
+
+
+ {showRec && (
+
+ )}
+
+
+
+
+ {/* Columna 3: Total Final (y retenciones si aplica) */}
+
+ {/* Retenciones */}
+ {
+
+
+
+ }
+
+ {/* Spacer para empujar el total hacia abajo en horizontal */}
+ {/* isHorizontal &&
*/}
+
+ {/* Total Final */}
+
+
+
+ Total factura
+
+
+ {formatMoney(totals.total)}
+
+
+
+
+
+
+
+ );
+};
+
+interface TotalsRowProps {
+ label: string;
+ value: string;
+ className?: string;
+ description?: string;
+ strong?: boolean;
+ variant?: "default" | "discount" | "tax";
+}
+
+const TotalsRow = ({
+ label,
+ value,
+ description,
+ className,
+ strong = false,
+ variant = "default",
+}: TotalsRowProps) => {
+ const valueColorClass = {
+ default: strong ? "text-foreground" : "text-muted-foreground",
+ discount: "text-amber-700 dark:text-amber-400",
+ tax: "text-primary-700 dark:text-primary-400",
+ };
+
+ return (
+
+
+
+ {label}
+
+
+ {description &&
{description}
}
+
+
+
+ {value}
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-compact-totals.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-compact-totals.tsx
new file mode 100644
index 00000000..2f86d27d
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-compact-totals.tsx
@@ -0,0 +1,266 @@
+import { MoneyHelper, PercentageHelper } from "@repo/rdx-utils";
+import {
+ Button,
+ Card,
+ CardContent,
+ CardFooter,
+ Item,
+ ItemContent,
+ ItemMedia,
+ ItemTitle,
+ Separator,
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@repo/shadcn-ui/components";
+import { cn } from "@repo/shadcn-ui/lib/utils";
+import {
+ ChevronDown,
+ ChevronUp,
+ CircleMinus,
+ LandmarkIcon,
+ PercentIcon,
+ ReceiptTextIcon,
+} from "lucide-react";
+import { type ReactNode, useState } from "react";
+
+import { useTranslation } from "../../../../i18n";
+import type { ProformaTotals } from "../../entities";
+
+interface ProformaCompactTotalsProps {
+ totals: ProformaTotals;
+ currency?: string;
+
+ showRec?: boolean;
+ showRetention?: boolean;
+
+ globalDiscountField?: ReactNode;
+
+ disabled?: boolean;
+ className?: string;
+}
+
+export const ProformaCompactTotals = ({
+ totals,
+ currency = "EUR",
+ showRec = false,
+ showRetention = false,
+ globalDiscountField,
+ disabled = false,
+ className,
+}: ProformaCompactTotalsProps) => {
+ const { t } = useTranslation();
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const formatMoney = (value: number): string => {
+ return MoneyHelper.formatCurrency(value, 2, currency);
+ };
+
+ const formatPercent = (value: number): string => {
+ return PercentageHelper.formatPercent(value);
+ };
+
+ return (
+
+ {/* Panel expandible con desglose */}
+
+
+
+
+ {/* Descuentos */}
+
+
+
+
+ En lineas
+ -{formatMoney(totals.lineDiscountTotal)}
+
+
+
+ Global
+ ({formatPercent(totals.globalDiscountPercentage)})
+
+
-{formatMoney(totals.globalDiscountAmount)}
+
+
+
+ Total descuentos
+
+ -{formatMoney(totals.totalDiscountAmount)}
+
+
+
+
+
+ {/* Impuestos */}
+
+
+
+ Impuestos
+
+
+ {totals.taxBreakdown.map((tax) => {
+ const key = `${tax.taxPercentage}:${tax.recPercentage ?? "none"}`;
+
+ return (
+
+
+
+ Impuesto
+ ({formatPercent(tax.taxPercentage)})
+
+
{formatMoney(tax.taxAmount)}
+
+ {showRec && tax.recPercentage !== null ? (
+
+
+
+ {t("proformas.update.totals.recPercentage", "Rec. equiv.")}
+
+ ({formatPercent(tax.recPercentage)})
+
+
{formatMoney(totals.recTotal)}
+
+ ) : null}
+
+ );
+ })}
+
+
+
+ Total impuestos
+
+ {formatMoney(totals.taxRecTotal)}
+
+
+
+
+
+ {/* Retenciones */}
+
+
+
+ Retenciones
+
+
+ {totals.retentionPercentage ? (
+ <>
+
+
+ IRPF
+ ({formatPercent(totals.retentionPercentage)})
+
+
-{formatMoney(totals.retentionAmount)}
+
+
+
+ Total retenciones
+ -{formatMoney(totals.retentionAmount)}
+
+ >
+ ) : (
+
Sin retenciones
+ )}
+
+
+
+
+
+
+ {/* Barra principal de totales - siempre visible */}
+
+ {/* Boton expandir */}
+
+
+ {/* Totales en linea */}
+
+
+
+ {t("proformas.update.totals.subtotalBeforeDiscounts", "Subtotal")}
+
+
+ {formatMoney(totals.subtotalBeforeDiscounts)}
+
+
+
+
+
+ } />
+
+ {t("proformas.update.totals.totalDiscountAmount", "Total descuentos")}
+
+
+
+
+ {t("proformas.update.totals.totalDiscountAmount", "Dtos")}
+
+ {`-${formatMoney(totals.totalDiscountAmount)}`}
+
+
+
+
+ {t("proformas.update.totals.taxableBase", "Base imp.")}
+
+ {formatMoney(totals.taxableBase)}
+
+
+
+
+ } />
+
+ {t("proformas.update.totals.taxTotal", "Total impuestos")}
+
+
+
+ {t("proformas.update.totals.taxTotal", "Impuestos")}
+
+
+ {formatMoney(totals.taxRecTotal)}
+
+
+
+ {showRetention ? (
+
+
+ } />
+
+ {t("proformas.update.totals.retentionAmount", "Total retenciones")}
+
+
+
+ {t("proformas.update.totals.retentionAmount", "Retenc.")}
+
+
+ {`-${formatMoney(totals.retentionAmount)}`}
+
+
+ ) : null}
+
+ {/* Total destacado */}
+
-
+
+
+
+
+
+ Total{" "}
+ {formatMoney(totals.total)}
+
+
+
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx
index eead15fb..3a914024 100644
--- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-line-editor.tsx
@@ -1,6 +1,5 @@
import {
AmountField,
- CheckboxField,
LineDescriptionField,
PercentageField,
QuantityField,
@@ -66,12 +65,6 @@ export const ProformaLineEditor = ({
const { t } = useTranslation();
const columns: LineEditorColumn[] = [
- {
- id: "isValued",
- header: t("form_fields.items.description.is_valued", "¿Valorado?"),
- headClassName: "w-[100px] text-right",
- cell: ({ index }) => ,
- },
{
id: "description",
header: t("form_fields.items.description.label", "Descripción"),
@@ -101,7 +94,7 @@ export const ProformaLineEditor = ({
{
id: "unitAmount",
header: t("form_fields.items.unit_amount.label", "Importe unitario"),
- headClassName: "w-[120px] text-right",
+ headClassName: "text-right",
cell: ({ index }) => (
(
MoneyHelper.formatCurrency(getItemAmounts(index).total, 2, currency),
},
];
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx
index 7e1b219a..d18373f5 100644
--- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx
@@ -74,7 +74,7 @@ export const ProformaTotalsSummary = ({
value={`-${formatMoney(totals.lineDiscountTotal)}`}
/>
- {globalDiscountField ? {globalDiscountField}
: null}
+ {globalDiscountField ? <>{globalDiscountField}> : null}
@@ -204,8 +204,8 @@ const TotalsRow = ({ label, value, description, className, strong = false }: Tot
{value}
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-header.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-header.tsx
new file mode 100644
index 00000000..973e1900
--- /dev/null
+++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-header.tsx
@@ -0,0 +1,97 @@
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@repo/shadcn-ui/components";
+import { ArrowLeftIcon, KeyboardIcon, MoreHorizontalIcon, SaveIcon, XIcon } from "lucide-react";
+
+export interface ProformaUpdateHeaderProps {
+ onSave?: () => void;
+ onCancel?: () => void;
+
+ disabled?: boolean;
+ readOnly?: boolean;
+ hasChanges?: boolean;
+}
+
+export const ProformaUpdateHeader = ({
+ onSave,
+ onCancel,
+ disabled = false,
+ readOnly = false,
+ hasChanges = false,
+}: ProformaUpdateHeaderProps) => {
+ return (
+
+
+
+
+
+
+ Editar proforma
+
+ {hasChanges && }
+
+
+
+
+ {/* Atajo de teclado info */}
+
+
+
+
+ }
+ />
+
+
+
+ Ctrl+S Guardar
+
+
+ Esc Cancelar
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ />
+
+
+ Duplicar proforma
+ Exportar PDF
+
+ Eliminar
+
+
+
+
+
+ );
+};
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx b/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx
index 28e2239b..d1cad852 100644
--- a/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx
+++ b/modules/customer-invoices/src/web/proformas/update/ui/components/proforma-info-alert.tsx
@@ -1,11 +1,11 @@
// packages/rdx-ui/src/components/feedback/info-alert.tsx
import { Alert, AlertDescription, AlertTitle } from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
-import { InfoIcon } from "lucide-react";
+import { AlertCircleIcon } from "lucide-react";
import type * as React from "react";
type ProformaInfoAlertProps = {
- title: string;
+ title?: string;
description?: string;
children?: React.ReactNode;
className?: string;
@@ -26,27 +26,32 @@ export const ProformaInfoAlert = ({
return (
-
- {title}
-
+ {title && (
+
+ {title}
+
+ )}
{(description || children) && (
{description ?? children}
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx
index fd19e61b..493d6a2c 100644
--- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx
+++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-payment-editor.tsx
@@ -38,7 +38,6 @@ export const ProformaUpdatePaymentEditor = ({
}
title={t("form_groups.proformas.settings.title", "Configuración")}
>
-
+
-
-
-
-
-
+ {/* Alerta informativa */}
+
+ Importante: Esta proforma no tiene validez fiscal.
+ Puedes convertirla en factura cuando sea aceptada por el cliente.
+
-
+ {/* Área principal */}
+
+
+ {/* Formulario de datos básicos */}
+
+
+ {/* Tabla de líneas */}
+
+
+
+ {/* Sidebar - desktop */}
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+ }
+ layout="vertical"
+ showRec={taxCtrl.hasRecPercentage}
+ showRetention={taxCtrl.hasRetentionPercentage}
+ totals={totalsCtrl.totals}
+ />
+
-
-
-
-
-
-
-
+ {/* Footer fijo con totales */}
+
);
};
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx
index b61ba65d..724227d1 100644
--- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx
+++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-header-editor.tsx
@@ -28,14 +28,13 @@ export const ProformaUpdateHeaderEditor = ({
return (
}
title={t("form_groups.proformas.basic_info.title")}
>
Autom.
}
/>
+
-
-
-
}
- title={t("form_groups.items.title")}
+ icon={}
+ title={t("form_groups.items.title", "Líneas de detalle")}
>
{
);
+ updateCtrl.form.d;
+
return (
@@ -85,6 +88,10 @@ export const ProformaUpdatePage = () => {
+
{updateCtrl.isUpdateError && (
acc + item.recAmount, 0);
+ const taxRecTotal = roundMoney(taxTotal + recTotal);
+
const normalizedRetentionPercentage =
retentionPercentage === null ? null : toCalculationNumber(retentionPercentage);
@@ -67,6 +69,8 @@ export const calculateProformaTotalsFromLines = ({
recTotal: roundMoney(recTotal),
+ taxRecTotal: roundMoney(taxRecTotal),
+
retentionPercentage: normalizedRetentionPercentage,
retentionAmount: roundMoney(retentionAmount),