FactuGES_ERP/docs/frontend/diseno-cuadros-dialogo.md
david 00777b3ba2 feat: add create recurring proforma dialog and related functionality
- Implemented CreateRecurringProformaFromProformaDialog component for creating recurring proformas from existing proformas.
- Added unit tests for the new dialog and its integration within the proforma detail page.
- Created API functions for handling the creation of recurring proformas.
- Developed hooks to manage the creation process and invalidate relevant queries upon success.
- Enhanced recurring proforma detail page to display relevant information and actions.
- Added utility functions for formatting recurring proforma schedules.
- Introduced layout components for better organization of recurring proforma UI.
2026-09-01 11:36:20 +02:00

8.6 KiB

Diseño de cuadros de diálogo de formulario

Estado

Este documento es la fuente de verdad para diseñar, implementar y revisar cuadros de diálogo que recogen o modifican datos en el frontend del ERP.

Todo diálogo nuevo de creación, edición o configuración debe seguir esta guía, salvo que una necesidad funcional justifique y documente una excepción.

Referencia canónica

La referencia productiva es el diálogo para crear una proforma recurrente desde una proforma:

  • componente: modules/customer-invoices/src/web/proformas/detail/ui/components/create-recurring-proforma-from-proforma-dialog.tsx;
  • controller: modules/customer-invoices/src/web/proformas/detail/controllers/use-create-recurring-proforma-from-proforma-dialog-controller.ts;
  • prueba de renderizado: modules/customer-invoices/src/web/proformas/detail/ui/components/create-recurring-proforma-from-proforma-dialog.test.tsx.

La guía no obliga a reutilizar ese componente. Obliga a mantener su jerarquía visual, separación de responsabilidades y comportamiento accesible.

Cuándo aplicar este patrón

Usar Dialog para una tarea breve y focalizada que:

  • se inicia desde la pantalla actual;
  • requiere pocos datos para completar una única acción;
  • no necesita una ruta propia ni navegación lateral;
  • permite volver al contexto sin perder orientación.

No usarlo para:

  • confirmaciones o acciones destructivas sin formulario: usar AlertDialog;
  • ediciones documentales complejas, varios pasos o trabajo prolongado: usar una página de edición;
  • vistas de solo lectura: usar un diálogo de consulta o el patrón existente del área.

Estructura visual obligatoria

Un diálogo de formulario se organiza de arriba abajo en cuatro zonas:

  1. Cabecera diferenciada.
  2. Campo de identidad destacado cuando exista, normalmente el nombre.
  3. Bloques de información agrupados por intención.
  4. Pie de acciones separado del contenido.
<DialogContent className="gap-0 overflow-hidden p-0 sm:max-w-2xl">
  <DialogHeader className="border-b bg-muted/40 px-6 py-5 sm:px-7">
    <DialogTitle>{title}</DialogTitle>
    <DialogDescription className="max-w-lg">{description}</DialogDescription>
  </DialogHeader>

  <form className="space-y-6 px-6 py-5 sm:px-7">
    <IdentityField />
    <InformationSection />
    <ConfigurationSection />
  </form>

  <DialogFooter className="border-t bg-muted/30 px-6 py-4 sm:px-7">
    <CancelAction />
    <SubmitAction />
  </DialogFooter>
</DialogContent>

No añadir una zona si no aporta información o una acción real. La estructura debe mejorar la lectura, no convertir un formulario pequeño en una pantalla artificialmente compleja.

1. Cabecera diferenciada

La cabecera comunica qué se va a hacer antes de pedir datos.

  • Incluir título directo, orientado a la acción: Crear plazo de pago, Modificar método de pago.
  • Incluir una descripción breve que explique el resultado o el contexto de origen.
  • Separarla visualmente del formulario mediante borde inferior y fondo sutil del tema (bg-muted/40).
  • Mantener los márgenes internos alineados con contenido y pie.
  • No colocar acciones de formulario en la cabecera; el cierre estándar del diálogo se mantiene disponible.

2. Campo de identidad destacado

Cuando el recurso tenga un nombre, código o referencia que ayude a reconocerlo, ese campo va inmediatamente después de la cabecera.

  • Usar el nombre como primer campo si es obligatorio para crear o identificar el recurso.
  • Dar al control una presencia moderada (h-11 y fondo de formulario) sin crear un segundo título.
  • Mantener el error de validación junto al campo.
  • No destacar campos técnicos, identificadores internos ni valores que el usuario no pueda modificar.

Si el diálogo no tiene un campo de identidad relevante, el primer bloque de información pasa a ocupar esa posición.

3. Bloques de información

Agrupar campos que responden a la misma pregunta del usuario. Por ejemplo: periodo de vigencia, recurrencia, condiciones comerciales o destinatario.

Cada bloque debe:

  • usar un section con aria-labelledby;
  • tener un título visible y conciso;
  • mostrar un icono solo cuando ayude a identificar el tipo de información;
  • usar borde, radio y fondo sutil para delimitarlo sin competir con la acción principal;
  • contener entre uno y seis controles relacionados por norma general.

Patrón base:

<section aria-labelledby={sectionId} className="rounded-xl border bg-muted/20 p-4 sm:p-5">
  <div className="mb-4 flex items-center gap-3">
    <span className="flex size-8 items-center justify-center rounded-lg bg-primary/10 text-primary">
      <SectionIcon aria-hidden className="size-4" />
    </span>
    <h3 className="font-medium text-sm" id={sectionId}>{sectionTitle}</h3>
  </div>

  <div className="grid gap-4 sm:grid-cols-2">
    <Field />
    <Field />
  </div>
</section>

Reglas de composición:

  • En escritorio, los campos de igual importancia pueden compartir dos columnas.
  • En móvil, las columnas deben apilarse de forma natural. Usar sm:grid-cols-2, nunca una anchura fija.
  • Añadir min-w-0 a las columnas cuando puedan contener textos localizados, selectores o controles flexibles.
  • Los selectores e inputs ocupan todo el ancho de su columna.
  • Un campo condicional se muestra dentro del bloque al que pertenece y se separa con borde superior si necesita distinguirse del resto.
  • Los interruptores y checkboxes que afectan a otro campo deben vivir junto a ese campo, no en una fila independiente sin contexto.

4. Pie de acciones

El pie mantiene las decisiones finales fuera de los datos.

  • Separarlo mediante borde superior y fondo sutil (bg-muted/30).
  • Colocar Cancelar como acción secundaria y la acción de envío como botón principal.
  • Durante el envío, deshabilitar ambos botones y todos los controles editables.
  • El botón principal debe reflejar el estado de progreso con texto o icono accesible.
  • Asociar el botón de envío al form mediante su atributo form cuando el pie quede fuera del elemento form.

Accesibilidad, validación e i18n

  • Usar DialogTitle y DialogDescription siempre que el diálogo tenga contenido operativo.
  • Asociar cada Label a un control mediante htmlFor e id.
  • Los títulos de bloque usan section y aria-labelledby.
  • Mantener los errores junto al control que los genera; no depender únicamente de avisos globales.
  • Todo texto visible procede de i18n y debe existir en es y en.
  • Los iconos decorativos usan aria-hidden; nunca sustituyen una etiqueta textual.
  • No bloquear el cierre por teclado salvo mientras exista una operación de envío en curso.

Responsabilidades del código

El componente de UI compone el diálogo y recibe estado ya preparado. No debe contener navegación, peticiones HTTP ni reglas de negocio.

El controller del diálogo concentra:

  • valores iniciales y reinicio del formulario;
  • validación de formulario;
  • llamada al hook de mutación;
  • estado de envío;
  • cierre, notificaciones y navegación tras un resultado correcto.

Las operaciones remotas permanecen en hooks de TanStack Query. Los DTOs de petición siguen siendo la fuente de verdad del formulario y de la validación.

Pruebas mínimas

Cada diálogo nuevo o modificado debe cubrir, como mínimo:

  • título, descripción y campo de identidad cuando exista;
  • campos obligatorios y campos condicionales;
  • acciones de cancelar y enviar;
  • estado de envío;
  • traducciones reales o una prueba que compruebe que las claves usadas existen en el catálogo;
  • resultado correcto y error de la mutación en el controller.

No usar un diccionario de traducciones manual en el test si el objetivo es verificar que el catálogo productivo contiene las claves consumidas por el componente.

Lista de comprobación

Antes de cerrar un cambio, confirmar:

  • La tarea cabe realmente en un diálogo.
  • La cabecera explica la acción y está diferenciada del formulario.
  • El nombre o campo de identidad aparece antes de los bloques de configuración.
  • Cada bloque agrupa una única intención comprensible.
  • El layout se apila correctamente en móvil.
  • Los textos, estados disabled, errores y carga son accesibles.
  • Existen traducciones en es y en.
  • Las pruebas y la validación de formato se han ejecutado desde Linux.

Excepciones

Una excepción debe justificarse en la revisión del cambio indicando:

  • qué requisito funcional impide usar esta estructura;
  • qué patrón alternativo se emplea;
  • cómo se mantiene la accesibilidad y el comportamiento responsive;
  • qué pruebas cubren la variante.

La preferencia visual local, copiar un diálogo antiguo o ahorrar un bloque de composición no son motivos válidos para omitir esta guía.