diff --git a/docs/componentes-de-interfaz.md b/docs/componentes-de-interfaz.md
index 6f2a04f..5a1cd92 100644
--- a/docs/componentes-de-interfaz.md
+++ b/docs/componentes-de-interfaz.md
@@ -332,7 +332,16 @@ TemplateSectionsEditor recibe secciones, referencias de campos, nombres, textos
`FormDialog` admite `size="small" | "medium" | "large"`. Los formularios sencillos mantienen el tamaño pequeño; personas y usuarios emplean el intermedio. `wide` sigue siendo un alias del tamaño grande: 90 % del ancho disponible, con máximo de 1.200 px y márgenes en móvil. No se ensanchan automáticamente las altas sencillas.
-La cabecera permanece visible. Un formulario modal utiliza una raíz flex con altura mínima cero, `FormBody` para campos, errores y ayuda, y `FormActions` como hermano final fuera del cuerpo desplazable. Los botones permanecen dentro del formulario HTML original: se conservan envío por teclado, validación nativa, estados pendientes y cancelación. La ayuda queda al final del cuerpo, inmediatamente antes de las acciones. Los diálogos de consulta conservan el cuerpo desplazable y su pie externo.
+`FormDialog layout="stable"` reserva la altura exterior para formularios y asistentes
+cuyo contenido cambia durante la interacción. Comparte con `layout="picker"` el tamaño
+adaptado a la ventana: altura dinámica menos 2 rem en móvil y mínimo de 85dvh/900px desde
+`sm`, limitado siempre por los márgenes. Conserva el espaciado normal del formulario,
+con `FormBody` desplazable y `scrollbar-gutter: stable`; no distribuye los hijos como una
+tabla. Cambiar de paso, añadir o retirar activos y desplegar ayuda no redimensiona el
+modal. La cabecera y las acciones permanecen visibles. `layout="content"` queda para
+contenido breve que no cambia de tamaño. Véase la [regla normativa de estabilidad](patron-de-dialogos.md#estabilidad-de-altura-del-modal).
+
+La cabecera permanece visible. Un formulario modal utiliza una raíz flex con altura mínima cero, `FormBody` para campos y errores, y `FormActions` como hermano final fuera del cuerpo desplazable. Los botones permanecen dentro del formulario HTML original: se conservan envío por teclado, validación nativa, estados pendientes y cancelación. La ayuda queda inmediatamente antes de las acciones; en diálogos de altura estable ocupa un bloque anclado abajo fuera del scroll de campos, con altura limitada y desplazamiento propio si se despliega contenido extenso. Los diálogos de consulta conservan el cuerpo desplazable y su pie externo.
`FormSection` agrupa datos relacionados mediante título accesible; la variante `card` añade un borde discreto y `plain` conserva el bloque sin marco. Ambas utilizan `FormSectionTitle`. Activos, recursos y plantillas lo utilizan para sus datos principales. Las características se distribuyen en dos bloques por fila cuando el contenedor dispone de al menos 900 px; los controles dentro de cada bloque mantienen una o dos columnas según el espacio. Una plantilla sin secciones puede aprovechar hasta tres columnas de características. Observaciones y ayuda ocupan toda la fila. La disposición responde al espacio del modal, no solo al ancho del navegador.
@@ -474,15 +483,20 @@ No conoce entidades, formularios, consultas ni endpoints.
`FormProgress` de `@repo/rdx-ui/components/form-progress` presenta pasos numerados, marca el actual con `aria-current="step"`, identifica los completados y lleva el foco al anuncio traducido de progreso. Recibe etiquetas, descripción y textos accesibles del consumidor; no conoce entidades ni consultas. Lo usan los asistentes de recursos, activos y puestos.
El diálogo de alta de puesto usa tamaño medio y cabecera con icono decorativo; la edición
-mantiene tamaño grande. Asociaciones, activos, identificación y resumen utilizan
+mantiene tamaño grande. Ambos utilizan `layout="stable"`. Asociaciones, activos, identificación y resumen utilizan
`FormSection variant="plain"`, títulos de grupo uniformes y separación de 20 px. Las
asociaciones y el resumen pasan a dos columnas según el ancho del contenedor; el nombre
-del alta ocupa todo el ancho. Los activos seleccionados usan filas compactas con separadores,
-nombres que pueden partirse y botones de retirada con etiqueta accesible. El resumen
+del alta ocupa todo el ancho. Los activos seleccionados utilizan `AssetSummaryCard`, con nombres que pueden partirse
+y botones de retirada con etiqueta accesible. El resumen
muestra `—` para valores ausentes. La ayuda del ordenador se vincula al selector mediante
`aria-describedby`; el nombre identifica su error junto al campo. La ayuda general queda
al final, antes del pie fijo, con navegación por flechas y cancelación deshabilitada durante
-el guardado. Se conservan el cuerpo desplazable y la altura según contenido.
+el guardado. El alta utiliza `layout="stable"`: el cuerpo se desplaza dentro de una altura
+exterior estable, independiente del paso o del número de activos seleccionados.
+La ayuda contextual del puesto es hermana de `FormBody`, inmediatamente antes de las
+acciones: permanece alineada abajo aunque el paso tenga pocos campos. Su contenedor
+elimina el margen superior de la variante plain, limita su altura al 40 % del formulario
+y permite scroll propio para ejemplos extensos, manteniendo visible el footer.
El nuevo puesto se organiza en sede/responsable, activos, ordenador principal y resumen. Los controles de asociaciones, composición, ordenador y resumen tienen responsabilidades propias y se reutilizan en la edición cuando corresponde. Las acciones Anterior/Siguiente conservan valores; Guardar aparece en el resumen. La sede única y el activo único tienen selección automática. Véase [Puestos de trabajo](puestos-de-trabajo.md#alta-en-cuatro-pasos).
@@ -570,7 +584,7 @@ En todos los diálogos de tipo `PickerList`, la columna del checkbox o radio ocu
### Altura estable de los diálogos PickerList
-Todos los diálogos `PickerList` usan `FormDialog layout="picker"`. Esta variante fija la altura a la ventana disponible en móvil, con un margen total de 2 rem; en escritorio utiliza el menor valor entre el 85 % de la altura dinámica de la ventana y 900 px, respetando siempre el margen. La altura no depende de carga, errores, búsqueda, número de resultados o tamaño de página. Los formularios de alta y edición conservan la altura por contenido, también cuando se abren desde un picker.
+Todos los diálogos `PickerList` usan `FormDialog layout="picker"`. Esta variante fija la altura a la ventana disponible en móvil, con un margen total de 2 rem; en escritorio utiliza el menor valor entre el 85 % de la altura dinámica de la ventana y 900 px, respetando siempre el margen. La altura no depende de carga, errores, búsqueda, número de resultados o tamaño de página. Los formularios de alta y edición con contenido variable deben utilizar `layout="stable"`, también cuando se abren desde un picker; las confirmaciones breves pueden conservar `layout="content"`.
`FormBody` hereda esta disposición del diálogo y distribuye sus hijos en columna. `EntityTable layout="fill"` ocupa el espacio restante; búsqueda, filtros, información y paginación no se desplazan con los resultados. El contenedor de tabla tiene scroll interno y `scrollbar-gutter: stable`; las cabeceras permanecen visibles al recorrer las filas. El área mantiene su espacio con pocos resultados o ninguno. En ventanas pequeñas, el cuerpo también puede desplazarse para evitar recortar controles. `AssetList` propaga `layout="fill"` por las pestañas y `AssetPickerList` conserva ese comportamiento en selección única y múltiple. `AssetPickerDialog` y `CatalogFieldPickerDialog` aplican esta variante común.
@@ -657,3 +671,46 @@ reutiliza `SwitchField`. `SiteDeleteDialog` compone `ConfirmDeleteDialog`, prese
nombre de la sede y conserva el error de negocio dentro del modal. La caché de detalle
de compañía se actualiza tras completar cada operación. Los selectores conservan la
sede anterior archivada y excluyen las demás archivadas. Véase [Sedes](sedes.md).
+
+## Fichas compactas de activos elegidos
+
+`AssetSummaryCard`, en `assets/shared/ui`, presenta nombre, icono y nombre de plantilla,
+resumen de características y estado. Reutiliza `Card` de shadcn/ui, `AssetTemplateIcon`,
+`assetCharacteristicsSummary` y `EntityStatus`. La especificación capturada determina el
+nombre de plantilla y el resumen; el catálogo aporta únicamente el icono actual, con
+icono de caja como alternativa. Los valores ausentes muestran `—`; cero y booleanos se
+conservan con las traducciones habituales.
+
+El consumidor aporta el activo, la plantilla opcional, acciones mediante `actions` y un
+aviso mediante `error`. La ficha no carga datos ni decide cómo seleccionar o retirar activos.
+Permanece en el módulo porque conoce sus DTO; las primitivas visuales siguen siendo
+independientes del dominio. La usan la selección de activos del puesto y su resumen final.
+Las tarjetas se distribuyen en una columna o dos desde 560 px de contenedor, igualan altura
+por fila y mantienen el orden del DOM. En selección, un contador anuncia los cambios y
+la retirada conserva el nombre accesible del activo. Los errores se presentan junto a
+la ficha y se distinguen también mediante texto.
+
+## Elección visual del ordenador principal
+
+`WorkstationComputerField` reutiliza `AssetSummaryCard` para los activos ya incorporados
+al puesto. Las fichas se componen con `FieldLabel`, `RadioGroup` y `RadioGroupItem` de
+shadcn/ui/Base UI. Pulsar cualquier zona de la etiqueta selecciona su radio, sin handlers
+de click duplicados ni otro modal. El grupo tiene leyenda, ayuda vinculada, nombres
+accesibles traducidos y selección única; la primitiva gestiona foco, flechas y Espacio.
+El borde y el radio marcado distinguen la selección sin depender solo del color.
+La cuadrícula conserva una o dos columnas según el contenedor y la altura estable del
+modal. No carga datos: recibe activos y catálogo del formulario. El activo único sigue
+seleccionándose automáticamente y el guardado bloquea los controles. En edición se
+conserva la acción explícita de quitar la selección cuando no es obligatoria.
+
+## Foco inicial de los selectores PickerList
+
+`EntityTable` identifica su input de búsqueda rápida mediante `data-quick-search`.
+Al abrir un `FormDialog layout="picker"`, `initialFocus` de Base UI apunta a ese input
+buscándolo dentro del popup, sin afectar a listados de página ni otros modales. La
+búsqueda permanece montada durante la carga: `AssetPickerDialog` presenta la lista con
+`loading`/`unavailable`, propagados por `AssetList` hasta `EntityTable`. Así se puede
+escribir desde la apertura, sin anunciar falsos vacíos. El foco no se reaplica al filtrar
+o recargar datos, y la primitiva conserva la restauración al cerrar diálogos anidados.
+La [regla normativa](patron-de-dialogos.md#foco-inicial-en-la-búsqueda-rápida) se aplica
+a todos los PickerList actuales y futuros, incluidos los de campos de ambos catálogos.
diff --git a/docs/patron-de-dialogos.md b/docs/patron-de-dialogos.md
index 0a2dd2d..5261fac 100644
--- a/docs/patron-de-dialogos.md
+++ b/docs/patron-de-dialogos.md
@@ -57,10 +57,39 @@ el [inventario de modales](inventario-de-modales.md) recoge las implementaciones
el teclado y el arrastre. No introducir un orden visual diferente mediante CSS.
- Cada grupo de campos responde a su propio ancho, también cuando su sección ocupa media
fila. El bloque «Sin sección» permanece debajo de las secciones, a ancho completo.
-- No convertir formularios sencillos en cuadrículas de secciones ni imponerles altura fija.
+- No convertir formularios sencillos en cuadrículas de secciones ni imponer alturas
+ absolutas que superen la ventana. La estabilidad de la altura exterior se rige por la
+ regla siguiente; las secciones interiores conservan altura según su contenido.
Los umbrales anteriores corresponden a los componentes citados; otros contenidos deben
elegir su disposición según el espacio que necesitan sus controles.
+## Estabilidad de altura del modal
+
+Esta regla se aplica a los cuadros de diálogo actuales y futuros. Al crear o modificar
+un diálogo, comprobar si su contenido puede variar mientras permanece abierto: pasos,
+selecciones, altas anidadas, filtros, carga, errores, ayuda plegable o grupos condicionales.
+En esos casos, la altura exterior debe mantenerse estable durante toda la interacción.
+No debe crecer, encogerse ni desplazar el pie por cada elección del usuario.
+
+- Utilizar `FormDialog layout="stable"` para formularios y asistentes con contenido
+ variable, como el alta de puestos. Reservar la altura desde la apertura, también cuando
+ el primer paso sea corto o los datos todavía estén cargando.
+- La altura se adapta a la ventana: en móvil ocupa la altura dinámica disponible menos
+ 2 rem de margen; desde `sm`, el menor valor entre el 85 % de la altura dinámica y 900 px,
+ respetando siempre el margen. Puede reajustarse al cambiar la ventana, orientación o
+ espacio disponible del teclado; no depende del contenido ni se mide con JavaScript.
+- Conservar cabecera y pie visibles, `min-h-0` en la cadena flex, `FormBody` desplazable
+ y espacio reservado para la barra vertical. El contenido puede cambiar de alto dentro
+ del cuerpo, sin recortar campos, errores o acciones ni reducirlos para hacerlos caber.
+- Mantener `layout="picker"` para selectores con tabla que ocupa el espacio restante;
+ ya proporciona altura estable y su distribución específica. No usarlo para fijar la
+ altura de formularios que no sean selectores.
+- `layout="content"` queda para confirmaciones y diálogos breves cuyo contenido no cambia
+ de tamaño. Si incorporan contenido variable, aplicar la variante estable al modificarlos.
+- Revisar pasos cortos y largos, listas vacías y extensas, carga/error y altas anidadas,
+ tanto en móvil como en escritorio. Deben conservarse la altura exterior y la posición
+ de las acciones, con acceso mediante scroll y teclado a todo el contenido.
+
## Secciones plegables y filas compactas
- Las secciones extensas o repetidas utilizan la primitiva `Collapsible` mediante
@@ -111,6 +140,27 @@ el [inventario de modales](inventario-de-modales.md) recoge las implementaciones
su radio. Reutilizar `onRowClick` de la tabla; los controles internos ejecutan solo su
propia acción, sin duplicar cambios. Respetar filas deshabilitadas y navegación por teclado.
+### Foco inicial en la búsqueda rápida
+
+Regla para todos los diálogos `PickerList` actuales y futuros: al abrirlos, el foco
+inicial debe situarse en el cuadro de búsqueda rápida, listo para escribir. No debe
+quedar en las pestañas, filtros, controles de selección o botones del pie.
+
+- Reutilizar `FormDialog layout="picker"` y la búsqueda de `EntityTable`, que se identifica
+ con `data-quick-search`. `FormDialog` establece el foco mediante `initialFocus` de Base UI,
+ limitado al input del propio modal. Los selectores con búsqueda propia deben mantener
+ el mismo atributo en su input y la búsqueda debe estar montada y habilitada al abrir.
+- Mantener la búsqueda visible y disponible durante carga, errores y resultados vacíos.
+ No esperar a los datos para montarla ni mostrar un mensaje de vacío mientras se carga.
+- Aplicar el foco al abrir, también al reabrir; no volver a enfocarlo por cada tecla,
+ filtrado, cambio de pestaña, paginación o actualización de datos. No utilizar temporizadores
+ ni efectos que roben el foco durante la interacción.
+- Mantener el aislamiento de los diálogos anidados y la restauración de foco de Base UI:
+ cerrar un alta relacionada devuelve el foco a su disparador dentro del picker;
+ cerrar el picker devuelve el foco al control que lo abrió.
+- Verificar apertura con ratón y teclado, reapertura, carga inicial y altas anidadas.
+ El foco visible y el uso de Tab, flechas y Escape deben seguir funcionando.
+
## Cuerpo, pie y accesibilidad
- Elegir la anchura del diálogo según su contenido y respetar los márgenes de la ventana.
@@ -120,9 +170,15 @@ el [inventario de modales](inventario-de-modales.md) recoge las implementaciones
`FormStepActions`, validar antes de avanzar y conservar el borrador al retroceder.
Guardar se muestra al final; ayuda, cancelación y errores permanecen dentro del diálogo.
El alta usa tamaño medio y la edición completa conserva tamaño grande.
-- Los formularios conservan altura según contenido; la altura estable por resultados es
- la regla de los `PickerList`. No introducir pestañas ni pasos para resolver solo espacio.
-- Situar ayuda contextual general al final del cuerpo, inmediatamente antes de `FormActions`.
+- Los formularios con contenido variable mantienen altura exterior estable según la
+ regla de estabilidad; sus grupos interiores conservan altura según contenido.
+ No introducir pestañas ni pasos para resolver solo espacio.
+- Situar la ayuda contextual general inmediatamente antes de `FormActions`. En los
+ diálogos de altura estable debe quedar anclada abajo, fuera del scroll principal:
+ campos, progreso y errores se desplazan en `FormBody`, mientras la ayuda permanece
+ pegada al pie incluso en pasos cortos. Eliminar márgenes superiores innecesarios y
+ conservar el padding normal junto al footer. Limitar su altura y permitir scroll propio al
+ desplegar ejemplos, para no ocultar los campos ni las acciones en ventanas pequeñas.
Mantener errores junto al contexto que los origina y las acciones dentro del formulario.
- Conservar foco, cancelación, cierre por teclado, estados de carga, error, vacío y envío.
No cerrar ni limpiar el formulario antes de confirmar una operación correcta.
diff --git a/docs/puestos-de-trabajo.md b/docs/puestos-de-trabajo.md
index 3f2715e..f5c3061 100644
--- a/docs/puestos-de-trabajo.md
+++ b/docs/puestos-de-trabajo.md
@@ -69,16 +69,21 @@ El estado mostrado del activo se calcula desde su estado técnico y los puestos
1. Sede y responsable. Si existe una única sede se selecciona automáticamente. El responsable es opcional; las altas relacionadas conservan el formulario.
2. Activos. Un diálogo con listado, búsqueda rápida y pestañas por plantilla permite añadir varios a la vez. Es obligatorio añadir al menos uno; se impiden duplicados, activos ocupados y nuevas asociaciones no operativas.
-3. Ordenador principal. Se elige en un diálogo de selección única entre los activos seleccionados. Si solo queda uno se asigna automáticamente; al retirar el ordenador se limpia la selección o se asigna el único restante.
-4. Resumen. Muestra sede, responsable, composición y ordenador principal. El nombre propuesto por sede/responsable se puede editar antes de Guardar.
+3. Ordenador principal. Se elige pulsando una de las fichas `AssetSummaryCard` de los activos seleccionados, sin abrir otro diálogo. Solo una tarjeta puede estar marcada; el borde y el radio identifican la elegida. El grupo conserva navegación por teclado mediante flechas y Espacio. Si solo queda uno se asigna automáticamente; al retirar el ordenador se limpia la selección o se asigna el único restante.
+4. Resumen. Muestra sede, responsable, composición y ordenador principal. El ordenador principal reutiliza `AssetSummaryCard` del paso 3, a ancho completo y con el mismo borde destacado: nombre, plantilla, icono, características y estado, sin controles de selección. La lista de activos excluye el ordenador principal para evitar duplicarlo; si no quedan otros activos muestra `—`. El nombre propuesto por sede/responsable se puede editar antes de Guardar.
-El diálogo de alta utiliza tamaño medio y cabecera con icono decorativo. Los grupos
+El diálogo de alta utiliza tamaño medio, cabecera con icono decorativo y
+`FormDialog layout="stable"`. Su altura exterior depende de la ventana y se conserva
+al cambiar de paso o añadir/retirar activos. El cuerpo tiene scroll interno y reserva
+espacio para la barra; cabecera y pie permanecen visibles. Los grupos
reutilizan `FormSection variant="plain"`; asociaciones y resumen responden al ancho
del contenedor, con dos columnas cuando caben y una en espacios estrechos. Los activos
-seleccionados se muestran como filas compactas, sin tarjetas anidadas. El nombre del
+seleccionados se muestran con `AssetSummaryCard`: fichas compactas con icono, plantilla,
+características y estado, en una o dos columnas según el espacio. El resumen reutiliza
+las mismas fichas sin acciones de retirada. El nombre del
resumen ocupa el ancho completo y los valores ausentes muestran `—`. La edición conserva
-el diálogo grande y los mismos grupos, conforme al [patrón de diálogos](patron-de-dialogos.md).
+el diálogo grande con altura estable y los mismos grupos, conforme al [patrón de diálogos](patron-de-dialogos.md).
-Anterior conserva valores y Siguiente valida los requisitos del paso. Guardar solo aparece en el resumen y no se envía el alta desde pasos intermedios. La edición mantiene un formulario único y permite conservar/completar puestos anteriores o desactivados. Cancelar permanece en el pie del modal y la ayuda inmediatamente encima.
+Anterior conserva valores y Siguiente valida los requisitos del paso. Guardar solo aparece en el resumen y no se envía el alta desde pasos intermedios. La edición mantiene un formulario único y permite conservar/completar puestos anteriores o desactivados. Cancelar permanece en el pie del modal y la ayuda se mantiene anclada inmediatamente encima, fuera del scroll de campos. Los ejemplos extensos tienen desplazamiento propio limitado al espacio disponible.
Se elimina `sharedAssetIds` de contratos, dominio, reservas y vistas. La migración explícita `removeSharedWorkstationAssets`, ejecutada durante la preparación del esquema, elimina las asociaciones de tipo compartido tanto actuales como históricas y limpia sus referencias técnicas cuando no existe ninguna asociación exclusiva histórica. No convierte activos compartidos en exclusivos, no borra activos ni revisiones y no genera reservas conflictivas. Es transaccional e idempotente. Las composiciones exclusivas e identidades se conservan.
diff --git a/modules/assets/src/common/locales/en.json b/modules/assets/src/common/locales/en.json
index 246a685..ead2079 100644
--- a/modules/assets/src/common/locales/en.json
+++ b/modules/assets/src/common/locales/en.json
@@ -386,7 +386,7 @@
"missingAsset": "Asset unavailable."
},
"workstationComputer": "Main computer",
- "workstationComputerHelp": "Select the computer from the workstation assets. Profiles will be created on it; the other assets are peripherals.",
+ "workstationComputerHelp": "Select the main computer by clicking its card among the workstation assets. Profiles will be created on it; the other assets are peripherals.",
"workstationLifecycle": {
"decommission": {
"title": "Deactivate",
@@ -418,9 +418,10 @@
"summary": "Summary",
"locationDescription": "Select a site. You can leave the owner unassigned.",
"assetsDescription": "Add at least one asset to the workstation.",
- "computerDescription": "Identify the asset that will host user profiles.",
+ "chooseComputer": "Choose {{name}} as the main computer",
+ "computerDescription": "Click the card of the asset that will host user profiles.",
"summaryDescription": "Review the details and workstation name before saving.",
- "singleComputer": "The only selected asset is automatically assigned as the main computer.",
+ "singleComputer": "The only asset is automatically selected as the main computer.",
"previous": "Previous",
"next": "Next"
},
diff --git a/modules/assets/src/common/locales/es.json b/modules/assets/src/common/locales/es.json
index 3c80e02..619b81b 100644
--- a/modules/assets/src/common/locales/es.json
+++ b/modules/assets/src/common/locales/es.json
@@ -386,7 +386,7 @@
"missingAsset": "Activo no disponible."
},
"workstationComputer": "Ordenador principal",
- "workstationComputerHelp": "Selecciona el ordenador entre los activos del puesto. Los perfiles se crearán en él; el resto de activos son periféricos.",
+ "workstationComputerHelp": "Pulsa la tarjeta del ordenador principal entre los activos del puesto. Los perfiles se crearán en él; el resto de activos son periféricos.",
"workstationLifecycle": {
"decommission": {
"title": "Desactivar",
@@ -418,9 +418,10 @@
"summary": "Resumen",
"locationDescription": "Selecciona una sede. Puedes dejar el responsable sin asignar.",
"assetsDescription": "Añade al menos un activo al puesto.",
- "computerDescription": "Identifica el activo que alojará los perfiles de usuario.",
+ "chooseComputer": "Elegir {{name}} como ordenador principal",
+ "computerDescription": "Pulsa la tarjeta del activo que alojará los perfiles de usuario.",
"summaryDescription": "Revisa los datos y el nombre del puesto antes de guardar.",
- "singleComputer": "El único activo seleccionado se asigna automáticamente como ordenador principal.",
+ "singleComputer": "El único activo está seleccionado automáticamente como ordenador principal.",
"previous": "Anterior",
"next": "Siguiente"
},
diff --git a/modules/assets/src/web/assets/select/asset-selection.test.tsx b/modules/assets/src/web/assets/select/asset-selection.test.tsx
index e7d8233..b3b1417 100644
--- a/modules/assets/src/web/assets/select/asset-selection.test.tsx
+++ b/modules/assets/src/web/assets/select/asset-selection.test.tsx
@@ -170,3 +170,38 @@ test("selector único: controles radio y disparador de diálogo; conserva el alt
assert.ok(trigger.includes("disabled"));
assert.equal(trigger.includes('role="combobox"'), false);
});
+
+test("picker: búsqueda rápida disponible durante carga y error, sin anunciar un falso vacío", async () => {
+ for (const language of ["es", "en"]) {
+ for (const loading of [true, false]) {
+ const html = await render(
+ language,
+
{t("assetPicker.selected", { count: c.selectedIds.length })}
{asset?.values.name ?? "—"}
+ {asset?.specification.templateName ?? "—"}
+
+ {summary || "—"}
+
+ {error}
+
+ {t("assetPicker.selected", { count: selectedIds.length })}
+ {name} {t(`inventoryErrors.${reason}`)} {t("noWorkstationAssets")}
+ {required &&
{t(singleComputer ? "workstationSteps.singleComputer" : "workstationComputerHelp")}
{t("noWorkstationAssets")}Quitar"), false);
+ const otherAsset = {
+ ...asset,
+ id: UniqueID.generateNewID().toPrimitive(),
+ values: { ...asset.values, name: "Monitor auxiliar" },
+ };
+
+ for (const computerAssetId of [asset.id, null]) {
+ const composition = await render(
+ language,
+
- {selectedIds.map((id) => {
- const name = assets.find((asset) => asset.id === id)?.values.name ?? "—";
- const reason = unavailableReason(id);
+
+ {selectedIds.map((id) => {
+ const asset = assets.find((item) => item.id === id);
+ const name = asset?.values.name ?? "—";
+ const reason = unavailableReason(id);
- return (
-
+ return (
+