diff --git a/docs/README.md b/docs/README.md index d04573e5..43a2ff6a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,6 +46,7 @@ Este directorio reúne la documentación técnica, operativa y de apoyo del ERP. - [README de `modules/identity`](../modules/identity/README.md) - [README de `modules/companies`](../modules/companies/README.md) - [README de `modules/auth`](../modules/auth/README.md) +- [API pública de selección y alta rápida de clientes](./customers/customer-selection-and-quick-create.md) #### Guías y estándares diff --git a/docs/customers/customer-selection-and-quick-create.md b/docs/customers/customer-selection-and-quick-create.md new file mode 100644 index 00000000..c311c53d --- /dev/null +++ b/docs/customers/customer-selection-and-quick-create.md @@ -0,0 +1,392 @@ +# 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` +- `country` +- `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` + +### 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: + +- `country = ES` +- `languageCode = es` +- `currencyCode = EUR` + +Además, `useQuickCreateCustomerFlow` y `useQuickCreateCustomerDialogController` admiten contexto opcional: + +- `defaultCountry?` +- `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)`. diff --git a/modules/customers/src/common/features/customer-selection/controllers/use-customer-selection-flow.ts b/modules/customers/src/common/features/customer-selection/controllers/use-customer-selection-flow.ts index 9d7f343b..ec0b917d 100644 --- a/modules/customers/src/common/features/customer-selection/controllers/use-customer-selection-flow.ts +++ b/modules/customers/src/common/features/customer-selection/controllers/use-customer-selection-flow.ts @@ -24,11 +24,19 @@ export const useCustomerSelectionFlow = (options: { defaultCurrencyCode: options.defaultCurrencyCode, }); + /** + * Se usa cuando la creación rápida nace dentro del selector. + * Si el usuario cancela, `onCancel` reabre el selector. + */ const openQuickCreateFromSelection = () => { selectCtrl.closeDialog(); quickCreateFlow.createCtrl.openDialog(); }; + /** + * Se usa cuando el usuario inicia la creación rápida desde una pantalla consumidora. + * Si cancela, el diálogo simplemente se cierra. + */ const openQuickCreateDialog = () => { quickCreateFlow.createCtrl.openDialog(); }; diff --git a/modules/customers/src/common/features/quick-create-customer/README.md b/modules/customers/src/common/features/quick-create-customer/README.md index 3131d093..cfc70c3b 100644 --- a/modules/customers/src/common/features/quick-create-customer/README.md +++ b/modules/customers/src/common/features/quick-create-customer/README.md @@ -2,15 +2,34 @@ Feature pública reutilizable del módulo `customers` para alta rápida embebida. +La documentación de diseño e integración entre módulos está consolidada en: + +- [docs/customers/customer-selection-and-quick-create.md](../../../../../../docs/customers/customer-selection-and-quick-create.md) + ## API pública ```ts import { QuickCreateCustomerDialog, + QuickCustomerCreateResult, useQuickCreateCustomerFlow, } from "@erp/customers/common"; ``` +## Responsabilidad + +- exponer un diálogo reutilizable de alta rápida; +- validar el formulario mínimo; +- crear el cliente usando la mutation propia de `customers`; +- devolver el resultado como `CustomerSelectionOption`. + +El consumidor no debería importar detalles internos como: + +- `CreateCustomerParams` +- `CustomerCreateForm` +- `useCustomerCreateMutation` +- `buildCreateCustomerParams` + ## Ejemplo mínimo ```tsx @@ -29,3 +48,24 @@ const quickCreate = useQuickCreateCustomerFlow({ - Éxito: devuelve un `CustomerSelectionOption`, listo para reutilizarse en flujos de selección. - Cancelación: no crea nada, cierra el diálogo y dispara `onCancel` si el consumidor lo ha proporcionado. - Integración con `customer-selection`: puede abrirse desde el selector y devolver el cliente para dejarlo seleccionado automáticamente. + +## Defaults y contexto + +El formulario rápido actual pide solo: + +- `isCompany` +- `name` +- `tin` +- `primaryEmail` + +Los defaults base de create se resuelven dentro de `customers`. Actualmente la feature conserva: + +- `country = ES` +- `languageCode = es` +- `currencyCode = EUR` + +Opcionalmente, el consumidor puede aportar contexto mediante: + +- `defaultCountry` +- `defaultLanguageCode` +- `defaultCurrencyCode`