diff --git a/docs/frontend/editor-layout-audit.md b/docs/frontend/editor-layout-audit.md new file mode 100644 index 00000000..458255e8 --- /dev/null +++ b/docs/frontend/editor-layout-audit.md @@ -0,0 +1,274 @@ +# Auditoría De Layouts De Editores + +Fecha de auditoría: 18 de agosto de 2026. + +## Objetivo + +Inventariar los editores reales del frontend del ERP y clasificarlos respecto a la norma visual vigente de edición focalizada. + +Esta auditoría: + +- inventaría rutas, páginas y dialogs de edición activos; +- clasifica cada caso por tipo; +- evalúa el layout actual frente a la norma; +- detecta desviaciones y excepciones justificadas; +- propone una secuencia pequeña de migración. + +Esta auditoría no migra masivamente las pantallas. + +## Norma visual de referencia + +SSOT vigente: + +- `packages/rdx-ui/docs/page-layouts.md` +- `packages/rdx-ui/docs/contributing-ui.md` + +Resumen operativo: + +- los editores de página completa deben usar por defecto shell `app-fullscreen`; +- la navegación mínima sale por `PageFormHeader` o un header de dominio equivalente; +- el contenido del editor se centra mediante `RdxEditorPageLayout`; +- `complexity="simple"` aplica a formularios focalizados centrados; +- `complexity="complex"` aplica a documentos o editores con `main + aside`, bloques especializados y footer sticky; +- los dialogs e inline editors no deben migrarse automáticamente a fullscreen. + +## Método de investigación + +Búsquedas y fuentes revisadas: + +- rutas y layouts en `apps/web/src/routes/app-routes.tsx` y `modules/*/src/web/*routes.tsx` +- contratos de layout en `modules/core/src/web/lib/modules/module-client.interface.ts` +- primitivas Rdx en: + - `packages/rdx-ui/src/components/editor-page/rdx-editor-page-layout.tsx` + - `packages/rdx-ui/src/components/editor-page/rdx-editor-sticky-footer.tsx` + - `modules/core/src/web/components/form/form-actions/page-form-header.tsx` + - `apps/web/src/layout/app-fullscreen-layout.tsx` + - `apps/web/src/layout/app-sidebar-layout.tsx` +- módulos revisados: + - `identity` + - `customers` + - `customer-invoices` + - `companies` + - `catalogs` + - `document-series` + - `supplier` +- búsquedas textuales sobre: + - `layout: "app-fullscreen"` + - `layout: "app-sidebar"` + - `RdxEditorPageLayout` + - `PageFormHeader` + - `RdxEditorStickyFooter` + - `create` + - `update` + - `edit` + - `Dialog` + +## Inventario + +| Module | Feature | Route | Type | Current layout | Rdx editor layout | Compliance | Risk | Priority | Recommended action | Evidence | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `identity` | Account self-service | `/account` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como referencia de editor simple focalizado. | `modules/identity/src/web/account-routes.tsx`, `modules/identity/src/web/account/ui/pages/account-page.tsx` | +| `identity` | Change password dialog | Sin ruta propia. Dialog dentro de `/account` | `DIALOG` | Host `app-fullscreen` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como dialog de edición contenida. | `modules/identity/src/web/account/ui/pages/account-page.tsx` | +| `customers` | Create customer | `/customers/create` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `LOW` | `-` | Ninguna. Referencia vigente de editor simple. | `modules/customers/src/web/customer-routes.tsx`, `modules/customers/src/web/create/ui/pages/customer-create-page.tsx` | +| `customers` | Update customer | `/customers/:id/edit` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `LOW` | `-` | Ninguna. Referencia vigente de editor simple. | `modules/customers/src/web/customer-routes.tsx`, `modules/customers/src/web/update/ui/pages/customer-update-page.tsx` | +| `customers` | Quick create customer | Sin ruta propia. Dialog reutilizado desde proformas | `DIALOG` | Host `app-fullscreen` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantenerlo como creación rápida embebida. | `modules/customers/src/common/features/quick-create-customer/ui/dialogs/quick-create-customer-dialog.tsx` | +| `companies` | Company details settings | `/settings/company/details` | `PAGE_SIMPLE` | `app-sidebar` | No usa `RdxEditorPageLayout` | `EXCEPTION_JUSTIFIED` | `MEDIUM` | `P3` | Mantener en settings por ahora. Revaluar solo si se define una variante focalizada específica para formularios de settings. | `modules/companies/src/web/company-routes.tsx`, `modules/companies/src/web/settings/company-details/ui/pages/company-details-settings-page.tsx` | +| `customer-invoices` | Verifactu settings | `/settings/billing/verifactu` | `PAGE_SIMPLE` | `app-sidebar` | No usa `RdxEditorPageLayout` | `EXCEPTION_JUSTIFIED` | `MEDIUM` | `P3` | Mantener en settings por ahora. Revaluar junto con una política específica de formularios largos dentro de settings. | `modules/customer-invoices/src/web/customer-invoice-routes.tsx`, `modules/customer-invoices/src/web/settings/verifactu/ui/pages/company-verifactu-settings-page.tsx` | +| `customer-invoices` | Create proforma | `/proformas/create` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `MEDIUM` | `-` | Ninguna. Mantener como bootstrap inicial de proforma hacia el editor documental completo. | `modules/customer-invoices/src/web/customer-invoice-routes.tsx`, `modules/customer-invoices/src/web/proformas/create/ui/pages/proforma-create-page.tsx`, `modules/customer-invoices/src/web/proformas/create/ui/blocks/proforma-create-form.tsx` | +| `customer-invoices` | Update proforma | `/proformas/:id/edit` | `PAGE_COMPLEX` | `app-fullscreen` | `complex` | `COMPLIANT` | `HIGH` | `-` | Ninguna. Mantener como referencia vigente de editor complejo. | `modules/customer-invoices/src/web/customer-invoice-routes.tsx`, `modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx`, `modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx` | +| `customer-invoices` | Change proforma status | Sin ruta propia. Dialog usado desde listado y detalle | `DIALOG` | Host mixto (`app-sidebar` o `app-fullscreen`) | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Es una edición contenida por dialog, no candidata a fullscreen. | `modules/customer-invoices/src/web/proformas/change-status/ui/change-status-dialog.tsx`, `modules/customer-invoices/src/web/proformas/list/ui/pages/list-proformas-page.tsx`, `modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx` | +| `catalogs` | Payment methods create/update | Host: `/settings/general/payment-methods` | `DIALOG` | Host `app-sidebar` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como edición contenida en dialog dentro de settings. | `modules/catalogs/src/web/catalogs-routes.tsx`, `modules/catalogs/src/web/settings/payment-methods/ui/pages/payment-methods-settings-page.tsx`, `modules/catalogs/src/web/settings/payment-methods/ui/components/payment-method-form-dialog.tsx` | +| `catalogs` | Payment terms create/update | Host: `/settings/general/payment-terms` | `DIALOG` | Host `app-sidebar` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como dialog aunque su contenido interno sea más rico. | `modules/catalogs/src/web/catalogs-routes.tsx`, `modules/catalogs/src/web/settings/payment-terms/ui/pages/payment-terms-settings-page.tsx`, `modules/catalogs/src/web/settings/payment-terms/ui/components/payment-term-form-dialog.tsx` | +| `catalogs` | Tax regimes create/update | Host: `/settings/general/tax-regimes` | `DIALOG` | Host `app-sidebar` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como dialog de edición contenida. | `modules/catalogs/src/web/catalogs-routes.tsx`, `modules/catalogs/src/web/settings/tax-regimes/ui/pages/tax-regimes-settings-page.tsx`, `modules/catalogs/src/web/settings/tax-regimes/ui/components/tax-regime-form-dialog.tsx` | +| `customer-invoices` | Issued invoices create/edit references | Referencias a `/issued-invoices/create` y `/customer-invoices/:id/edit`, sin ruta activa | `UNCLEAR` | Sin ruta activa | No aplica | `UNKNOWN` | `MEDIUM` | `P2` | Aclarar si esas rutas deben existir o limpiar referencias muertas antes de cualquier trabajo visual. | `modules/customer-invoices/src/web/issued-invoices/list/ui/pages/list-issued-invoices-page.tsx`, `modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/issued-invoices-grid.tsx`, `modules/customer-invoices/src/web/customer-invoice-routes.tsx` | +| `document-series` | Frontend editor activo | No detectado | `LEGACY_OR_UNUSED` | Sin route frontend | No aplica | `UNKNOWN` | `LOW` | `P3` | Confirmar si el módulo tendrá UI activa antes de cualquier migración de layout. | Sin `src/web/*routes.tsx` ni páginas frontend activas en `modules/document-series` | +| `supplier` | Frontend editor activo | No detectado | `LEGACY_OR_UNUSED` | Sin route frontend | No aplica | `UNKNOWN` | `LOW` | `P3` | Resolver primero si el módulo se reactiva o se limpia; no priorizar migración visual. | Sin `src/web/*routes.tsx`; solo `modules/supplier/src/web/shared/*` | + +## Editores conformes + +Conformes respecto a la norma vigente: + +- `customers/create` +- `customers/update` +- `/account` +- `proformas/:id/edit` + +Motivos comunes observados: + +- ruta bajo `app-fullscreen`; +- navegación mínima por `PageFormHeader` o header de dominio equivalente; +- uso explícito de `RdxEditorPageLayout`; +- geometría coherente con `simple` o `complex` según caso; +- acciones de salida/guardado visibles y no delegadas al sidebar. + +## Desviaciones + +### 1. `proformas/create` + +HECHO OBSERVADO + +- la ruta usa `app-fullscreen`; +- la página usa header de edición y `RdxEditorPageLayout complexity="simple"`; +- el flujo de alta actual solo captura cabecera inicial y deriva después al editor documental completo; +- no existe todavía editor de líneas, `aside`, totales sticky ni footer persistente equivalentes a `proformas/:id/edit`. + +IMPACTO + +- el create queda alineado con el patrón focalizado simple sin fingir que ya es el editor documental completo; +- la diferencia visual restante frente a `update` responde a una diferencia funcional real y no a drift accidental de layout. + +DECISIÓN + +- `PAGE_SIMPLE`; +- `COMPLIANT`. + +### 2. Referencias a create/edit de issued invoices sin ruta activa + +HECHO OBSERVADO + +- hay referencias a `/issued-invoices/create` y a `/customer-invoices/:id/edit`; +- `customer-invoice-routes.tsx` no declara esas rutas activas. + +IMPACTO + +- el inventario de editores queda ambiguo en esa área; +- puede existir deuda de navegación muerta o una funcionalidad incompleta; +- no conviene planificar migración visual ahí hasta aclarar si hay editor real. + +RECOMENDACIÓN + +- confirmar si esas rutas deben implementarse; +- si no, limpiar referencias muertas antes de meterlas en cualquier backlog de layouts. + +## Excepciones justificadas + +### Company details settings + +HECHO OBSERVADO + +- es un formulario de página completa; +- vive bajo `/settings/company/details`; +- depende del shell de settings con `app-sidebar`. + +INFERENCIA + +- la navegación lateral de settings forma parte funcional de la tarea y hoy actúa como mecanismo primario de cambio de subsección. + +DECISIÓN DE AUDITORÍA + +- `EXCEPTION_JUSTIFIED` por ahora; +- no priorizar fullscreen salvo que el sistema de settings evolucione con una variante focalizada explícita. + +### Verifactu settings + +HECHO OBSERVADO + +- es un formulario de configuración largo; +- vive bajo `/settings/billing/verifactu`; +- comparte patrón de settings con shell lateral. + +INFERENCIA + +- igual que en `company/details`, la tarea sigue integrada en navegación de settings y no en un editor aislado. + +DECISIÓN DE AUDITORÍA + +- `EXCEPTION_JUSTIFIED` por ahora; +- revisar sólo si se aborda una política transversal de formularios largos dentro de settings. + +## Dialogs e inline editors + +Dialogs correctamente clasificados y no candidatos automáticos a fullscreen: + +- `QuickCreateCustomerDialog` +- `PaymentMethodFormDialog` +- `PaymentTermFormDialog` +- `TaxRegimeFormDialog` +- dialog de cambio de contraseña de `/account` +- `ChangeProformaStatusDialog` + +No se detectaron rutas independientes que deban reclasificarse desde `DIALOG` o `INLINE` a `PAGE_*`. + +No se detectaron inline editors de entidad de primer nivel con route propia. Sí existen bloques especializados internos, como el editor de líneas de proformas, pero forman parte de un `PAGE_COMPLEX` y no deben inventariarse como pantalla independiente. + +## Incoherencias create/update + +Agregados revisados: + +- `customers` + - `create` y `update` comparten patrón y están alineados. +- `proformas` + - `create` usa `RdxEditorPageLayout complexity="simple"` porque hoy es un bootstrap inicial; + - `update` usa `RdxEditorPageLayout complexity="complex"` porque ya es el editor documental completo con `main`, bloques especializados y footer sticky. + +Conclusión: + +- la diferencia create/update restante es funcional legítima: `create` todavía no edita el documento completo y `update` sí. + +## Riesgos de migración + +Riesgos bajos: + +- `customers/create` +- `customers/update` +- `/account` + +Riesgos medios: + +- `proformas/create` por convivencia con quick-create/select customer dialogs y porque el flujo actual parece de bootstrap inicial, no de edición documental completa; +- `company/details` y `verifactu` si se intentara moverlos fuera del shell settings. + +Riesgos altos: + +- `proformas/:id/edit`, aunque hoy ya cumple. Cualquier cambio allí impacta: + - scroll complejo + - footer sticky + - totales embebidos + - `aside` + - dialogs anidados + +## Plan incremental + +### Fase A + +- completada: migración de `proformas/create` + +Motivo: + +- era el único `PAGE_*` activo no plenamente alineado con el patrón Rdx; +- ya usaba `app-fullscreen`, así que el salto era incremental; +- permite distinguir mejor entre bootstrap inicial y editor documental completo del agregado Proforma. + +### Fase B + +- revisar si `company/details` y `verifactu` deben seguir como excepción de settings o si merece una variante focalizada específica para formularios largos dentro de settings + +Motivo: + +- ahora mismo la excepción parece razonable; +- no conviene forzar fullscreen sin una decisión explícita sobre el sistema de settings. + +### Fase C + +- aclarar `issued-invoices` create/edit + +Motivo: + +- primero hay que determinar si existe editor real o solo referencias muertas. + +### Fase D + +- hacer una auditoría UX más fina de dialogs ricos, empezando por `payment-terms` + +Motivo: + +- no es un incumplimiento de fullscreen; +- sí es el dialog más complejo de los auditados y puede requerir reglas específicas de consistencia interna. + +## No cambios + +Durante esta auditoría: + +- no se modificaron rutas ni layouts runtime de `customers`, `companies`, `customer-invoices`, `catalogs`, `identity`, `document-series` o `supplier`; +- no se tocaron hooks, controllers, formularios, React Query ni backend de los editores auditados; +- el único cambio de código del turno es este documento operativo de auditoría. diff --git a/modules/customer-invoices/src/web/proformas/create/ui/blocks/proforma-create-form.tsx b/modules/customer-invoices/src/web/proformas/create/ui/blocks/proforma-create-form.tsx index 5604b8fa..1c839047 100644 --- a/modules/customer-invoices/src/web/proformas/create/ui/blocks/proforma-create-form.tsx +++ b/modules/customer-invoices/src/web/proformas/create/ui/blocks/proforma-create-form.tsx @@ -1,6 +1,5 @@ import type { CustomerSelectionOption } from "@erp/customers"; import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers"; -import { cn } from "@repo/shadcn-ui/lib/utils"; import { ProformaCreateAutoAppliedInfo } from "./proforma-create-auto-applied-info"; import { ProformaCreateEmptyLinesCard } from "./proforma-create-empty-lines-card"; @@ -20,7 +19,6 @@ interface ProformaCreateFormProps { proformaSeriesOptions: { value: string; label: string }[]; targetInvoiceSeriesOptions: { value: string; label: string }[]; paymentTermOptions: { value: string; label: string }[]; - className?: string; } export const ProformaCreateForm = ({ @@ -37,31 +35,28 @@ export const ProformaCreateForm = ({ proformaSeriesOptions, targetInvoiceSeriesOptions, paymentTermOptions, - className, }: ProformaCreateFormProps) => { return ( -
-
-
- + +
+ - + - -
- -
+ +
+ ); }; diff --git a/modules/customer-invoices/src/web/proformas/create/ui/pages/proforma-create-page.tsx b/modules/customer-invoices/src/web/proformas/create/ui/pages/proforma-create-page.tsx index 60567ba8..7126e402 100644 --- a/modules/customer-invoices/src/web/proformas/create/ui/pages/proforma-create-page.tsx +++ b/modules/customer-invoices/src/web/proformas/create/ui/pages/proforma-create-page.tsx @@ -1,6 +1,7 @@ import { ErrorAlert } from "@erp/core/components"; import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks"; import { QuickCreateCustomerDialog, SelectCustomerDialog } from "@erp/customers"; +import { RdxEditorPageLayout } from "@repo/rdx-ui/components"; import { FormProvider } from "react-hook-form"; import { ProformaInfoAlert } from "../../../update/ui/components"; @@ -29,44 +30,50 @@ export const ProformaCreatePage = () => { - {createCtrl.isCreateError && ( - - )} +
+ +
+
+ {createCtrl.isCreateError && ( + + )} - {createCtrl.isSeriesLoadError && ( - - )} + {createCtrl.isSeriesLoadError && ( + + )} -
- + +
+
+
{ return ( - -
- {label} -
-
+
+ +
+
+ {label} +
+
+
+
); }; @@ -62,25 +62,27 @@ const AccountErrorState = ({ variant: React.ComponentProps["variant"]; }) => { return ( - -
- -
-
+
+ +
+ +
+
+
); }; @@ -200,6 +202,9 @@ export const AccountPage = () => { const { t } = useTranslation(); const ctrl = useAccountPageController(); const [isPasswordDialogOpen, setPasswordDialogOpen] = useState(false); + const { navigateBack } = useReturnToNavigation({ + fallbackPath: "/dashboard", + }); useEffect(() => { if (!ctrl.passwordSuccessVersion) { @@ -246,144 +251,150 @@ export const AccountPage = () => { }; return ( - -
- - -
-
-

{t("account.title")}

-

- {t("account.description")} -

-
+
+ - {ctrl.inlineProfileError ? ( - - ) : null} +
+ +
+
+ + +
+

+ {t("account.description")} +

-
- -
- - className="md:col-span-6" - disabled={ctrl.isSavingProfile} - inputClassName="bg-background" - label={t("account.sections.personal.fields.name.label")} - name="name" - placeholder={t("account.sections.personal.fields.name.placeholder")} - required - /> + {ctrl.inlineProfileError ? ( + + ) : null} - - className="md:col-span-6" - disabled={ctrl.isSavingProfile} - inputClassName="bg-background" - label={t("account.sections.personal.fields.email.label")} - name="email" - placeholder={t("account.sections.personal.fields.email.placeholder")} - required - typePreset="email" - /> + + +
+ + className="md:col-span-6" + disabled={ctrl.isSavingProfile} + inputClassName="bg-background" + label={t("account.sections.personal.fields.name.label")} + name="name" + placeholder={t("account.sections.personal.fields.name.placeholder")} + required + /> - - className="md:col-span-12" - description={t("account.sections.personal.fields.languageCode.description")} - disabled={ctrl.isSavingProfile} - inputClassName="bg-background" - items={[ - { - value: "es", - label: t("account.sections.personal.fields.languageCode.options.es"), - }, - { - value: "en", - label: t("account.sections.personal.fields.languageCode.options.en"), - }, - ]} - label={t("account.sections.personal.fields.languageCode.label")} - name="languageCode" - placeholder={t("account.sections.personal.fields.languageCode.placeholder")} + + className="md:col-span-6" + disabled={ctrl.isSavingProfile} + inputClassName="bg-background" + label={t("account.sections.personal.fields.email.label")} + name="email" + placeholder={t("account.sections.personal.fields.email.placeholder")} + required + typePreset="email" + /> + + + className="md:col-span-12" + description={t("account.sections.personal.fields.languageCode.description")} + disabled={ctrl.isSavingProfile} + inputClassName="bg-background" + items={[ + { + value: "es", + label: t("account.sections.personal.fields.languageCode.options.es"), + }, + { + value: "en", + label: t("account.sections.personal.fields.languageCode.options.en"), + }, + ]} + label={t("account.sections.personal.fields.languageCode.label")} + name="languageCode" + placeholder={t("account.sections.personal.fields.languageCode.placeholder")} + /> +
+
+ + +

+ {ctrl.isSavingProfile + ? t("account.feedback.savingProfile") + : t("account.feedback.idle")} +

+ + + } + secondary={ + + } />
-
- + + -

- {ctrl.isSavingProfile - ? t("account.feedback.savingProfile") - : t("account.feedback.idle")} -

+ +
+
+

{t("account.sections.security.passwordTitle")}

+

+ {t("account.sections.security.passwordDescription")} +

+
- - } - secondary={ - - } - /> + +
+
+ + +
+

+ {t("account.sections.session.description")} +

+ +
+
-
-
- - -
-
-

{t("account.sections.security.passwordTitle")}

-

- {t("account.sections.security.passwordDescription")} -

-
- - -
-
- - -
-

- {t("account.sections.session.description")} -

- -
-
+
+
{ onOpenChange={handlePasswordDialogOpenChange} t={t} /> - +
); }; diff --git a/packages/rdx-ui/docs/contributing-ui.md b/packages/rdx-ui/docs/contributing-ui.md index 78814ab4..4ddca758 100644 --- a/packages/rdx-ui/docs/contributing-ui.md +++ b/packages/rdx-ui/docs/contributing-ui.md @@ -64,6 +64,8 @@ Deben recibir props ya preparadas por la capa consumidora. ## Editor simple +- por defecto, las pantallas cuya finalidad principal sea crear, editar o configurar mediante formulario deben usar shell `app-fullscreen` y no el shell con sidebar; +- usar `PageFormHeader` o el header de dominio ya existente para la navegación mínima de salida, sin duplicar una segunda navegación lateral; - `RdxEditorSection` con `variant="plain"` es el patrón por defecto del editor simple; - usar una única superficie editable cuando sea útil separar claramente la página del área de formulario; - no envolver cada sección en `Card` por defecto; diff --git a/packages/rdx-ui/docs/page-layouts.md b/packages/rdx-ui/docs/page-layouts.md index 04eab419..dd13ffcc 100644 --- a/packages/rdx-ui/docs/page-layouts.md +++ b/packages/rdx-ui/docs/page-layouts.md @@ -101,6 +101,8 @@ scroll container Reglas: +- las rutas cuya tarea principal sea crear, editar o configurar mediante un formulario focalizado deben entrar por el shell `app-fullscreen`; +- listados, dashboards, detalle y navegación de settings mantienen `app-sidebar` salvo necesidad funcional explícita; - `RdxEditorPageLayout` resuelve padding interior, ancho centrado y `min-w-0`; - el editor simple puede apoyarse en una única superficie contenedora para diferenciar la página del área editable, sin volver a cards por sección; - `RdxEditorSection` expresa jerarquía por defecto con spacing y divisor inferior; @@ -109,22 +111,25 @@ Reglas: - usar secciones planas con espacio o divisores; - reservar sticky footer solo cuando el scroll lo justifique. -Ejemplo validado en `customers/update` y `customers/create`: +Ejemplo validado en `customers/update`, `customers/create` y `/account`: ```tsx -
- -
- {notice} + + +
+ +
+ {notice} -
- - {fields} - -
-
-
-
+
+ + {fields} + +
+
+
+
+ ``` Deuda registrada: @@ -142,8 +147,16 @@ ESPECÍFICO defaults iniciales wiring de submit feedback de error + navegación de retorno concreta ``` +Candidatos explícitos para auditoría posterior: + +- `customers/create` +- `customers/update` +- `/account` +- `proformas/update` + ## Editor complejo ```txt