# 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: ```text 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: ```ts type QuickCustomerCreateResult = CustomerSelectionOption; ``` Eso significa que, para el consumidor, no hay dos contratos distintos: ```text 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: ```text 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: ```text 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 ```text 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: ```text Company.defaultPaymentTermId ``` No debe haber diferencias entre: - create completo; - quick create; - API directa. Fuente funcional principal: - [Plazos de pago](../catalogs/payment-terms.md) ### 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: ```text 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: ```text 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: ```ts 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)`.