FactuGES_ERP/docs/customers/customer-selection-and-quick-create.md
David a4398e163f refactor: update country references to use CountryCode in supplier and company modules
- Changed references from Country to CountryCode in supplier input mappers, audit services, and snapshot builders.
- Updated tests to reflect the changes in country handling.
- Refactored postal address to utilize CountryCode instead of Country.
- Added new company address mappers and tests to accommodate the updated structure.
- Ensured all related schemas and DTOs are aligned with the new country code implementation.
2026-09-07 12:22:04 +02:00

13 KiB

Customer Selection y Quick Create

Objetivo

modules/customers/src/common/features es la superficie pública del módulo customers para colaboración entre módulos frontend del ERP.

Estas features permiten que un consumidor:

  • seleccione un cliente existente;
  • cree un cliente de alta rápida;
  • reciba siempre un contrato público homogéneo para aplicar el resultado en su propio formulario.

La dependencia deseada es:

consumer module
      │
      ▼
@erp/customers o @erp/customers/common
      │
      ▼
customers/common/features
      │
      ├── customer-selection
      └── quick-create-customer

Un consumidor no debe resolver estos flujos importando implementación interna desde:

  • modules/customers/src/web/create
  • modules/customers/src/web/update
  • modules/customers/src/web/shared

Esos directorios siguen siendo responsabilidad interna del módulo customers.

Features públicas

customer-selection

Resuelve el problema de buscar y seleccionar un cliente desde otro módulo, exponiendo un flujo reutilizable con diálogo de selección y un contrato público estable para devolver el cliente elegido.

API pública real exportada por modules/customers/src/common/features/customer-selection/index.ts:

  • useCustomerSelectionFlow
  • CustomerSelectionCriteria
  • CustomerSelectionOption
  • SelectCustomerDialog
  • buildCustomerSelectionOption

Responsabilidades principales:

  • búsqueda y paginación de clientes;
  • apertura y cierre del selector;
  • transformación del resultado de listado al contrato público CustomerSelectionOption;
  • coordinación con alta rápida cuando el consumidor usa el flow público;
  • entrega del cliente seleccionado al callback del consumidor.

No pertenece a esta feature:

  • construir CreateCustomerParams;
  • conocer el formulario completo de create/update de clientes;
  • invalidar cachés externas del consumidor;
  • decidir qué hace el consumidor con el cliente ya seleccionado.

quick-create-customer

Resuelve el problema de crear un cliente mínimo y embebible sin exponer al consumidor los detalles internos del CRUD de customers.

API pública real exportada por modules/customers/src/common/features/quick-create-customer/index.ts:

  • useQuickCreateCustomerFlow
  • QuickCustomerCreateResult
  • QuickCreateCustomerDialog

Responsabilidades principales:

  • formulario reducido de alta rápida;
  • validación del formulario;
  • apertura y cierre del diálogo;
  • creación mediante la mutation propia de customers;
  • adaptación del resultado de creación a CustomerSelectionOption;
  • gestión de toasts, errores y estado del diálogo;
  • aplicación de defaults internos y contexto opcional del consumidor.

No pertenece a esta feature:

  • que el consumidor construya CreateCustomerParams;
  • que el consumidor importe useCustomerCreateMutation;
  • que el consumidor gestione manualmente la caché de customers.

Contrato público: CustomerSelectionOption

CustomerSelectionOption representa la proyección pública que customers entrega a otros módulos cuando el resultado del flujo es “ya tienes un cliente seleccionable y aplicable”.

No es la entity completa del CRUD de customers. Es un shape orientado a integración y UI.

Campos reales actuales:

  • id
  • status
  • isCompany
  • name
  • tradeName
  • tin
  • street
  • street2
  • city
  • province
  • postalCode
  • countryCode
  • primaryEmail
  • primaryPhone
  • primaryMobile

Su objetivo es permitir que el consumidor:

  • persista el identificador funcional (customerId);
  • muestre un resumen útil del cliente;
  • trate de la misma forma un cliente seleccionado y uno recién creado.

Convergencia entre selección y creación

La convergencia es intencional.

modules/customers/src/common/features/quick-create-customer/entities/quick-customer-create-result.entity.ts define:

type QuickCustomerCreateResult = CustomerSelectionOption;

Eso significa que, para el consumidor, no hay dos contratos distintos:

seleccionar cliente existente ───────┐
                                     ├──► CustomerSelectionOption
crear cliente rápidamente ───────────┘

Una vez terminada la operación, el consumidor puede usar el mismo callback para ambos casos. En el flow público actual, ese callback es onCustomerSelected(customer).

useCustomerSelectionFlow

useCustomerSelectionFlow es el punto de composición pública recomendado cuando una pantalla necesita:

  • seleccionar un cliente existente;
  • abrir alta rápida desde el selector;
  • abrir alta rápida directamente desde la pantalla;
  • recibir siempre un CustomerSelectionOption.

Parámetros públicos actuales:

  • onCustomerSelected(customer: CustomerSelectionOption)
  • defaultLanguageCode?
  • defaultCurrencyCode?

Estado y artefactos que coordina:

  • selectCtrl para el selector;
  • createCtrl para el diálogo de alta rápida;
  • SelectCustomerDialog;
  • QuickCreateCustomerDialog;
  • entrypoints de apertura para creación rápida en distintos contextos.

El consumidor no debería reproducir manualmente esta coordinación si ya consume este flow.

Dos entrypoints de creación rápida

La implementación actual diferencia dos contextos y su semántica importa.

openQuickCreateDialog

Uso típico:

  • botón Nuevo cliente de una pantalla consumidora.

Comportamiento:

abre creación rápida directamente
→ cancelar
→ simplemente se cierra el diálogo

No intenta reabrir el selector porque no presupone que el usuario viniera de ahí.

openQuickCreateFromSelection

Uso típico:

  • botón Nuevo cliente dentro de SelectCustomerDialog.

Comportamiento:

cierra selector
→ abre creación rápida
→ cancelar
→ vuelve a abrir selector

Esta diferencia evita que el consumidor tenga que gestionar manualmente el retorno al selector.

Diagrama del flujo

useCustomerSelectionFlow
│
├── selectCtrl.openDialog()
│      │
│      ▼
│   SelectCustomerDialog
│      │
│      ├── confirmar selección
│      │      │
│      │      ▼
│      │   onCustomerSelected(CustomerSelectionOption)
│      │
│      └── Nuevo cliente
│             │
│             ▼
│   openQuickCreateFromSelection()
│             │
│             ▼
│   QuickCreateCustomerDialog
│             │
│             ├── cancelar
│             │      │
│             │      ▼
│             │   reabrir selector
│             │
│             └── crear
│                    │
│                    ▼
│          onCustomerSelected(CustomerSelectionOption)
│
└── openQuickCreateDialog()
       │
       ▼
    QuickCreateCustomerDialog
       │
       ├── cancelar
       │      │
       │      ▼
       │   cerrar diálogo
       │
       └── crear
              │
              ▼
    onCustomerSelected(CustomerSelectionOption)

Detalle de quick-create-customer

Default de plazo de pago

La alta rápida no necesita pedir el plazo de pago al usuario.

Si no envía un valor explícito, el backend aplica la misma regla que en el alta completa:

Company.defaultPaymentTermId

No debe haber diferencias entre:

  • create completo;
  • quick create;
  • API directa.

Fuente funcional principal:

Formulario reducido

La alta rápida no intenta sustituir al CRUD completo de customers.

El schema público de formulario rápido actual (QuickCustomerCreateFormSchema) expone solo:

  • isCompany
  • name
  • tin
  • primaryEmail

Defaults internos y contexto opcional

La feature reutiliza buildCustomerCreateDefault() del área interna de create y resuelve por defecto:

  • countryCode = ES
  • languageCode = es
  • currencyCode = EUR

Además, useQuickCreateCustomerFlow y useQuickCreateCustomerDialogController admiten contexto opcional:

  • defaultCountryCode?
  • defaultLanguageCode?
  • defaultCurrencyCode?

Regla conceptual:

consumidor
    → aporta contexto opcional

customers
    → sigue construyendo, validando y ejecutando la creación

El consumidor no debe construir CreateCustomerParams.

Validación, errores y toasts

La responsabilidad de validación y feedback está dentro de quick-create-customer:

  • QuickCustomerCreateFormSchema valida el formulario;
  • useQuickCreateCustomerDialogController aplica errores de validación al form;
  • la feature muestra showSuccessToast, showWarningToast y showErrorToast;
  • el diálogo mantiene submitError y el estado isSubmitting.

Por tanto, un consumidor normal no debería duplicar:

  • try/catch del submit;
  • toasts de éxito/error de creación;
  • setError sobre el formulario rápido.

Caché

La creación usa useCustomerCreateMutation() desde el módulo customers.

La sincronización de React Query y su estrategia de caché siguen siendo responsabilidad de customers. El consumidor no debe invalidar manualmente queries de customers tras una creación rápida salvo un caso excepcional y explícitamente justificado.

Ejemplo real: proformas/update

Caso real actual:

  • modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-page-controller.ts
  • modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts
  • modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx

Integración conceptual actual:

useCustomerSelectionFlow(...)
        │
        ├── seleccionar cliente existente
        └── crear cliente rápido
                 │
                 ▼
      onCustomerSelected(customer)
                 │
                 ▼
      updateCtrl.setCustomer(customer)
                 │
                 ▼
      form.setValue("customerId", customer.id)

En proformas/update:

  • el page controller compone useCustomerSelectionFlow;
  • la pantalla usa selectCustomerCtrl.openQuickCreateDialog para el botón principal Nuevo cliente;
  • SelectCustomerDialog usa selectCustomerCtrl.openQuickCreateFromSelection para Nuevo cliente dentro del selector;
  • ambos caminos convergen en updateCtrl.setCustomer(customer).

Rehidratación y snapshots externos

CustomerSelectionOption es el contrato ideal que customers entrega a consumidores.

Eso no implica que cualquier otro recurso del ERP tenga que persistir exactamente el mismo shape. Por ejemplo, una proforma puede contener un snapshot suficiente para representar a su cliente, pero no necesariamente todos los campos disponibles en CustomerSelectionOption.

La integración correcta consiste en:

  • usar CustomerSelectionOption cuando el dato proviene del flujo público de customers;
  • permitir que recursos externos reconstruyan su propio resumen cuando trabajan con snapshots parciales.

Qué debe importar un consumidor

Imports recomendados:

import { useCustomerSelectionFlow } from "@erp/customers/common";
import type { CustomerSelectionOption } from "@erp/customers";
import { QuickCreateCustomerDialog, SelectCustomerDialog } from "@erp/customers";

La ruta concreta puede variar según el barrel elegido, pero debe salir de la superficie pública exportada por @erp/customers o @erp/customers/common.

Qué no debe importar un consumidor

No debe depender directamente de piezas internas del CRUD como:

  • CreateCustomerParams
  • CustomerCreateForm
  • useCustomerCreateMutation
  • buildCreateCustomerParams
  • componentes o hooks desde modules/customers/src/web/create/*
  • detalles internos de modules/customers/src/web/shared/*

Respuestas rápidas de contrato

  1. customer-selection resuelve búsqueda, selección y entrega homogénea de cliente.
  2. quick-create-customer resuelve alta mínima embebible sin exponer el CRUD interno.
  3. La API pública real está definida por los barrels de cada feature, no por sus internals.
  4. El consumidor debe importar desde @erp/customers o @erp/customers/common.
  5. No debe importar internals de web/create, web/update o web/shared.
  6. CustomerSelectionOption es la proyección pública reutilizable de cliente seleccionado.
  7. QuickCustomerCreateResult converge en el mismo contrato para no bifurcar la integración.
  8. openQuickCreateDialog es apertura directa; openQuickCreateFromSelection preserva el retorno al selector.
  9. La cancelación y la reapertura del selector las gestiona el flow de customers.
  10. La creación, validación, errores y caché pertenecen a customers.
  11. El consumidor integra el resultado aplicando CustomerSelectionOption a su propio caso de uso.
  12. proformas/update lo hace actualmente con updateCtrl.setCustomer(customer).