169 lines
22 KiB
Markdown
169 lines
22 KiB
Markdown
|
|
# R2.0 — Contratos conceptuales de API y DTO
|
|||
|
|
|
|||
|
|
> **Ratificación posterior — 24 de octubre de 2026:** D01–D10 y D04-R ratificadas en los términos recomendados de R2.0; variante A, Resource único con `scope` explícito. Solo se autoriza implementar R2.1a. El registro vigente de alcance y evidencias está en [11 — Lectores compatibles](11-r2-1a-lectores-compatibles.md). Los estados «propuesto», «recomendado» y «pendiente» del cuerpo se conservan como evidencia de la versión documental inicial y quedan actualizados por este registro; ratificación conceptual no equivale a ejecución de R2.1b–R2.6.
|
|||
|
|
|
|||
|
|
Estado: **RECOMENDADA D09/D05**, sin rutas ni schemas nuevos implementados. Los identificadores de operación, tipos y errores nuevos de este documento son nombres propuestos. No se presentan como exports existentes. DTO de producción futuro debe inferirse de schemas Zod v4, no copiar interfaces de este Markdown.
|
|||
|
|
|
|||
|
|
## 1. Convenciones y compatibilidad
|
|||
|
|
|
|||
|
|
Hoy Resource usa `/api/resources/:companyId`, POST de alta/update/status/delete, JSON {values}, sesión y CSRF, schemas strict y errores {code,requestId}; no GET por ID ni paginación del inventario. `/api/resource-catalog/:scope` trata campos/plantillas. [resource.routes.ts:54](../../../modules/resources/src/api/infrastructure/http/resource.routes.ts#L54) [resource-catalog.routes.ts:65](../../../modules/resources/src/api/infrastructure/http/resource-catalog.routes.ts#L65) [inventory-api.ts:11](../../../modules/resources/src/web/resources/shared/api/inventory-api.ts#L11).
|
|||
|
|
|
|||
|
|
Propuesta de routers propios dentro de Resources: `/api/resource-global-applications`, `/api/resource-application-candidates` y `/api/resource-application-references`. Sus nombres delimitan finalidad y evitan colisionar con `/:companyId`; no representan un módulo ni tabla nuevos. Son paths **propuestos para ratificación**, justificados por plural, kebab-case, UUID en path, GET lectura, POST comandos y sufijos update/status ya usados. No se reinterpreta `/resource-catalog/global` ni se envían propiedades nuevas al ResourceSchema legacy.
|
|||
|
|
|
|||
|
|
Las operaciones locales existentes mantienen body/response y errores documentados. Nueva API requiere schema estricto para params/query/body/headers relevantes. UUID string validado mediante z.uuid, nuevos creationId exigen UUID v7; arrays/búsquedas acotados. Body con scope, companyId, actorId o integration no declarado se rechaza, no se ignora.
|
|||
|
|
|
|||
|
|
## 2. Operaciones propuestas
|
|||
|
|
|
|||
|
|
Capacidades R=sesión/global.read; M=global.manage vigente; C=sesión/contexto compañía válido bajo D04. Ninguna concede ACL no implementada. Sin sesión 401; falta capacidad 403; ID ausente/ámbito incorrecto 404 uniforme.
|
|||
|
|
|
|||
|
|
| ID | Método/path propuesto | Responsabilidad/ámbito | Auth | Request / schema conceptual | Response |
|
|||
|
|
| --- | --- | --- | --- | --- | --- |
|
|||
|
|
| OP-G01 | GET /api/resource-global-applications | Listar/buscar productos globales, activos e inactivos | R | search, active opcional, provider opcional, sort, direction, cursor, pageSize / ListGlobalApplicationsRequestSchema | Page de GlobalApplicationSummary |
|
|||
|
|
| OP-G02 | GET /api/resource-global-applications/:id | Ficha pública al soporte autenticado, sin historial | R | UUID; query vacía / GetGlobalApplicationRequestSchema | {resource:GlobalApplicationDetail} |
|
|||
|
|
| OP-G03 | GET /api/resource-global-applications/:id/admin | Root completo de gestión global | M | UUID; query vacía / GetGlobalApplicationAdminRequestSchema | {resource:GlobalResourceAdmin} |
|
|||
|
|
| OP-G04 | POST /api/resource-global-applications | Crear Resource global, scope inferido por comando | M + CSRF | {creationId,values} / CreateGlobalApplicationRequestSchema | 201 inicial, 200 reintento válido; {resource:GlobalResourceAdmin} |
|
|||
|
|
| OP-G05 | POST /api/resource-global-applications/:id/update | Editar valores permitidos, sin ámbito/owner/template nuevos | M + CSRF | {values} / UpdateGlobalApplicationRequestSchema | 200 {resource:GlobalResourceAdmin} |
|
|||
|
|
| OP-G06 | POST /api/resource-global-applications/:id/status | Desactivar/reactivar, idempotente | M + CSRF | {active:boolean} / SetGlobalApplicationActiveRequestSchema | 200 {resource:GlobalResourceAdmin} |
|
|||
|
|
| OP-G07 | GET /api/resource-global-applications/:id/history | Historial administrativo del producto | M | UUID, cursor, pageSize / ListGlobalApplicationHistoryRequestSchema | Page de GlobalResourceRevision |
|
|||
|
|
| OP-C01 | GET /api/resource-application-candidates/:companyId | Búsqueda mixta de candidatos nuevos | C | purpose, search, cursor, pageSize / ListApplicationCandidatesRequestSchema | {purpose,items:ResourceCandidate[],nextCursor} según purpose |
|
|||
|
|
| OP-R01 | POST /api/resource-application-references/:companyId/resolve | Resolver UUIDs explícitos para lectura instalación/perfil | C + CSRF | {purpose,ids} / ResolveApplicationReferencesRequestSchema | {purpose,items:ResolutionResult[]} en orden solicitado |
|
|||
|
|
| OP-S01 | POST /api/software-installations/:companyId y /:id/update | Asociación a recurso de software global/local en contrato operativo existente | C + CSRF | SoftwareInstallationInputSchema existente, sin scope del producto | SoftwareInstallationResponseSchema existente |
|
|||
|
|
| OP-A01 | POST /api/application-configurations/:companyId y /:id/update | Asociación al producto para perfil de C | C + CSRF | ApplicationConfigurationInputSchema existente | ApplicationConfigurationResponseSchema existente |
|
|||
|
|
|
|||
|
|
OP-S01/OP-A01 no son endpoints nuevos; se adaptará resolución backend manteniendo DTO operativo. Listados/historiales GET de instalación/perfil mantienen sus shapes y consultan referencias por lotes desde la capa API/query, sin cargar todo el inventario. La autorización y validación de guardado se repiten en servidor, independientemente de OP-R01. No se añade endpoint global de delete, fusión o cambio de scope.
|
|||
|
|
|
|||
|
|
OP-C01 incluye globales solo si está habilitado el consumidor de su purpose; con flag apagado conserva candidatos locales válidos. OP-R01 resuelve usados globales aun con flags apagados. OP-G06 permite desactivación de seguridad con catálogo off; reactivación requiere catálogo habilitado.
|
|||
|
|
|
|||
|
|
OP-G04/05 values conserva name, templateId, active, notes, characteristics, con límites Resource existentes y **sin integration**. Alta resuelve templateId con stockKey y verifica que el enviado coincida; nunca acepta UUID arbitrario de copia. Edición mantiene templateId y todos los demás campos fijos; permite editar producto inactivo. creationId es una identidad solicitada, no un permiso. Payload equivalente se compara contra primera revisión normalizada para reintento; distinto devuelve conflicto aunque el nombre coincida. No existe idempotencia global por nombre. Detalle en D07.
|
|||
|
|
|
|||
|
|
## 3. Proyecciones de datos
|
|||
|
|
|
|||
|
|
| DTO conceptual | Campos incluidos | Campos excluidos |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| CompanyResourceLegacy | ResourceDTO existente exacto: companyId UUID, values/spec/history/proyecciones | scope nuevo, NULL y globales |
|
|||
|
|
| GlobalResourceAdmin | id, scope=global, companyId=null, values globales, specification; historial por OP-G07 | Instalaciones, compañías usuarias y proyecciones operativas |
|
|||
|
|
| GlobalResourceRevision | id, actorId, actorName, recordedAt, values/specification históricos | Uso por compañías o cambios de sus relaciones |
|
|||
|
|
| GlobalApplicationSummary | id, scope=global, companyId=null, templateId, kind=application, name, active, provider nullable | notes, characteristics extra, history, actores |
|
|||
|
|
| GlobalApplicationDetail | Summary + url y description nullable procedentes de campos aprobados | Especificación arbitraria/historial, cuentas y datos operativos |
|
|||
|
|
| ResourceCandidate | id/scope/companyId/templateId/kind/name/provider, active=true | History, integración, notas, licencias y versión instalada |
|
|||
|
|
| ApplicationReference | Identidad/procedencia/nombre/templateId/kind=application, active boolean | Instalación/configuración y permiso efectivo |
|
|||
|
|
| SoftwareReference | ApplicationReference o referencia OS **local** | OS global y licencias |
|
|||
|
|
| Información instalación | DTO operativo existente, resourceId, assetId, licencia local, version/state/notes/history/needsReview | Datos de B o historia administrativa del global |
|
|||
|
|
| Información perfil | DTO operativo existente, profileId/resourceId/delivery/version/state/notes/history/needsReview | Datos de instalación del equipo como estado compartido |
|
|||
|
|
|
|||
|
|
`ResourceReferenceDTO` existente en Resources common describe account/system/environment/resource_type de Access. **No se reutiliza** ese nombre/schema para referencias de aplicación; son otro contrato. [inventory.ts:35](../../../modules/resources/src/common/inventory.ts#L35) [software-installations.ts:19](../../../modules/companies/src/common/software-installations.ts#L19) [application-configurations.ts:20](../../../modules/companies/src/common/application-configurations.ts#L20).
|
|||
|
|
|
|||
|
|
Solo se publican características cuyo field stockKey sea provider, url o description en la plantilla de serie; se resuelven sus UUID reales en servidor. Si no están ligados o no tienen valor, null; no se busca un campo por nombre visible ni se usa otro campo similar. Candidate solo necesita provider; reference mínima no necesita proveedor/URL. Whitelist evita publicar automáticamente campos administrativos añadidos a la plantilla. Valores extra permanecen en DTO admin. Fabricante es provider textual, no ManufacturerId. No existe categoría/familia/externalId canónico de serie: no se inventan filtros de esas entidades.
|
|||
|
|
|
|||
|
|
## 4. Vocabulario TypeScript conceptual
|
|||
|
|
|
|||
|
|
Este bloque expresa contratos, **no validación ni implementación lista para copiar**. En producción se definen schemas semánticos y tipos z.infer. Las strings UUID externas se validan; dominio usa UniqueID. No exportar enums, tipos ORM ni casts como validación.
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
type ResourceScope = "global" | "company";
|
|||
|
|
|
|||
|
|
type ScopedResource =
|
|||
|
|
| { scope: "global"; companyId: null }
|
|||
|
|
| { scope: "company"; companyId: string };
|
|||
|
|
|
|||
|
|
type ResourcePurpose =
|
|||
|
|
| "software-installation"
|
|||
|
|
| "application-configuration";
|
|||
|
|
|
|||
|
|
type ResourceAvailability = "active" | "inactive";
|
|||
|
|
|
|||
|
|
type ApplicationReference = ScopedResource & {
|
|||
|
|
id: string;
|
|||
|
|
templateId: string;
|
|||
|
|
name: string;
|
|||
|
|
kind: "application";
|
|||
|
|
active: boolean;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type LocalOsReference = {
|
|||
|
|
scope: "company";
|
|||
|
|
companyId: string;
|
|||
|
|
id: string;
|
|||
|
|
templateId: string;
|
|||
|
|
name: string;
|
|||
|
|
kind: "os";
|
|||
|
|
active: boolean;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type SoftwareReference = ApplicationReference | LocalOsReference;
|
|||
|
|
|
|||
|
|
type ResourceCandidate =
|
|||
|
|
(ApplicationReference | LocalOsReference) & {
|
|||
|
|
active: true;
|
|||
|
|
provider: string | null;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type ReferenceQuery =
|
|||
|
|
| { purpose: "software-installation"; ids: readonly string[] }
|
|||
|
|
| { purpose: "application-configuration"; ids: readonly string[] };
|
|||
|
|
|
|||
|
|
type ResolutionResult =
|
|||
|
|
| { id: string; status: "resolved"; reference: SoftwareReference }
|
|||
|
|
| { id: string; status: "unavailable" };
|
|||
|
|
|
|||
|
|
type ResourceValidationError =
|
|||
|
|
| "INVALID_REQUEST"
|
|||
|
|
| "RESOURCE_GLOBAL_TEMPLATE_NOT_ALLOWED"
|
|||
|
|
| "TEMPLATE_UNAVAILABLE"
|
|||
|
|
| "RESOURCE_CREATION_CONFLICT";
|
|||
|
|
|
|||
|
|
type ResourceEligibility =
|
|||
|
|
| { result: "eligible" }
|
|||
|
|
| { result: "existing-only"; reason: "inactive" }
|
|||
|
|
| { result: "rejected"; reason: "not-found-or-not-allowed" };
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Los schemas de respuesta de application-configuration restringen reference/candidate a kind application; software-installation admite la unión SoftwareReference. El tipo ancho de la tabla no habilita OS para perfil. OP-C01 y OP-R01 responden además con el purpose literal solicitado; la respuesta por propósito se valida como unión discriminada externa, no un genérico que pierda correlación. availability es vocabulario derivado de active, no una columna/campo adicional obligatorio.
|
|||
|
|
|
|||
|
|
`existing-only` significa lectura/recuperación de vínculo ya existente según mapper; no es un token ni permiso para dar un alta nueva. En el guardado backend, la consulta de resolución estructural se combina con `new` o `edit` y el registro previo encontrado por servidor. No se acepta previous/existing enviado por cliente para saltar disponibilidad.
|
|||
|
|
|
|||
|
|
## 5. Errores y límites
|
|||
|
|
|
|||
|
|
| Código | Existente/propuesto | HTTP y significado |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| INVALID_REQUEST | Existente | 400: schema, scope/owner contradictorio, unknown fields, UUID/propósito/cursor/límites inválidos |
|
|||
|
|
| UNAUTHENTICATED / FORBIDDEN | Existentes Identity | 401/403 por sesión/capacidad; nuevas routes mapean 403 explícito |
|
|||
|
|
| COMPANY_NOT_FOUND / RESOURCE_NOT_FOUND | Existentes | 404: contexto o root no disponible; no revelar local en route global |
|
|||
|
|
| TEMPLATE_UNAVAILABLE | Existente | 409: no disponible para **alta Resource**, no error universal de asociación |
|
|||
|
|
| RESOURCE_GLOBAL_TEMPLATE_NOT_ALLOWED | Propuesto | 409: plantilla/tipo/owner/campos/integración incompatible con alta global |
|
|||
|
|
| TEMPLATE_FIXED / *_TARGET_IMMUTABLE | Existentes | 409: destino/plantilla fijos en contratos que los admiten; campos nuevos de scope en body estricto dan 400 |
|
|||
|
|
| SOFTWARE_REFERENCE_NOT_FOUND / APPLICATION_REFERENCE_NOT_FOUND | Existentes | 404: UUID ausente, ajeno o tipo incorrecto, mismo error seguro |
|
|||
|
|
| SOFTWARE_REFERENCE_UNAVAILABLE / APPLICATION_REFERENCE_UNAVAILABLE | Existentes | 409: referencia estructural válida pero inactiva/no disponible al asociar/reconfigurar |
|
|||
|
|
| RESOURCE_CREATION_CONFLICT | Propuesto | 409: creationId ocupado o reintento no equivalente; sin datos de raíz ajena |
|
|||
|
|
| RESOURCE_GLOBAL_FEATURE_DISABLED | Propuesto | 409: operación de escritura global/despliegue no habilitada |
|
|||
|
|
| RESOURCE_CLIENT_UPGRADE_REQUIRED | Propuesto | 409: cliente legacy intenta leer/mutar relación que ya usa global; recargar versión compatible |
|
|||
|
|
| INTERNAL_ERROR | Contrato de error saneado | 500, requestId; causa técnica solo en diagnóstico servidor |
|
|||
|
|
|
|||
|
|
Errores nuevos pasan por Result de negocio y traducción HTTP coherente; no convertir fallos SQL/corrupción a []/unavailable. No usar sufijo NOT_FOUND/409 como único mapper para errores nuevos de autorización. OP-R01 devuelve unavailable por ID inaccesible y sigue con los accesibles; una falta de sesión/contexto deniega el lote entero antes de leer. Un error de infraestructura falla la petición completa para no fingir ausencia. Como mucho 100 UUIDs únicos; 0, 101, duplicados o inválidos →400. Orden de salida igual al de entrada.
|
|||
|
|
|
|||
|
|
## 6. Búsqueda y paginación
|
|||
|
|
|
|||
|
|
Contrato recomendado común: search omitida equivale a búsqueda vacía; trim, máximo 100 caracteres; pageSize entero 1..100 con valor operativo por defecto 20 (default técnico de paginación, sin rellenar datos de negocio). cursor opaco con tamaño máximo 2048 caracteres. Primer request sin cursor; siguiente usa nextCursor o null. No mezcla de offset y cursor; no total exacto obligatorio ni count transversal. Page={items,nextCursor}; filtro/orden se aplican **antes** del límite. Admin/public catálogo puede filtrar active true/false u omitir ambos; candidatos siempre activos.
|
|||
|
|
|
|||
|
|
Orden catálogo propuesto: sort=name o id, direction=asc/desc, default name asc; candidatos name asc y UUID como desempate estable, sin prioridad implícita por origen. Orden name se compara por la clave normalizeSearchText con comparación lexicográfica determinista de strings de ECMAScript, después UUID, sin locale de cliente/SO; no se presenta como collation española de diccionario. Comparación textual recomendada equivalente a normalizeSearchText/createTextMatcher, ahora presente en el árbol por cambios concurrentes: trim, minúsculas, NFD, quitar de vocales las marcas U+0300/0301/0302/0303/0308, volver a NFC; conservar ñ y otras escrituras. Buscar substring en nombre o provider (OR); provider como filtro separado combina por AND. No colapsar espacios interiores para búsqueda, manteniendo el helper; la normalización de aviso de duplicados D07 es deliberadamente distinta. La búsqueda es recuperación amplia, no equivalencia. [text-search.ts:2](../../../packages/rdx-ui/src/lib/text-search.ts#L2). No asumir que utf8mb4_unicode_ci o quitar todos los diacríticos cumple la misma semántica. Sin regex/SQL del usuario: % y _ son texto literal. La lógica pura puede compartirse desde un paquete común sin dependencia servidor→rdx-ui; se exige equivalencia observable, no duplicar implementación divergente.
|
|||
|
|
|
|||
|
|
Primera implementación recomendada sin columnas de búsqueda nuevas: SQL carga en lote únicamente metadatos candidatos autorizados (scope/tipo/compañía/estado y provider), y el servicio de servidor aplica normalización, filtros, orden y cursor antes de producir la página. No LIMIT antes del filtro normalizado, no lectura de history/spec completa ni una consulta por fila. El coste de recorrer metadatos elegibles es explícito y debe medirse en P02; si no es aceptable, se necesita aprobar una proyección/indexación derivada que preserve exactamente esta búsqueda, no degradarla silenciosamente.
|
|||
|
|
|
|||
|
|
Cursor representa último tuple de orden (clave normalizada de nombre/id o id), filtros, propósito y versión. Se valida que corresponda al request/contexto; nunca acredita autorización ni salta el filtro C. Si cambia search/filter/purpose/sort, comenzar sin cursor. Bajo ediciones concurrentes del catálogo no se promete snapshot entre páginas: un renombrado puede mover filas; refrescar reinicia cursor y el frontend deduplica solo por UUID, sin borrar homónimos. Pruebas deterministas de paginación sin mutación y pruebas de recuperación ante cambio, no garantías temporales inventadas.
|
|||
|
|
|
|||
|
|
Historial global: orden position descendente con UUID de revisión/posición como cursor, misma validación de tamaño; conservar orden inmutable y siguiente página sin recargar todo el producto. Campos históricos no se buscan en candidatos.
|
|||
|
|
|
|||
|
|
## 7. Compatibilidad de clientes, caché y navegación
|
|||
|
|
|
|||
|
|
Legacy ResourceSchema local sigue exacto y non-null. Los DTO globales viven en schemas nuevos y los de instalación/perfil no reciben campos de producto extra. Antes de activar referencias globales se despliegan lectores de nombres por lote en listados, historiales, fichas, puestos, entorno y revisión de procesos. [sequelize-process.repository.ts:109](../../../modules/companies/src/api/infrastructure/persistence/sequelize-process.repository.ts#L109) [inventory-api.ts:11](../../../modules/resources/src/web/resources/shared/api/inventory-api.ts#L11) [resource-related-query-keys.ts:4](../../../modules/resources/src/web/resources/shared/api/resource-related-query-keys.ts#L4).
|
|||
|
|
|
|||
|
|
**Propuesta de negociación:** cliente nuevo envía `X-Metis-Resources-Contract: 2` en flujos operativos capaces de resolver globales. Es una señal de compatibilidad, no autorización; valores desconocidos/contradictorios se rechazan al negociar. Ausencia conserva comportamiento legacy mientras las relaciones de la respuesta/operación sean locales. Si una respuesta/registro contextual ya contiene un Resource global y falta capacidad v2, servidor devuelve 409 RESOURCE_CLIENT_UPGRADE_REQUIRED, sin servir una lista parcial ni fingir inventario vacío. No se bloquea la gestión local de productos por existir globales. El servidor no puede garantizar que un navegador con JavaScript antiguo se actualice automáticamente; el gate de despliegue requiere recarga coordinada y fallback de error. Backends antiguos no pueden atender compañías con referencias globales.
|
|||
|
|
|
|||
|
|
Keys conceptuales: catálogo público por filtros/orden/cursor; admin por actor/authVersion/id; candidatos por companyId/purpose/search/cursor; referencias por companyId/purpose/lista ordenada de IDs; operaciones por compañía como hoy. Mutación global invalida catálogo, candidatos, referencias y familias software/application afectadas en el cliente que muta; nunca toda la caché ni caché admin de otro sujeto. Para otros clientes: refetch al foco/reconexión y revalidación de queries operativas al volver al contexto; no afirmar push ni consistencia inmediata entre pestañas. Todos los guardados validan de nuevo en servidor. Logout/denegación elimina caché no autorizada; un stale no concede permiso.
|
|||
|
|
|
|||
|
|
Navegación propuesta, pendiente de D09: `/catalogs/global-applications` y `/catalogs/global-applications/:id`, dentro del shell existente de catálogos globales de Resources. No son rutas actuales acreditadas. Enlaces globales van a esa ficha, los locales conservan `/companies/C/resources/ID`; retorno conserva compañía, activo/perfil y estado del listado. Catálogo amplio usa patrón de [listados](../../patron-de-listados-de-entidades.md); referencias de dashboard usan [compactos](../../patron-de-listados-compactos.md), no otra tabla de exploración.
|
|||
|
|
|
|||
|
|
Reutilizar EntityTable/EntityListHeader y CompactTable/CompactList/CollectionPreview según tarea, comprobando sus contratos; CompactTable no busca/pagina remoto. SelectWithAction actual no implementa búsqueda remota. Selector nuevo compone primitivas existentes con datos/acciones del consumidor, búsqueda conjunta y origen «Global»/«De la compañía», sin paso previo de scope. Inactivo sigue en referencia actual, fuera de candidatos nuevos; versión/licencia no se autocompletan. Alta/edición restringidas en modal propio con foco/teclado/altura estable y ayuda antes del pie. Valores ausentes de celdas: —, i18n es/en. [select-with-action.tsx:19](../../../packages/rdx-ui/src/components/select-with-action.tsx#L19) [option-picker-list.tsx:7](../../../packages/rdx-ui/src/components/option-picker-list.tsx#L7) [compact-table.tsx:26](../../../packages/rdx-ui/src/components/compact-table.tsx#L26) [collection-preview.tsx:6](../../../packages/rdx-ui/src/components/collection-preview.tsx#L6).
|
|||
|
|
|
|||
|
|
Skills: ux-tables principal y ux-search complementaria, ya consultadas en la auditoría de esta conversación; influyen en tabla amplia frente a referencia compacta, búsqueda visible y recuperación sin resultados. Se contrastan DESIGN, matriz de skills y patrones actuales. No se aplica diseño visual nuevo ni se afirma auditoría de accesibilidad/navegador. Auditores UX especializados no instalados; no se instalan. Pruebas visuales futuras en 08.
|