diff --git a/apps/web/src/components/module-routes.tsx b/apps/web/src/components/module-routes.tsx index 281493ca..395d7c1c 100644 --- a/apps/web/src/components/module-routes.tsx +++ b/apps/web/src/components/module-routes.tsx @@ -1,57 +1,146 @@ -import type { IModuleClient, ModuleClientParams } from "@erp/core/client"; -import type { JSX } from "react"; -import { type RouteObject, useRoutes } from "react-router-dom"; +import type { + IModuleClient, + ModuleClientParams, + ModuleRouteLayout, + ModuleRouteObject, +} from "@erp/core/client"; +import type { ReactNode } from "react"; +import type { RouteObject } from "react-router-dom"; -interface ModuleRoutesProps { +import { RequireAuthRouteGuard } from "@/routes/require-auth-guard"; + +interface BuildModuleRoutesParams { modules: IModuleClient[]; params: ModuleClientParams; + layout: ModuleRouteLayout; } -interface WarpIfProtectedProps { - component: JSX.Element; - isProtected: boolean; +interface NormalizeRouteParams { + route: ModuleRouteObject; + module: IModuleClient; + targetLayout: ModuleRouteLayout; + inheritedLayout?: ModuleRouteLayout; + inheritedProtected?: boolean; } -// biome-ignore lint/correctness/noUnusedVariables: -const WarpIfProtected = ({ component, isProtected }: WarpIfProtectedProps) => { - return isProtected ? <>{component} : component; +const DEFAULT_LAYOUT: ModuleRouteLayout = "app-sidebar"; + +const resolveRouteLayout = ( + route: ModuleRouteObject, + module: IModuleClient, + inheritedLayout?: ModuleRouteLayout +): ModuleRouteLayout => { + return route.handle?.layout ?? inheritedLayout ?? module.layout ?? DEFAULT_LAYOUT; }; -export const ModuleRoutes = ({ modules, params }: ModuleRoutesProps) => { +const resolveRouteProtected = ( + route: ModuleRouteObject, + module: IModuleClient, + inheritedProtected?: boolean +): boolean => { + return route.handle?.protected ?? inheritedProtected ?? module.protected ?? false; +}; + +const wrapProtectedElement = (element: ReactNode, isProtected: boolean): ReactNode => { + if (!isProtected) { + return element; + } + + return {element}; +}; + +const normalizeRoute = ({ + route, + module, + targetLayout, + inheritedLayout, + inheritedProtected, +}: NormalizeRouteParams): RouteObject | null => { + const routeLayout = resolveRouteLayout(route, module, inheritedLayout); + const routeProtected = resolveRouteProtected(route, module, inheritedProtected); + + const normalizedChildren = route.children + ?.map((child) => + normalizeRoute({ + route: child, + module, + targetLayout, + inheritedLayout: routeLayout, + inheritedProtected: routeProtected, + }) + ) + .filter((child): child is RouteObject => child !== null); + + const hasMatchingLayout = routeLayout === targetLayout; + const hasChildren = Boolean(normalizedChildren?.length); + const hasElement = route.element !== undefined || route.Component !== undefined; + + if (!(hasMatchingLayout || hasChildren)) { + return null; + } + + if (!hasMatchingLayout && hasChildren) { + return { + path: route.path, + index: route.index, + children: normalizedChildren, + } as RouteObject; + } + + const { children: _children, handle, element, ...rest } = route; + + const normalizedRoute = { + ...rest, + handle, + element: element ? wrapProtectedElement(element, routeProtected) : undefined, + children: normalizedChildren, + } as RouteObject; + + if (!(hasElement || hasChildren)) { + console.warn( + `[ModuleRoutes] Ruta sin element ni children válidos: "${String(route.path ?? "index")}"` + ); + return null; + } + + return normalizedRoute; +}; + +export const buildModuleRoutes = ({ + modules, + params, + layout, +}: BuildModuleRoutesParams): RouteObject[] => { const routes: RouteObject[] = []; - if (modules) { - for (const module of modules) { - if (typeof module.routes !== "function") { - console.warn(`[ModuleRoutes] El módulo "${module.name}" no define una función 'routes()'`); - continue; - } + for (const module of modules) { + if (typeof module.routes !== "function") { + console.warn(`[ModuleRoutes] El módulo "${module.name}" no define una función routes().`); + continue; + } - const moduleRoutes = module.routes(params); + const moduleRoutes = module.routes(params); - if (!Array.isArray(moduleRoutes)) { - console.error( - `[ModuleRoutes] El módulo "${module.name}" debe devolver un RouteObject[], pero devolvió:`, - moduleRoutes - ); - continue; - } - - const allAreRouteObjects = moduleRoutes.every( - (r) => typeof r === "object" && r.element !== undefined + if (!Array.isArray(moduleRoutes)) { + console.error( + `[ModuleRoutes] El módulo "${module.name}" debe devolver un ModuleRouteObject[], pero devolvió:`, + moduleRoutes ); + continue; + } - if (!allAreRouteObjects) { - console.error( - `[ModuleRoutes] El módulo "${module.name}" contiene elementos inválidos en su RouteObject[]`, - moduleRoutes - ); - continue; + for (const route of moduleRoutes) { + const normalizedRoute = normalizeRoute({ + route, + module, + targetLayout: layout, + }); + + if (normalizedRoute) { + routes.push(normalizedRoute); } - - routes.push(...moduleRoutes); } } - return useRoutes(routes); + return routes; }; diff --git a/apps/web/src/layout/app-fullscreen-layout.tsx b/apps/web/src/layout/app-fullscreen-layout.tsx new file mode 100644 index 00000000..314ead21 --- /dev/null +++ b/apps/web/src/layout/app-fullscreen-layout.tsx @@ -0,0 +1,9 @@ +import { Outlet } from "react-router-dom"; + +export const AppFullscreenLayout = () => { + return ( +
+ +
+ ); +}; diff --git a/apps/web/src/layout/app-layout.tsx b/apps/web/src/layout/app-sidebar-layout.tsx similarity index 94% rename from apps/web/src/layout/app-layout.tsx rename to apps/web/src/layout/app-sidebar-layout.tsx index db18620e..a5ef6dfc 100644 --- a/apps/web/src/layout/app-layout.tsx +++ b/apps/web/src/layout/app-sidebar-layout.tsx @@ -6,7 +6,7 @@ import { AppMain } from "./app-main"; import { AppSidebar } from "./app-sidebar"; import { AppTopbar } from "./app-topbar"; -export const AppLayout = () => { +export const AppSidebarLayout = () => { return ( { + return ( +
+
+ +
+
+ ); +}; diff --git a/apps/web/src/layout/index.ts b/apps/web/src/layout/index.ts index 87af5708..bfe3fb03 100644 --- a/apps/web/src/layout/index.ts +++ b/apps/web/src/layout/index.ts @@ -1,4 +1,6 @@ -export * from './app-layout'; -export * from './app-main'; -export * from './app-sidebar'; -export * from './app-topbar'; +export * from "./app-fullscreen-layout"; +export * from "./app-main"; +export * from "./app-sidebar"; +export * from "./app-sidebar-layout"; +export * from "./app-topbar"; +export * from "./auth-layout"; diff --git a/apps/web/src/routes/app-routes.tsx b/apps/web/src/routes/app-routes.tsx index 7f87c707..c06902f6 100644 --- a/apps/web/src/routes/app-routes.tsx +++ b/apps/web/src/routes/app-routes.tsx @@ -1,66 +1,103 @@ // apps/web/src/routes/app-routes.tsx -import type { IModuleClient } from "@erp/core/client"; -import { Navigate, Route, createBrowserRouter, createRoutesFromElements } from "react-router-dom"; -import { ModuleRoutes } from "@/components/module-routes"; -import { AppLayout } from "@/layout"; +import { Navigate, createBrowserRouter } from "react-router-dom"; + +import { buildModuleRoutes } from "@/components/module-routes"; +import { AppFullscreenLayout, AppSidebarLayout, AuthLayout } from "@/layout"; import ShadcnShowcasePage from "@/pages/shadcn-ui-page"; import TailwindV4ShowcasePage from "@/pages/tailwindcss-page"; -import { ErrorPage, LoginForm } from "../pages"; +import { ErrorPage } from "../pages"; import { modules } from "../register-modules"; -function groupModulesByLayout(modules: IModuleClient[]) { - const groups: Record = { - auth: [], - app: [], - }; - - for (const module of modules) { - const layout = typeof module.layout === "string" ? module.layout : "app"; - - groups[layout] = groups[layout] ?? []; - groups[layout].push(module); - } - - return groups; -} - export const getAppRouter = () => { const params = { ...import.meta.env, }; - const grouped = groupModulesByLayout(modules); + const authRoutes = buildModuleRoutes({ + modules, + params, + layout: "auth", + }); - return createBrowserRouter( - createRoutesFromElements( - - } index /> + const appSidebarRoutes = buildModuleRoutes({ + modules, + params, + layout: "app-sidebar", + }); - {/* Auth Layout */} - - } index /> - } path="login" /> - } path="*" /> - + const appFullscreenRoutes = buildModuleRoutes({ + modules, + params, + layout: "app-fullscreen", + }); - {/* App Layout */} - }> - {/* Test */} - } path="shadcnui" /> - } path="tailwindcss4" /> + const appRoutes = createBrowserRouter([ + { + path: "/", + children: [ + { + index: true, + element: , + }, - {/* Static / provisional routes */} - } path="dashboard" /> - } path="settings" /> - } path="catalog" /> - } path="quotes" /> + { + path: "auth", + element: , + children: [ + { + index: true, + element: , + }, + ...authRoutes, + ], + }, - {/* Dynamic module routes. Keep this last. */} - } path="*" /> - - - ) - ); + { + element: , + children: appFullscreenRoutes, + }, + + { + element: , + children: [ + { + path: "shadcnui", + element: , + }, + { + path: "tailwindcss4", + element: , + }, + { + path: "dashboard", + element: , + }, + { + path: "settings", + element: , + }, + { + path: "catalog", + element: , + }, + { + path: "quotes", + element: , + }, + + ...appSidebarRoutes, + ], + }, + + { + path: "*", + element: , + }, + ], + }, + ]); + + return appRoutes; }; diff --git a/apps/web/src/routes/public-only-guard.tsx b/apps/web/src/routes/public-only-guard.tsx new file mode 100644 index 00000000..fc10b991 --- /dev/null +++ b/apps/web/src/routes/public-only-guard.tsx @@ -0,0 +1,18 @@ +// apps/web/src/routes/guards/public-only-route.tsx +import type { ReactNode } from "react"; +import { Navigate } from "react-router-dom"; + +interface PublicOnlyRouteProps { + children: ReactNode; +} + +export const PublicOnlyRouteGuard = ({ children }: PublicOnlyRouteProps) => { + // TODO: sustituir por tu estado real de auth. + const isAuthenticated = false; + + if (isAuthenticated) { + return ; + } + + return <>{children}; +}; diff --git a/apps/web/src/routes/require-auth-guard.tsx b/apps/web/src/routes/require-auth-guard.tsx new file mode 100644 index 00000000..e02752b3 --- /dev/null +++ b/apps/web/src/routes/require-auth-guard.tsx @@ -0,0 +1,20 @@ +// apps/web/src/routes/guards/require-auth.tsx +import type { ReactNode } from "react"; +import { Navigate, useLocation } from "react-router-dom"; + +interface RequireAuthRouteProps { + children: ReactNode; +} + +export const RequireAuthRouteGuard = ({ children }: RequireAuthRouteProps) => { + const location = useLocation(); + + // TODO: sustituir por tu estado real de auth. + const isAuthenticated = true; + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +}; diff --git a/modules/auth/src/web/auth-routes.tsx b/modules/auth/src/web/auth-routes.tsx index 6e360537..4b47c91e 100644 --- a/modules/auth/src/web/auth-routes.tsx +++ b/modules/auth/src/web/auth-routes.tsx @@ -1,27 +1,33 @@ -import { ModuleClientParams } from "@erp/core/client"; -import { Outlet, RouteObject } from "react-router-dom"; -import { AuthLayout } from "./components"; +import type { ModuleClientParams } from "@erp/core/client"; +import type { RouteObject } from "react-router-dom"; + import { LoginPage } from "./pages"; export const AuthRoutes = (params: ModuleClientParams): RouteObject[] => { return [ { - path: "*", - element: ( - - - - ), - children: [ - { - path: "login", - element: , - }, - { - path: "register", - element:
Register
, - }, - ], + path: "login", + handle: { + layout: "auth", + protected: false, + }, + element: , + }, + { + path: "register", + handle: { + layout: "auth", + protected: false, + }, + //element: , + }, + { + path: "forgot-password", + handle: { + layout: "auth", + protected: false, + }, + //element: , }, ]; }; diff --git a/modules/auth/src/web/manifest.ts b/modules/auth/src/web/manifest.ts index c44d73b0..8484f91f 100644 --- a/modules/auth/src/web/manifest.ts +++ b/modules/auth/src/web/manifest.ts @@ -1,10 +1,7 @@ -import type { IModuleClient, ModuleClientParams } from "@erp/core/client"; +import type { IModuleClient } from "@erp/core/client"; import { AuthRoutes } from "./auth-routes"; -//import enResources from "../common/locales/en.json"; -//import esResources from "../common/locales/es.json"; - const MODULE_NAME = "auth"; const MODULE_VERSION = "1.0.0"; @@ -13,13 +10,10 @@ export const AuthModuleManifest: IModuleClient = { version: MODULE_VERSION, dependencies: ["core"], protected: false, + layout: "auth", - routes: (params: ModuleClientParams) => { - //i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true); - //i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true); - return AuthRoutes(params); - }, + routes: (params) => AuthRoutes(params), }; export default AuthModuleManifest; diff --git a/modules/core/src/web/lib/modules/module-client.interface.ts b/modules/core/src/web/lib/modules/module-client.interface.ts index b6464dd4..0d93f771 100644 --- a/modules/core/src/web/lib/modules/module-client.interface.ts +++ b/modules/core/src/web/lib/modules/module-client.interface.ts @@ -3,11 +3,34 @@ import type { RouteObject } from "react-router-dom"; import type { ModuleMetadata } from "../../../common"; -export type ModuleClientParams = { [key: string]: any }; +export type ModuleClientParams = Record; + +export type ModuleRouteLayout = "app-sidebar" | "auth" | "app-fullscreen"; + +export interface ModuleRouteHandle { + layout?: ModuleRouteLayout; + protected?: boolean; +} + +export type ModuleRouteObject = Omit & { + children?: ModuleRouteObject[]; + handle?: RouteObject["handle"] & ModuleRouteHandle; +}; export interface IModuleClient extends ModuleMetadata { - protected?: boolean; // determina si las rutas deben ser protegidas + /** + * Default de protección para las rutas del módulo. + * Puede sobrescribirse por ruta con handle.protected. + */ + protected?: boolean; + + /** + * Default de layout para las rutas del módulo. + * Puede sobrescribirse por ruta con handle.layout. + */ + layout?: ModuleRouteLayout; + icon?: ReactNode; - routes?: (params: ModuleClientParams) => RouteObject[]; - layout?: "app" | "auth"; + + routes?: (params: ModuleClientParams) => ModuleRouteObject[]; } diff --git a/modules/customer-invoices/src/web/customer-invoice-routes.tsx b/modules/customer-invoices/src/web/customer-invoice-routes.tsx index 2b14b241..214006ff 100644 --- a/modules/customer-invoices/src/web/customer-invoice-routes.tsx +++ b/modules/customer-invoices/src/web/customer-invoice-routes.tsx @@ -30,20 +30,42 @@ export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[] return [ { path: "proformas", + handle: { + layout: "app-sidebar", + protected: true, + }, element: ( ), children: [ - { index: true, element: }, // index - { path: "list", element: }, - //{ path: "create", element: }, - { path: ":id/edit", element: }, + { + index: true, + element: , + }, + { + path: "list", + element: , + }, ], }, + + { + path: "proformas/:id/edit", + handle: { + layout: "app-fullscreen", + protected: true, + }, + element: , + }, + { path: "customer-invoices", + handle: { + layout: "app-sidebar", + protected: true, + }, element: ( diff --git a/modules/customer-invoices/src/web/manifest.ts b/modules/customer-invoices/src/web/manifest.ts index ff36ef44..3b89e0f3 100644 --- a/modules/customer-invoices/src/web/manifest.ts +++ b/modules/customer-invoices/src/web/manifest.ts @@ -1,4 +1,4 @@ -import type { IModuleClient, ModuleClientParams } from "@erp/core/client"; +import type { IModuleClient } from "@erp/core/client"; import { CustomerInvoiceRoutes } from "./customer-invoice-routes"; @@ -9,12 +9,11 @@ export const CustomerInvoicesModuleManifest: IModuleClient = { name: MODULE_NAME, version: MODULE_VERSION, dependencies: ["auth", "Core", "Catalogs", "Customers"], - protected: true, - layout: "app", - routes: (params: ModuleClientParams) => { - return CustomerInvoiceRoutes(params); - }, + protected: true, // protegido por defecto + layout: "app-sidebar", // layout por defecto + + routes: (params) => CustomerInvoiceRoutes(params), }; export default CustomerInvoicesModuleManifest; 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 3a914024..42f6d48e 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,5 +1,6 @@ import { AmountField, + FormFieldLabel, LineDescriptionField, PercentageField, QuantityField, @@ -14,6 +15,7 @@ import type { ProformaItemField, ProformaItemsTotals, } from "../../controllers"; +import type { ProformaTotals } from "../../entities"; import { LineEditor, type LineEditorColumn } from "./line-editor"; @@ -25,7 +27,8 @@ interface ProformaLineEditorProps { getItemAmounts: (index: number) => ProformaItemAmounts; getItemErrorMessage: (index: number) => string | undefined; - totals: ProformaItemsTotals; + itemsTotals: ProformaItemsTotals; + totals: ProformaTotals; addItemAtStart: () => void; appendItem: () => void; @@ -39,6 +42,7 @@ interface ProformaLineEditorProps { currency?: string; className?: string; } +2; export const ProformaLineEditor = ({ fields, @@ -48,6 +52,7 @@ export const ProformaLineEditor = ({ getItemAmounts, getItemErrorMessage, + itemsTotals, totals, addItemAtStart, @@ -94,7 +99,7 @@ export const ProformaLineEditor = ({ { id: "unitAmount", header: t("form_fields.items.unit_amount.label", "Importe unitario"), - headClassName: "text-right", + headClassName: "w-[200px] text-right", cell: ({ index }) => ( MoneyHelper.formatCurrency(getItemAmounts(index).total, 2, currency), }, @@ -174,6 +179,79 @@ export const ProformaLineEditor = ({ onRemove={removeItem} removeLabel={t("common.remove", "Eliminar")} renderFooter={() => ( + + )} + title={t("form_fields.items.title", "Líneas de detalle")} + /> + ); +}; + +type ProformaLineFooterEditorProps = Pick< + ProformaLineEditorProps, + "totals" | "itemsTotals" | "currency" +>; + +export const ProformaLineFooterEditor = ({ + totals, + itemsTotals, + currency, +}: ProformaLineFooterEditorProps) => { + const { t } = useTranslation(); + + return ( +
+ {/* Global discount */} +
+ Descuento global (%): +
+ +
+ +
+
+ {t("form_fields.items.total.label", "Total dto. global")}: + + {MoneyHelper.formatCurrency(totals.globalDiscountAmount, 2, currency)} + +
+
+ {/*totals.descuentoGlobal > 0 && ( + + -{MoneyHelper.formatCurrency(descuentoGlobal)} + + )*/} +
+ + {/* Totals summary */} +
+
+
+ {t("form_fields.items.total.label", "Total dto. líneas")}: + + {MoneyHelper.formatCurrency(itemsTotals.discountAmount, 2, currency)} + +
+
+ +
+
+ {t("form_fields.items.total.label", "Total")}: + + {MoneyHelper.formatCurrency(itemsTotals.total, 2, currency)} + +
+
+
+
+ ); +}; + +/* +(
@@ -186,8 +264,5 @@ export const ProformaLineEditor = ({
- )} - title={t("form_fields.items.title", "Líneas de detalle")} - /> - ); -}; + ) +*/ diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx index f5fc51ee..158c7793 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx @@ -18,7 +18,6 @@ import type { UseUpdateProformaTaxControllerResult, UseUpdateProformaTotalsControllerResult, } from "../../controllers"; -import { EditorSidebar } from "../blocks"; import { NewProformaTotalsSummary } from "../blocks/new-proforma-totals-summary"; import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor"; @@ -72,35 +71,6 @@ export const ProformaUpdateEditorForm = ({ onKeyDown={preventEnterKeySubmitForm} onSubmit={onSubmit} > - {/* Contenido principal */} -
- {/* Área principal */} -
-
- {/* Formulario de datos básicos */} - - - {/* Tabla de líneas */} - -
-
- - {/* Sidebar - desktop */} - -
-
@@ -111,6 +81,7 @@ export const ProformaUpdateEditorForm = ({ disabled={isSubmitting} itemsCtrl={itemsCtrl} taxCtrl={taxCtrl} + totalsCtrl={totalsCtrl} />
diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx index dffeb0ac..99feaf34 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx @@ -3,22 +3,28 @@ import { ListIcon } from "lucide-react"; import type { ComponentProps } from "react"; import { useTranslation } from "../../../../i18n"; -import type { UseUpdateProformaTaxControllerResult } from "../../controllers"; -import type { UseUpdateProformaItemsControllerResult } from "../../controllers/use-update-proforma-items-controller"; +import type { + UseUpdateProformaItemsControllerResult, + UseUpdateProformaTaxControllerResult, + UseUpdateProformaTotalsControllerResult, +} from "../../controllers"; import { ProformaLineEditor } from "../blocks"; interface ProformaUpdateItemsEditorProps extends ComponentProps<"fieldset"> { + totalsCtrl: UseUpdateProformaTotalsControllerResult; itemsCtrl: UseUpdateProformaItemsControllerResult; taxCtrl: UseUpdateProformaTaxControllerResult; } export const ProformaUpdateItemsEditor = ({ + totalsCtrl, itemsCtrl, taxCtrl, disabled, ...props }: ProformaUpdateItemsEditorProps) => { const { t } = useTranslation(); + const { totals } = totalsCtrl; return ( ); diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx index 26ba4b0a..88433a27 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx @@ -5,6 +5,7 @@ import { SwitchField, } from "@repo/rdx-ui/components"; import { PercentageHelper } from "@repo/rdx-utils"; +import { Separator } from "@repo/shadcn-ui/components"; import { ReceiptTextIcon } from "lucide-react"; import { useTranslation } from "../../../../i18n"; @@ -42,7 +43,7 @@ export const ProformaUpdateTaxEditor = ({ )} disabled={disabled} icon={} - title={t("form_groups.proformas.taxes.title", "Impuestos")} + title={t("form_groups.proformas.taxes.title", "Impuestos y retenciones")} > (typeof value === "number" ? String(value) : "")} /> + +