Skip to content

Resincronización con la plataforma de origen: código y documentación rebrandeados al satélite - #14

Merged
beyondnetPeru merged 35 commits into
mainfrom
migration/unimar-ums-resync-2026-08-09
Aug 10, 2026
Merged

Resincronización con la plataforma de origen: código y documentación rebrandeados al satélite#14
beyondnetPeru merged 35 commits into
mainfrom
migration/unimar-ums-resync-2026-08-09

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Resincroniza src/ y docs/ con la plataforma de origen (unimar-ums), aplicando el mapa de marca del satélite y preservando lo que es propio de este repo. Cero residuos de marca en todo el repositorio.

Mapa de marca

origen destino
Unimar.Ums.* Ums.*
Unimar.Shells.Net.* (feed privado unimar-peru) BeyondNetCode.Shell.* (nuget.org)
@unimar-peru/ums-sdk-* @ums/sdk-*
inquilino raíz UNIMAR / unimar.com.pe BEYONDNET / beyondnet.com.pe (mismo GUID)
unimar-cluster-ums, unimar-core, unimar_arch evolith-ums-cluster, Evolith Core, evolith-core
prefijo de telemetría unimar. evolith.

El namespace del instalador de AOP no coincide con el id del paquete: BeyondNetCode.Shell.Aop.DI como paquete, BeyondNetCode.Shell.Aop.Microsoft.Extensions.DependencyInjection.Aspects.Installer como namespace.

Con esto el arnés e2e y la siembra por fin nombran al mismo inquilino raíz: el arnés ya decía BEYONDNET_TENANT_ID mientras el seeder seguía sembrando UNIMAR.

Preservado del satélite

Activos que no existen en la plataforma de origen y que un reemplazo ciego habría borrado:

  • Branding por inquilino — entidad hija reintegrada al agregado Tenant (SetBranding, UpdateBranding, VerifyBrandingDns, FailBrandingDns, RemoveBranding), sus cinco eventos, DomainErrors.Branding, endpoints REST y la pestaña «Identidad Visual» reenganchada en el web (BrandingPanel había quedado huérfano al sustituirse TenantDetailPanel).
  • GraphQL — vuelve HotChocolate al .csproj y el cableado al bootstrapper (AddUmsGraphQl + MapGraphQlSurface). Los resolvers pasan a static: son sin estado y el gate heredado promueve S2325 a error.
  • Evolith.Contracts.MasterData — conserva su namespace a propósito. Es el contrato de wire del productor (MMS) y MassTransit enruta por el URN del tipo (ADR-0106). No sigue la convención Ums.* del satélite deliberadamente.
  • Identidad Evolith en Dockerfile, clúster local y evolith.yaml.

Retirado por sustitución

Menu/SubMenu/OptionMenuNode · SchemaBootstrapper SQLite/PostgreSQL y umsdev.db → migraciones EF · RemoveBranchCloseBranch (ADR-0164) · RemoveTemplateItemDeactivateItem · RequestContextAccessorObservability/RequestContext · sink y background service de AuditTrail → AuditTrailPersistenceConsumer · PromotionStatus → modelo IGA de madurez de rol.

También se van Ums.slnx (apuntaba a libs/shell inexistentes) y src/Ums.ReadModels, que estaba mal ubicado con rutas anidadas rotas (src/Ums.ReadModels/... y src/Ums/ReadModels/...) que dejó una resincronización anterior; el proyecto vive en apps/ums.api/Ums.ReadModels, que es lo que la solución referencia.

Documentación

La documentación de origen usa taxonomía SDLC (01-concepcion..05-entrega) y la de este repo es bilingüe por dominio. Cada documento se ubica donde le corresponde; no se importa el árbol de fases.

  • 02-diseno/dominio/**docs/domain-es/** (85 fichas actualizadas)
  • 02-diseno/historias-funcionales/**governance/requirements-es/functional-stories (slug español → inglés, mismo número)
  • 01-concepciongovernance/product-es · 02-diseno/*.mdarchitecture/*.es.md · 04-validacionqa/

Validación

resultado
Ums.sln build 0 errores
Ums.Domain.Test 930/930
Ums.Application.Test 933/934
Ums.Sdk.Tests 53/53
vite build + vitest OK · 1697/1697
EF model vs snapshot sin cambios pendientes

El único fallo es del reloj, no del cambio: T12_AuditFields_OnCreate_CarryActorFromUserContext compara DateTime.UtcNow.Date contra un CreatedAt que el paquete externo BeyondNetCode.Shell.Ddd.ValueObjects estampa en hora local, así que falla en la ventana UTC 00:00–05:00 (al ejecutarlo, UTC era 10-ago y Lima 9-ago).

Decisiones que conviene revisar

  1. Renumeración FS-25/26/27 → FS-38/39/40. El código traído usa FS-25 = dataset semilla y FS-26 = Admin Root, así que el origen se queda con esos números y las historias propias del repo se corren a FS-38 (jerarquía DDD), FS-39 (preview del grafo) y FS-40 (broken rules), con referencias e índices al día.
  2. Se elimina la migración DropTenantBrandingTable en vez de añadir un re-create. Este satélite nunca soltó la tabla —InitialPostgresCreate ya la crea— y así se evita un drop/create que destruiría datos de branding. El snapshot de EF se regenera: sin cambios pendientes de modelo.
  3. Se corrige un fallo que también está en el origen: faltaba Apply(TemplateAutoAssignedEvent) en ProfileDomainEventsManager y sin él CreateProfileCommandHandler revienta al auto-asignar plantilla (3 pruebas en rojo).

Pendiente (no bloquea)

Las fichas de dominio nuevas y las historias FS-25/26/36/37 llegan solo en español; los índices en inglés las enlazan marcadas como tales. docs/domain/ (EN) queda por detrás de docs/domain-es/.

🤖 Generated with Claude Code

beyondnetPeru and others added 5 commits August 9, 2026 20:16
…ndeado al satélite

Reemplaza Ums.Domain/Application/Infrastructure/Presentation/ReadModels y sus
pruebas por el árbol de la plataforma de origen, aplicando el mapa de marca del
satélite: Unimar.Ums -> Ums, Unimar.Shells.Net -> BeyondNetCode.Shell (nuget.org,
sin feed privado), inquilino raíz UNIMAR -> BEYONDNET (mismo GUID, que es el que
el arnés e2e ya esperaba), prefijo de telemetría unimar. -> evolith.

Se preserva lo que es del satélite y no existe en el origen:

- Branding por inquilino. La entidad hija vuelve al agregado Tenant (SetBranding,
  UpdateBranding, VerifyBrandingDns, FailBrandingDns, RemoveBranding), con sus
  cinco eventos, sus dos códigos de DomainErrors.Tenant y el catálogo
  DomainErrors.Branding. Se retira la migración DropTenantBrandingTable del
  origen —este satélite nunca soltó la tabla, que ya la crea InitialPostgresCreate—
  y se regenera el snapshot de EF: sin cambios pendientes de modelo y sin
  drop/re-create que destruya datos.
- GraphQL. Vuelve HotChocolate al csproj y el cableado al bootstrapper
  (AddUmsGraphQl + MapGraphQlSurface, sin rate limiter porque el árbol nuevo no
  registra ninguno). Los resolvers pasan a static: son sin estado y el gate
  heredado promueve S2325 a error.
- Evolith.Contracts.MasterData conserva su namespace a propósito: es el contrato
  de wire del productor (MMS) y MassTransit enruta por el URN del tipo (ADR-0106).

Se retira lo que el diseño nuevo sustituye: Menu/SubMenu/Option (los reemplaza
MenuNode), los SchemaBootstrapper de SQLite/PostgreSQL y los umsdev.db (el
arranque migra con EF y el proveedor es PostgreSQL), RequestContextAccessor,
CorrelationIdMiddleware, el sink y el background service de AuditTrail,
RemoveBranch (lo sustituye CloseBranch, ADR-0164), RemoveTemplateItem
(DeactivateItem) y PromotionStatus (modelo IGA de madurez de rol). Ums.slnx se
va por obsoleto: apuntaba a libs/shell inexistentes.

Se corrige un fallo que también está en el origen: faltaba
Apply(TemplateAutoAssignedEvent) en ProfileDomainEventsManager, y sin él
CreateProfileCommandHandler revienta al auto-asignar plantilla (3 pruebas).

Validación: build 0 errores; Domain.Test 930/930; Application.Test 933/934. El
fallo restante es del reloj, no del cambio: T12_AuditFields compara
DateTime.UtcNow.Date contra un CreatedAt que el paquete externo
BeyondNetCode.Shell.Ddd.ValueObjects estampa en hora local, así que falla en la
ventana UTC 00:00-05:00 (ahora mismo UTC es 10-ago y Lima 9-ago).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rma de origen

Trae el árbol de libs/sdk rebrandeado: Unimar.Ums.Sdk.* -> Ums.Sdk.*,
Unimar.Shells.Net.Aop* -> BeyondNetCode.Shell.Aop* (el namespace del instalador
de DI no es el id del paquete: BeyondNetCode.Shell.Aop.DI como paquete,
BeyondNetCode.Shell.Aop.Microsoft.Extensions.DependencyInjection.Aspects.Installer
como namespace), @unimar-peru/ums-sdk-* -> @ums/sdk-* y <Authors> a BeyondNetCode.

Validación: build 0 errores; Ums.Sdk.Tests 53/53.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rvando Branding y GraphQL

Reemplaza src/ y tests/ del web-app por el árbol de origen rebrandeado y vuelve a
enganchar lo que es del satélite y el origen no tiene:

- BrandingPanel quedaba huérfano tras la sustitución de TenantDetailPanel. Se
  reintroduce la pestaña «Identidad Visual» de punta a punta: el tipo de pestaña
  en use-tenant-dashboard, el icono y la etiqueta en el panel, y las claves
  tabBranding en los dos idiomas.
- Se conserva la capa GraphQL (graphqlClient y los módulos *.graphql.ts), que
  sigue siendo el transporte de lectura de branding.service.

Se retiran dos ficheros que quedan sin consumidor porque el árbol nuevo accede a
datos por REST: query-transport.service (elegía REST/GraphQL por parámetro) y
branding.model (ya estaba muerto antes de este cambio). Los componentes de
jerarquía MenuRow/SubMenuRow/OptionRow/ModuleCard se van con el modelo de menú de
tres niveles: los sustituye SuiteNode/SuiteNodeTree.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… provisioning

Trae tests/e2e-functional, infra, provisioning y scripts del origen, rebrandeados:
el clúster local pasa a evolith-ums-cluster (kind-evolith-ums-cluster), el host de
UAT a evolith-ums-uat.local, unimar-core/unimar_arch a Evolith Core y las
credenciales de prueba al inquilino BEYONDNET (beyondnet.com.pe,
BeyondNet.Dev.2026). Con esto el arnés y la siembra por fin nombran al mismo
inquilino raíz: hasta ahora el arnés ya decía BEYONDNET_TENANT_ID y el seeder
seguía sembrando UNIMAR.

Se elimina src/Ums.ReadModels: estaba mal ubicado —con rutas anidadas rotas
(src/Ums.ReadModels/... y src/Ums/ReadModels/...) que dejó una resincronización
anterior— y la solución referencia apps/ums.api/Ums.ReadModels, que es donde el
proyecto vive ahora.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…a del satélite

La documentación de origen usa taxonomía SDLC (01-concepcion..05-entrega) y la de
este repo es bilingüe por dominio. Cada documento se ubica donde le corresponde,
rebrandeado; no se importa el árbol de fases.

- 02-diseno/dominio/** -> docs/domain-es/** (85 fichas actualizadas). Entra
  menu-node.md y salen menu/option/sub-menu.md, también del espejo en inglés:
  describían un modelo que el código ya no tiene. Se conserva identity/branding.md,
  que es del satélite.
- 02-diseno/historias-funcionales/** -> governance/requirements-es/functional-stories,
  traduciendo el slug español al inglés que ya usa este repo (fs-01-autenticacion-usuario
  -> fs-01-user-authentication) y manteniendo el número.
- 01-concepcion -> governance/product-es (PRD y backlog ágil).
- 02-diseno/*.md transversales y detallados -> architecture/*.es.md.
- 04-validacion/plan-e2e-fase1-autenticacion -> qa/phase1-authentication-e2e-plan.es.md.

FS-25, FS-26 y FS-27 colisionaban: el código resincronizado usa FS-25 para el
dataset semilla y FS-26 para el Admin Root, así que las tres historias propias de
este repo se renumeran a FS-38 (jerarquía DDD), FS-39 (preview del grafo) y FS-40
(broken rules), con sus referencias e índices al día. Sus variantes .es.md se
mueven a requirements-es, que es donde toca.

Las fichas de dominio nuevas y las historias FS-25/26/36/37 llegan solo en
español; los índices en inglés las enlazan marcadas como tal. La traducción queda
pendiente.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
// débil sin que nada falle. Se publica junto a una buena para comprobar que se descarta ella
// sola, no el JWKS entero.
const buena = parDeClaves('kid-bueno');
const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 1024 });
'Content-Type': 'application/json',
...(portador ? { Authorization: `Bearer ${portador}` } : {}),
},
body: cuerpo === undefined ? undefined : JSON.stringify(cuerpo),
...(cookie ? { Cookie: cookie } : {}),
Accept: 'application/json',
},
body: cuerpo ? JSON.stringify(cuerpo) : undefined,
* fallos de carga y mentía sobre qué se había cargado. Se espera lo que la propia respuesta pide.
*/
async function api(metodo, ruta, cuerpo, intento = 0) {
const res = await fetch(`${URL_BASE}${ruta}`, {
Comment thread src/apps/ums.web-app/src/application/stores/auth.store.ts Fixed
Comment thread src/apps/ums.web-app/tests/permission-template-state.spec.ts Fixed

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

beyondnetPeru and others added 3 commits August 9, 2026 20:27
- npm audit (high): el lockfile del origen arrastra nanoid 3.3.16 vía
  @vitejs/plugin-react -> vite -> postcss, afectado por GHSA-2v37-7h3g-55p8
  (bucle infinito con size 0). Se fija override "nanoid": "3.3.17", versión
  exacta como el resto. El lock se actualiza en sitio (--package-lock-only, 3
  líneas), no se regenera: regenerarlo desde cero en este monorepo deja fuera
  dependencias transitivas. `npm audit --omit=dev --audit-level=high` pasa a 0.

- Sensitive Information Scan: el escáner busca `password\s*[:=]\s*['"]` en docs/
  y el ejemplo de e2e-sdlc-dashboard-integration-analysis.es.md casaba con un
  placeholder ya redactado (`password:'…'`). Se sustituye por `<omitido>`, que
  dice lo mismo sin parecer un literal.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los cuatro fallan por bugs propios del workflow, no por hallazgos. Dos de ellos
(npm audit y hadolint) llevan `if: github.event_name == 'pull_request'` y en los
push a main salen `skipped`, así que se ejecutaron por primera vez en este PR y
salieron rotos de fábrica.

security.yml
- npm Vulnerability Audit: `npm ci` moría con ENOENT buscando un package.json en
  la raíz. El workspace npm/nx vive bajo src/. Se añade el mismo
  `defaults.run.working-directory: src` que ya usa ci.yml, con node 22 (React
  Router v8 exige >=22, el NODE_VERSION global es 20) y cache sobre
  src/package-lock.json. La auditoría pasa a `--omit=dev` por la razón que ci.yml
  ya documenta: se audita lo que se despliega; las high restantes son de tooling
  de build y no llegan al runtime.
- Docker Image Security Scan: `hadolint/hadolint-action@v3` no es un tag que
  exista y el job moría en «Set up job». Se fija a v3.3.0. Además apuntaba a un
  Dockerfile en la raíz que no hay: el `if [ -f "Dockerfile" ]` nunca se cumplía,
  no se construía imagen y Trivy escaneaba una referencia inexistente (lo tapaba
  su continue-on-error). Ahora apunta a src/apps/ums.api/Dockerfile y construye
  con contexto src/, que es lo que el grafo de proyectos necesita.

docs-quality.yml
- Mermaid Diagram Syntax: `grep -r "```mermaid"` entre comillas dobles abre una
  sustitución de comandos y el paso moría con «unexpected EOF while looking for
  matching `». Se pasa a comillas simples y se añade `|| true` para el caso sin
  coincidencias, que en grep es salida 1 y bajo `bash -e` tumbaba el paso igual.
- Check for TODO/demo references: buscaba la palabra suelta en todo docs/, así que
  disparaba sobre prosa española («TODOS los perfiles») y sobre los documentos que
  hablan DE los marcadores —el registro de deuda técnica, el TODO del proyecto que
  es su inventario, las actas de release—. Cumplirlo exigía borrar el inventario de
  deuda para poder publicar documentación. Ahora busca el marcador (`TODO:`,
  `TODO(`, `FIXME`, `HACK:`, `DEMO`), excluye esos documentos meta y vacía los code
  spans antes de comparar: citar `TODO(G-069)` para explicar dónde está la deuda no
  es tener deuda.

Verificado en local: los dos YAML parsean, la extracción de mermaid produce 7095
bloques sin reventar, el check de TODO queda limpio y `npm audit --omit=dev
--audit-level=high` da 0 vulnerabilidades.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l web-app

- security.yml: el job de Docker seguía muriendo en «Set up job», ahora por
  `github/codeql-action/upload-security-results@v2`, que no existe. La acción real
  es `upload-sarif`, y v2 está retirada. Se pasa a v3 y se sube solo si Trivy llegó
  a escribir el fichero, en vez de fallar el paso.

- web-app: se aplica `eslint --fix` y `prettier --write` sobre el árbol importado
  (45 ficheros). Baja de 858 a 645 hallazgos; lo corregido es formato y arreglos
  automáticos, sin cambios de comportamiento.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

beyondnetPeru and others added 3 commits August 9, 2026 20:55
…rbol importado

Primer tramo de la limpieza de lint sobre el código traído en la resincronización:
598 -> 372 errores. Todo es código inerte; no hay cambios de comportamiento.

- Imports sin usar: se quitan los especificadores que nadie referencia y las
  sentencias que quedan vacías.
- Bindings sin usar: propiedades de desestructuración que nadie lee y `const`
  cuyo valor no se consume. Solo se borran cuando el inicializador no encierra
  llamadas; los que sí las tenían se revisaron uno a uno para confirmar que la
  llamada es una consulta pura (useI18n, useQueryClient, getActionState) antes de
  retirarla.
- Casos con criterio: `PaginationControls` declaraba estado de colapso en el
  componente que no lo pinta (lo usa el segundo del fichero); `DataViewShell`
  destructuraba los manejadores de un splitter horizontal que no monta;
  `ParameterCatalogScreen` construía tres catálogos de opciones que ninguna vista
  consume; el test del esquema de auditoría usaba el idioma
  `const { x: _omitted, ...resto }` y ahora omite la clave sin dejar un binding
  muerto.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… switch

Segundo tramo de la limpieza de lint: 372 -> 237 errores.

- `as any` en mocks de test (64 casos) pasa a `as unknown as ReturnType<typeof X>`:
  un mock parcial sigue siendo parcial, pero deja de apagar el comprobador de tipos
  en todo lo que toque ese valor.
- `console.info/warn` (19) pasa al `logger` del repo. El helper de limpieza de
  Playwright conserva `console` con un disable justificado: corre en Node y su
  salida ES el diagnóstico, igual que utils/logger.ts.
- Bloques `catch {}` vacíos (8): se documenta por qué se traga el error. Son dos
  motivos distintos —logout best-effort y sessionStorage que puede lanzar en modo
  privado— frente a los formularios, donde el error ya lo notifica la mutación.
- `switch` de login: los `case` llamaban a `fail()` sin `break`. `fail` devuelve
  `never`, así que la caída era inalcanzable y no había bug, pero solo lo sabía
  quien mirara la firma. Ahora es `return fail(...)`.
- `case` con `const`/`let` sin acotar (6): se cierra el bloque para que no se
  filtren al resto del switch.
- `void mutation.mutate(...)`: el `void` sobraba porque `mutate` ya devuelve void.
  En TenantSignupRequestsPanel sí tapaba una promesa al aire (`mutateAsync`); se
  cambia a `mutate`, la variante fire-and-forget que usan los demás paneles.
- Arreglos con criterio: `setApiHeaderProvider` acepta `null` en su firma —el
  módulo ya lo contemplaba y solo el tipo lo prohibía—; el test de M3Card usa
  `createRef<HTMLDivElement>` en vez de un objeto suelto; se extrae el tipo
  `ViewMode`, que estaba repetido en seis sitios; y el panel de permisos de módulo
  descarta 'thumbnail' con una guarda explicada en vez de forzar el tipo.
- `httpClient`: la cascada de ternarios que sacaba el mensaje de error se convierte
  en `extractErrorMessage`, que recorre las claves por orden de preferencia.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ción

Tercer tramo: 237 -> 198 errores. Las cascadas de ternarios anidados se sustituyen
por tablas con nombre, que es donde ya vivía la decisión, solo que repartida.

- `logger`: el nivel elige el canal de consola. Se resuelve como `console[level]`
  en la llamada, no al importar: capturar la referencia antes dejaba fuera los
  espías que instalan las pruebas (lo comprobó el primer intento, que las rompió).
- `PermissionTemplateDetailPanel` y `NodeDetailPanel`: icono, color, etiqueta y
  frase explicativa de cada estado efectivo pasan a una tabla por estado. Estaban
  en cuatro cascadas por componente, y había que leerlas enteras para saber de qué
  color se pinta un «Partial».
- `SystemSuiteDomainResourcesPanel`: misma idea para agregado / entidad / método de
  dominio. Además, la cadena de ternarios que elegía cuerpo dentro del JSX se
  convierte en un `bodyState` con nombre y cuatro bloques independientes.
- `M3TextField`: la densidad deja de deducirse cuatro veces y se decide una vez
  (`resolveDensity`), con tablas para altura y espaciado. Las matrices
  foco/error de borde y color de etiqueta pasan a funciones con retorno temprano.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -0,0 +1,58 @@
import { test, expect } from '@playwright/test';
import { PaginaDeAcceso } from '../paginas/acceso.pagina';
import { TABLERO, USUARIOS, claveDe, exigirIdentidadConfigurada } from '../fixtures/entorno';
beyondnetPeru and others added 6 commits August 9, 2026 21:25
…ffect y las credenciales dev

198 -> 175 errores. Los dos grupos que no se arreglan reescribiendo código, porque el código
ya dice lo que quiere decir.

- `react-hooks/set-state-in-effect` (17 sitios, 15 ficheros): son patrones deliberados del
  árbol importado —selección inicial cuando la lista llega del servidor, sincronización del
  formulario con la entidad en edición, reinicio al cerrar un diálogo, carga al montar, cierre
  del cajón al pasar a escritorio, y el temporizador de bloqueo del login—. Cada fichero lleva
  su propio motivo, no uno genérico. La regla sigue activa en el resto del repositorio. Mismo
  criterio que el resync anterior (81ed609).

- `sonarjs/no-hardcoded-passwords` (6): son las DEV_PERSONAS que siembra CoreDevDataSeeder
  (FS-25 §4.4) y que el popup «Datos de prueba» publica en pantalla. Ahí la credencial ES el
  dato: moverla a variables de entorno no ocultaría nada —la UI la enseña— y rompería la
  funcionalidad. El disable se acota al array, no al fichero.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… cascadas de ternarios

175 -> 145 errores.

- Nuevo `shared/components/field-state.ts`: la matriz foco/error del borde y del color de
  etiqueta estaba duplicada en M3TextField, M3Select, M3FieldsetWrapper y SearchableSelect,
  cuatro veces la misma cascada. Ahora la regla se lee una vez: el foco manda en el grosor,
  el error manda en el color.
- `IdpPanel`: el botón de activar/desactivar tiene TRES estados, no dos —en modo Local la
  activación está bloqueada—, y eso se colaba como primera rama de dos cascadas distintas
  (tooltip y clases). Ahora hay un `idpToggleState` con nombre y una tabla. El filtro y la
  ordenación pasan a funciones; la cadena del cuerpo, a bloques independientes.
- `ApiErrorBanner`: icono, título y pista se decidían por separado sobre los mismos dos
  booleanos. Ahora hay un `ApiErrorKind` y una tabla por clase de error.
- `TreeNode`: icono y color por estado, misma tabla que ya usa NodeDetailPanel.
- `ProfileForm`: el efecto del permiso y las etiquetas de los selects pasan a funciones con
  retorno temprano (`pickLabel` deja escrito que bloqueado gana sobre cargando, porque sin el
  prerequisito no hay nada que cargar), y el cuerpo del árbol se parte en bloques.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
145 -> 104 errores. `@typescript-eslint/no-explicit-any` queda a cero.

- Decoradores (`RequireOption`, `RequirePermission`): `target` no se usa en el cuerpo, así que
  pasa a `_target: unknown`; los argumentos se reenvían tal cual con `apply`, así que
  `unknown[]` es exactamente lo que se sabe de ellos.
- `catch (err: any)` (5 sitios) pasa a `unknown`, que es lo que un catch debe recibir. El acceso
  al cuerpo del error va por `asApiError`, nuevo en httpClient: no valida nada —el error viene de
  la red y puede ser cualquier cosa— sino que da acceso tipado a los sitios donde tiene sentido
  mirar (`normalised`, `response.data`, `message`).
- Mocks y stubs de test (32): selectores de store, variables de mutación, colecciones y stubs de
  componentes quedan tipados. Un mock parcial sigue siendo parcial, pero deja de apagar el
  comprobador en todo lo que toque.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 -> 82 errores.

- `todo-tag`: tres de los seis eran falsos positivos sobre la palabra española «todo» en prosa
  («por todo el subárbol», «en todo src/»); se reescribe la frase. Los otros tres eran
  marcadores reales en specs de Playwright ya saltadas: el motivo pasa al propio
  `test.skip(true, '…')`, que es donde un lector lo va a buscar.
- `no-clear-text-protocols` (6): las URLs `http://` de security.test SON la entrada bajo prueba
  —se comprueba que se rechacen direcciones privadas y de loopback—; escribirlas en https no
  probaría nada. Disable acotado al fichero con esa razón.
- `use-type-alias` (5): uniones repetidas que ganan nombre (`DateLike`, `ConsoleTab`,
  `DomainResourceType`, `UserCategory`).
- Regex: se quita un escape inútil dentro de una clase de caracteres, `[A-Za-z0-9_]` pasa a `\w`,
  y el patrón de email acota sus cuantificadores (con `+` sin techo, una cadena larga sin arroba
  hace retroceder al motor de forma cuadrática; 254 es el máximo por RFC 5321).
- `pseudo-random`: el token de una corrida e2e pasa a `crypto.randomUUID`.
- `assertions-in-tests`: el test «auto-selects first suite» renderizaba el hook y no afirmaba
  nada, pese a que su nombre prometía la comprobación. Ahora comprueba que la suite
  seleccionada es la primera de la lista sembrada; pasa.
- `AccessGuard`: las dos últimas puertas eran ramas de un if/else con cuerpos idénticos. Las
  cuatro comprobaciones pasan a una conjunción, que se lee de una vez y cortocircuita igual.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… guardas

82 -> 67 errores. La transformación preserva el orden de evaluación —cada rama lleva la
negación de todas las anteriores, que es lo que hacía el ternario— y es segura porque las
ramas son elementos JSX: nunca 0 ni cadena vacía, así que `&&` no puede renderizar un valor
falsy por accidente. Las negaciones invierten el comparador (`length !== 0`) en vez de
envolver en `!(...)`, que es lo que pide sonarjs/no-inverted-boolean-check.

Se aplicó fichero a fichero con `tsc` como puerta: cuatro casos con JSX más enrevesado no
sobrevivieron a la transformación automática y quedaron intactos para tratarlos a mano.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o supo transformar

67 -> 63 errores.

- `M3AuthorizationGraphPanel` y `M3DataView`: cadenas cargando/error/vacío/contenido que pasan a
  bloques con guardas.
- `AppConfigurationDetailPanel`: la cadena vivía dentro de un `content={...}`, que espera UN
  valor y no admite guardas con `&&`. Se extrae a `renderValue()` con retornos tempranos, y de
  paso queda escrito que «cifrado» gana sobre los otros dos estados.
- `SearchableSelect`: la lista desplegable tenía cuatro ramas (cargando, vacío, agrupado, plano).
- `M3DataView` además saca el aspecto de la píldora del splitter a una tabla: arrastrando gana
  sobre plegado, que antes había que deducir del orden de los ternarios.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
beyondnetPeru and others added 7 commits August 9, 2026 22:13
…presiones

63 -> 48 errores. Mismo criterio que en los tramos anteriores: la decisión pasa a una tabla o a
una función con nombre, en vez de deducirse del orden de los ternarios.

- `ConfirmDialog`: clases de botón e icono por variante, con `primary` cayendo en los valores
  por defecto en vez de ser la última rama.
- `ProfileSystemActionsPanel`: icono, color y etiqueta de cada estado de acción.
- `BrandingPanel`: mensaje por estado de verificación DNS.
- `FeatureFlagDetailPanel`: icono por estado de la bandera.
- `DomainResourcesPanel` y `ProfileDomainResourcesPanel`: `nodeTypeLabel` (para un CRUD la
  etiqueta es el verbo; para un método, una marca fija) y `ExpandChevron`, que saca el ternario
  anidado del JSX del árbol.
- `PermissionTemplateForm` y `ParameterDefinitionPickerDialog`: etiquetas por estado, con el
  orden explicado —bloqueado gana sobre cargando porque sin suite no hay roles que traer—.
- `DataList`: `viewMode === 'list' || !renderThumbnail` dice de una vez lo que el ternario
  anidado repartía en dos ramas con el mismo cuerpo.

Validación: tsc sin errores; vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…atro niveles

48 -> 39 errores.

- Bandejas de solicitudes (perfil, alta de inquilino, alta de usuario): el manejador que
  despliega una fila se declaraba dentro del `map`, dejando cinco funciones anidadas
  (render -> map -> onToggle -> actualizador de estado). Sube a un `useCallback` del componente:
  no necesitaba el cierre del map, le bastaba el id.
- `ToastQueue`: los actualizadores del estado de la cola pasan a funciones de módulo
  (`marcarSaliente`, `quitarPorId`). Estaban dentro de un efecto y de un temporizador, y el
  `map`/`filter` de dentro sumaba el quinto nivel.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
39 -> 28 errores. Estas no eran cuestión de estilo.

- `useQueryClient()` dentro de un try/catch en dos hooks: es una llamada CONDICIONAL a un hook
  (rules-of-hooks). El helper de react-query lanza cuando no hay proveedor, y el try/catch quería
  tolerar justo eso —montarse en una prueba sin QueryClientProvider—. Se lee el contexto directo
  con `useContext(QueryClientContext)`, que devuelve undefined sin proveedor y se llama siempre,
  en el mismo orden.
- `useSessionMonitor` leía `Date.now()` durante el render. No lo importa nadie: era código muerto,
  y se elimina en vez de arreglarse.
- `ParameterDefinitionPickerDialog`: el efecto llamaba a `loadParameters` antes de su declaración.
  La declaración sube.
- `LoginScreen`: el bloqueo por intentos fallidos no contaba atrás. El efecto solo corría al
  CAMBIAR `lockoutUntil`, así que escribía «Espere N segundos» una vez, el número se quedaba
  congelado y el desbloqueo dependía de que algo ajeno provocara un render. Ahora hay un intervalo
  de verdad, y `isLocked` sale de estado en vez de leer el reloj en render.

Además se recoloca `renderValue` en `AppConfigurationDetailPanel`: al extraerlo en el tramo
anterior quedó DENTRO del guard `if (!config)`, y la línea que lo usa vive fuera de ese alcance.
Habría sido un ReferenceError en cuanto se abriera el panel. Ni el build ni las pruebas lo
detectaron; ver el gap del typecheck.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
28 -> 16 errores. `sonarjs/no-nested-conditional` queda a cero.

- `PermissionTemplateDetailPanel`: estado efectivo del recurso, píldora de color por tipo de nodo
  y frase explicativa pasan a funciones y tablas. Además `const Icon = getIcon()` creaba un tipo
  de componente nuevo en cada render y React remontaba el subárbol
  (react-hooks/static-components); ahora se indexa una tabla externa. De paso se ve que `getIcon`
  devolvía `Component` en dos de sus tres ramas.
- `ProfileDetailPanel`: `targetLabel` deja escrito que el backend manda '-' o '—' cuando no hay
  nombre, y que para una suite se cae al nombre de la suite cargada.
- `TenantSelect` pasa a usar el `resolveBorderClass` compartido; `EntityRow` saca fondo y borde
  de la fila a una tabla (seleccionada gana sobre interactiva).
- `LoginScreen` y `BranchManager`: cadenas JSX a bloques con guardas.
- `DataGrid`: `cellPaddingClass` explica que la primera columna de una fila hija se sangra para
  marcar jerarquía y que, si no, manda la clase de la columna.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 -> 13 errores.

- `asHttpError` (complejidad 33, la peor del repo) repetía a mano la misma comprobación
  `typeof x === 'string' ? x : undefined` una vez por campo. Se extraen `str`, `num` y
  `asErrorData`, y el caso «no hay response» sale por retorno temprano en vez de anidar.
- La búsqueda de una opción en el grafo de autorización estaba escrita DOS veces —en
  `use-access-resolution` y en el decorador `RequireAccess`— con cuatro bucles anidados cada una.
  Pasa a `authorization/utils/graph-lookup`, que además deja escrito el porqué de la parte que no
  se ve: `menuCode` puede nombrar un menú o directamente un submenú, porque quien declara un
  guard no siempre sabe a qué nivel colgó la opción en la suite.

Validación: vite build OK; vitest 1697/1697 (incluidas las 23 de http-error y las 7 de
use-access-resolution, que cubren estos dos cambios).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 -> 10 errores.

- `graphqlClient`: la construcción de cabeceras y la traducción de una respuesta no-ok a su
  excepción salen a `buildHeaders` y `throwForResponse`. Esta última deja escrito por qué
  502/503/0 se distinguen del resto: significan «el backend no está», no «la petición está mal»,
  y quien los captura arriba enseña otro mensaje.
- `computeNodeState` (en los dos árboles de recursos): el recuento recursivo sale a
  `countEffects` y el caso hoja a `resolveLeafState`. Queda escrito el criterio que antes había
  que deducir: con reglas de un solo signo el nodo hereda ese signo si él mismo la lleva; si la
  traen solo sus hijos, es «parcial» porque no está decidido a su nivel.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 -> 4 errores.

- `auth.service.login`: la traducción de una respuesta fallida al error que ve quien entra sale a
  `throwLoginError`, con los mensajes por código en una tabla. Queda escrito por qué el
  `supportReferenceId` viaja con el error: es lo único que permite cruzar una queja de usuario
  con la traza del servidor.
- `buildQueryString`: los nueve `if (params.x) p.set(...)` idénticos pasan a un recorrido sobre
  la lista de claves opcionales; la paginación sigue aparte porque siempre va, con sus valores
  por defecto.
- `ProfileForm.handleSave`: la cadena de cinco validaciones sale a `primerCampoQueFalta`, y se
  deja escrito que el ORDEN importa —es el del formulario, así que el mensaje señala el primer
  hueco que quien rellena se va a encontrar—.
- `computeNodeState`/`computeEffectiveState` en los árboles de navegación y de permisos: misma
  descomposición que en los de recursos.
- `use-local-overrides`: el memo escribía una ref durante el render. No hacía falta: el dueño del
  snapshot es `patchItem`/`patchItems`, que lo guardan ANTES de crear el override, así que cuando
  el memo ve un patch el original ya está registrado.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
beyondnetPeru and others added 11 commits August 9, 2026 23:18
4 -> 0 errores. El lint del web-app queda limpio: 598 -> 0.

- `M3TextField`: las ocho medidas del campo salían de dos banderas y cada una releía `dense` por
  su cuenta; ahora se resuelven de una vez en `resolveMetrics`. Las clases del <input> y la
  decisión de flotar la etiqueta salen del cuerpo del componente, esta última con el porqué
  escrito: los tipos fecha/hora flotan siempre porque el navegador ya pinta su propio texto y una
  etiqueta centrada se solaparía.
- `M3DataView`: textos y clases del plegado y del arrastre pasan a dos tablas. `zoneClasses` deja
  dicho que sin altura fija el contenido desborda A PROPÓSITO —los desplegables tienen que poder
  salirse— y que mientras se arrastra no se anima porque la transición pelearía con el puntero.
- `ProfileForm.handleSave`: el cuerpo del bucle que sincroniza cada permiso sale a
  `aplicarAjustesLocales`, que además explica por qué se salta lo que no tiene contraparte: sin
  el permiso guardado y sin el ítem de plantilla no hay nada contra qué comparar.
- `PermissionTemplateDetailPanel`: la cabecera pasa a componente propio. Sus condicionales —qué
  botón se ve en cada estado y el paso de confirmación de borrado— eran la mitad de la
  complejidad del panel.

Validación: vite build OK; vitest 1697/1697.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…robado nada

`tsconfig.app.json` y `tsconfig.node.json` declaran `tsBuildInfoFile` sin `incremental`, que
es un ERROR de configuración (TS5111): tsc lo reporta y sale ANTES de comprobar tipos. Un
identificador inexistente pasa sin queja y con salida 0.

Salió a la luz durante la resincronización: un import de `useCallback` omitido en tres
componentes pasó el «typecheck» y lo cazó vitest en tiempo de render; un segundo caso —un
helper extraído dentro del guard `if (!config)` mientras su consumidor vivía fuera— pasó
también el build y habría sido un ReferenceError al abrir el panel.

No es una regresión: la plataforma de origen tiene el mismo tsconfig y, por tanto, los mismos
errores ocultos. Al habilitar `incremental` afloran 545 errores de tipos preexistentes, entre
ellos un desajuste entre el esquema del grafo de autorización (`nodes`) y sus consumidores
(`menus`), que es la ruta que decide lo que un usuario puede ver.

No se arregla en este PR: meter 545 errores dentro de la resincronización los enterraría. El
arreglo es una línea; el trabajo es el backlog que hay detrás.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…an en claro

CodeQL levanta tres alertas de severidad alta en la resincronización. Dos no son accionables
—una clave RSA de 1024 bits generada A PROPÓSITO por un test que comprueba que el SDK la
descarta, y un claim que sí gobierna una decisión de autorización pero sobre un token cuya
firma se verifica antes—. La tercera es real.

`SimulatedNotificationAdapter` es la ÚNICA implementación de `INotificationService` y se
registra sin ninguna guarda de entorno, escribiendo el cuerpo completo de cada notificación en
`LogInformation`. Esos cuerpos llevan enlaces de restablecimiento de contraseña y tokens de
aprobación: en el log son credenciales que funcionan, durante todo el tiempo de retención.

Viene de la plataforma de origen, no de este cambio. Se registra en vez de arreglarse aquí
porque el arreglo completo exige decidir qué pasa fuera de desarrollo, donde hoy no hay
adaptador real que entregue nada.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…la app no arrancaba

Regresión que introduje en 8a9de0c al hacer `static` los 39 resolvers para satisfacer S2325.

HotChocolate descubre los campos de un `[ExtendObjectType]` por reflexión sobre los métodos de
INSTANCIA del tipo. En static son invisibles para esa reflexión: el tipo `Query` se queda sin un
solo campo y el esquema no compila —«The object type `Query` has to at least define one field in
order to be valid»—. Como el esquema se construye al arrancar, la aplicación NO LEVANTA.

Se manifestó en CI de la forma más cara posible: 229 de 316 pruebas de integración en rojo, no
porque fallara ninguna aserción sino porque el host no llegaba a estar en pie. Yo no había
ejecutado esa suite en local, solo Domain, Application y SDK; el hueco era mío.

S2325 deja de gatearse bajo `Ums.Presentation/GraphQL` mediante una sección de .editorconfig que
explica por qué: el analizador tiene razón sobre el lenguaje —los resolvers no usan estado de
instancia— y se equivoca sobre el framework.

Validación: build 0 errores; ContractTest 37/38. El fallo restante
(`VerifyAllPacts_ForUmsWebApp`) es deuda heredada: la plataforma de origen falla exactamente la
misma prueba, con las mismas dos discrepancias (falta `status`/`title` en la respuesta de error y
un 404 donde el pacto espera 400).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…índice de BoundedContext

Cierra los 6 fallos de integración que quedaban. Los dos son consecuencias del rebranding que
no se ven leyendo el diff.

1) `BeyondNetCode.Shell.Factory` NO es equivalente a `Unimar.Shells.Net.Factory`.

   El mapeo de paquetes 1:1 asumió que sí. No lo es: el ensamblado de origen lleva lambdas
   `<AddTransient>b__3_0` / `<AddSingleton>b__2_0` que el de BeyondNetCode no tiene, y que
   registran además el TIPO CONCRETO. Aquí solo quedaba registrada la interfaz, así que en
   cuanto la factoría elegía una rama y pedía la implementación al contenedor, reventaba con
   «No service for type '...Strategy' has been registered» —mapeado a 400 por el manejador
   global—. Se registran los 23 tipos concretos explícitamente, que funciona con cualquiera de
   los dos shells y no depende de un comportamiento no documentado.

   Afectaba a aprobaciones (creación manual y auto-aprobada), reglas de notificación y
   resolución de IdP: cuatro caminos de negocio caídos, no un detalle de pruebas.

2) `UmsSerilogLogger.BoundedContext` leía el 4.º segmento del namespace.

   Al pasar de `Unimar.Ums.Application.X` a `Ums.Application.X` el namespace perdió un
   segmento, así que ese índice devolvía el SIGUIENTE: «Tenant» en vez de «Identity»,
   «FeatureFlag» en vez de «Configuration». El script de rebranding incluso reescribió el
   comentario que lo explicaba, dejándolo coherente en apariencia. Cada traza de la aplicación
   salía etiquetada con el contexto acotado equivocado.

Validación: build 0 errores; IntegrationTest 314/316 (2 omitidas), antes 229 en rojo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ación en ejecución

Al levantar la API contra una base PostgreSQL limpia para comprobar el funcionamiento real —no
solo que compila— aparecieron dos defectos heredados que ningún pipeline toca:

TD-006: `UpdatePostgresMassTransitOutbox` usa `gen_random_bytes` (pgcrypto) y la migración que
declara la extensión va DESPUÉS. Sobre una base vacía el migrador aborta con 42883 y la API no
llega a arrancar. No se nota porque los entornos existentes ya tienen la extensión y las suites
usan el proveedor InMemory: ningún pipeline migra jamás un PostgreSQL vacío.

TD-007: la cadena de conexión de desarrollo apunta al puerto 5433 y el compose publica el 5432;
las contraseñas tampoco coinciden. El camino documentado «levanta el stack local y arranca la
API» no funciona tal como está escrito.

Ambos son idénticos en la plataforma de origen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o en inglés

Ambos ADR se citaban desde más de quince documentos como decisiones aceptadas y no existían
como ficheros —tampoco en la plataforma de origen—, así que esas citas no tenían destino. Se
redactan a partir del código que ya los implementa y de las propias citas, en versión bilingüe,
y se registran en los dos índices.

- **ADR-0090** (árbol recursivo `MenuNode`): documenta por qué se retiró la cadena rígida de
  cuatro niveles —un submenú obligatorio que había que inventar, funcionalidad↔opción 1:1 sin
  clave ajena, cero metadatos de gobernanza por nodo, y tres entidades para un solo concepto
  recursivo— y qué se paga a cambio: el árbol se reconstruye en memoria al cargar, y la
  recursión admite ciclos que una cadena fija no permitía.
- **ADR-0164** (cierre terminal de sucursal): documenta las tres cosas que estaban mal en
  `RemoveBranch` —huérfanos silenciosos porque `Profiles.BranchId` y `UserAccounts.BranchId` no
  tienen clave ajena, un pasado que dejaba de ser explicable, y un código de sucursal liberado
  que volvía ambigua cualquier consulta histórica— y lo que cuesta: la colección crece de forma
  monótona y las lecturas tienen que acordarse de filtrar `!IsClosed`.

Además se corrige el espejo inglés de `domain/`, que seguía describiendo como vigente lo que ya
no existe: `Menu`/`SubMenu`/`Option` en los índices y en las reglas de consistencia,
`RemoveBranch` en el agregado Tenant y en el registro de broken rules, y
`RemoveTemplateItemCommand` en las plantillas de permisos —donde además el nombre documentado
del sustituto estaba mal: es `DeactivateTemplateItemCommand`, no `DeactivateItemCommand`—. La
nota sobre el modo de consulta de EF Core deja de justificarse por SQLite, que ya no se usa.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l servidor ya no envía

`hasMenuAccess`, `hasOptionAccess` y el decorador `RequireAccess` recorrían
`menuAccess[].menus[].subMenus[].options[]`. El contrato v2.0.0 (`@ums/sdk-contracts`,
`MenuModule.nodes`; y su gemelo .NET `AuthorizationGraph.cs`) es un árbol RECURSIVO de
`NavigationNode` con `children` y `actions`, y `MenuModule` NO tiene `menus`. Contra un grafo
real esas lecturas daban `undefined`: los guards de menú y de opción no concedían ni denegaban,
fallaban.

La prueba que cubría esto no lo detectaba porque su fixture usaba la MISMA forma retirada que el
código. Confirmaba el error en vez de encontrarlo. Ahora el fixture es el contrato real, con
`nodes`/`children`/`actions`.

Se preserva la semántica que tenía el modelo retirado: `optionCode` casa contra el código del
nodo O contra el de una de sus acciones —quien declara un guard nombra «la opción», y en la
suite eso podía ser cualquiera de las dos—, y el primer parámetro acota a un subárbol, que ahora
puede ser cualquier ancestro porque la profundidad dejó de ser fija.

Es deuda heredada: la plataforma de origen lee `mod.menus` igual. Es exactamente el desajuste
que TD-004 anticipaba —«el esquema del grafo (`nodes`) y sus consumidores (`menus`) han
derivado»— y que el typecheck apagado no podía señalar.

Validación: lint 0 errores; vite build OK; vitest 1697/1697 en tres corridas seguidas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rbol recursivo

La documentación describía `menuAccess[].menus[].subMenus[].options[]` con un efecto por opción.
El contrato real —v2.0.0, ADR-0090, en los dos SDK— es `menuAccess[].nodes[]`: un árbol
recursivo de `NavigationNode` con `children` y una lista de `actions` por nodo. `MenuModule` no
tiene `menus`.

Es la misma forma retirada que leía el código del web-app (arreglada en el commit anterior), así
que la documentación llevaba a escribir consumidores rotos.

Se corrigen, en versión bilingüe: el esquema del SDK de contratos, el README del SDK .NET, y los
diagramas de árbol y los ejemplos JSON de la ficha `auth-graph` del dominio. Queda escrito lo que
antes había que descubrir leyendo el emisor: que `kind` clasifica el papel del nodo sin fijar su
profundidad, que hay que recorrer `children` hasta agotarlo, y que solo viaja lo alcanzable —la
ausencia significa «no concedido», no «error»—.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ta lo nuevo

- **Persistencia**: la referencia aplicada del API .NET decía «baseline SQL Server y soporte
  SQLite local»; es PostgreSQL como único proveedor relacional (ADR-0082), con el esquema
  aplicado por migraciones de EF. El patrón que `shell-libraries/bootstrapper` recomendaba
  —encadenar `SqlServerSchemaBootstrapper`— está retirado con los propios bootstrappers; se
  sustituye por lo que UMS hace de verdad (`Database.MigrateAsync`) y se deja dicho dónde el
  patrón Bootstrapper sí se gana su sitio: en la composición de servicios, no en el esquema.
- **`governance/architecture{,-es}/persistence-phase1/`** (18 ficheros) se marca como histórico.
  Es el análisis de una fase cerrada, escrito cuando SQL Server era la línea base supuesta;
  vale por el razonamiento, no como estado actual.
- **Transporte de consultas**: `FRONTEND_CONFIG_TRANSPORT` se marca retirado en el catálogo de
  parámetros, y TD-002 se reescribe: su *workaround* era precisamente ese flag, que ya no
  existe, así que la deuda pasa de «tiene solución conocida» a «hay que volver a medirla» —y se
  dice que si ya no reproduce, lo correcto es cerrarla, porque arrastrar un fantasma es peor que
  no arrastrar nada—. Las notas que llamaban «transitorio» al REST-only dejan de hacerlo.
- **`PromotionStatus` → `RolePromotionStatus`** en las fichas IGA y en el contexto DDD.
- **Artefactos nuevos documentados** en la referencia React aplicada, biling��e: `field-state`,
  `graph-lookup` y `asApiError`/`NormalisedApiError`, cada uno con la razón por la que existe.
- El árbol de ficheros de `overview.es.md` listaba `menu.md`, `sub-menu.md` y `option.md`, que no
  existen; ahora lista `menu-node.md`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Último tramo de la puesta al día. Los documentos ER físicos y el modelo conceptual no se
reescriben a medias: llevan un aviso preciso de qué parte quedó superada —la cadena
`FUNCTIONAL_MENU`/`FUNCTIONAL_SUBMENU`/`FUNCTIONAL_OPTION`, que hoy es una sola tabla recursiva
`SystemSuiteNodes` más el puente `SystemSuiteNodeActions`— y dicen dónde está la fuente de
verdad. El resto de esos documentos sigue siendo válido, y un aviso honesto vale más que un
diagrama reescrito a medias que vuelva a envejecer mal.

Se corrige la prosa que sí era corta y clara de arreglar: el índice DDD, el PRD, el árbol del
grafo en ADR-0071, la historia funcional FS-04 y los trackers de agregados, en las dos lenguas
donde existe el gemelo.

En el análisis de integración con el Tablero se aclara la equivalencia en vez de tachar el
recuento: la spec declarativa sigue escrita con el vocabulario menú/submenú/opción y el cargador
la traduce a altas de `MenuNode` sobre `/modules/{id}/nodes`, así que el número es correcto y lo
que faltaba era decir en qué se convierte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@beyondnetPeru
beyondnetPeru merged commit c011e42 into main Aug 10, 2026
22 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants