fix: saldar la deuda técnica registrada (TD-004 a TD-007) - #15
Merged
Conversation
**TD-005 — cuerpos de notificación en claro.** `SimulatedNotificationAdapter` era la única implementación de `INotificationService` y se registraba sin guarda de entorno, escribiendo el cuerpo completo en `LogInformation`. Esos cuerpos llevan enlaces de restablecimiento de contraseña y tokens de aprobación: en el log son credenciales que funcionan, y durante todo el tiempo de retención, que sobrevive de largo al token. Ahora el simulador solo se registra fuera de producción, y ya no escribe el cuerpo —canal, destinatario y asunto bastan para seguir un flujo; la línea informa la longitud, no el contenido—. Fuera de desarrollo se registra `UnconfiguredNotificationAdapter`, que **falla ruidosamente** en vez de callar: un no-op silencioso daría por enviada una invitación que nadie recibe, y eso solo se descubre cuando el usuario reclama. Sigue sin haber entrega real; ese tipo es precisamente la documentación de que falta. **TD-006 — una base PostgreSQL limpia no migraba.** `UpdatePostgresMassTransitOutbox` usa `gen_random_bytes` y la migración que declara pgcrypto va después, así que sobre una base vacía el migrador abortaba con 42883 y la API no arrancaba. Se añade un `CREATE EXTENSION IF NOT EXISTS pgcrypto;` idempotente al principio de esa migración, en vez de reordenar: reordenar reescribiría un historial ya aplicado en entornos vivos. Verificado de la única forma que vale: una base virgen (solo `plpgsql`) migra las **32** migraciones, la API arranca y el login contra los datos sembrados funciona. **TD-007 — el entorno local no coincidía consigo mismo.** La cadena de desarrollo apuntaba al 5433 y el compose publica el 5432; las contraseñas tampoco coincidían. Ahora concuerdan, con el compose como fuente de verdad porque es lo que ya usan CI y el camino de contenedor. Validación: build 0 errores; IntegrationTest 314/316. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…poner los tipos del barril TD-004, primer tramo: 538 -> 346 errores de tipo. 130 claves de traducción se citaban desde los componentes y no existían en ningún diccionario. 61 pintaban `undefined` en pantalla; el resto se salvaba con un `?? '...'` en el sitio de uso, que es la razón de que nadie lo notara. 26 existían antes de la resincronización y se recuperan del historial con su copia original (4a8b940); las 104 restantes nunca existieron —tampoco en la plataforma de origen— y su texto sale del valor por defecto que el propio componente traía, o de su contexto de uso. El barril de componentes reexportaba nueve interfaces de props declaradas locales, más `EntityCardProps` y `RouteLoaderProps`, que no existen: EntityCard tiene dos modos (Simple y Generic) y RouteLoader no lleva props. `TreeNode` se reexportaba desde HierarchicalList, que solo lo importa; ahora sale de su origen. `PrefetchEntry.queryKey` estaba declarado `string[]` y react-query usa `readonly unknown[]`. Las claves de ese fichero llevan números y `undefined`, y han de coincidir literalmente con las que arma cada pantalla: con el tipo estrecho, el arreglo natural es convertir a texto, y entonces el prefetch calienta una entrada de caché que la pantalla nunca lee. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ps que los componentes ignoraban TD-004, segundo tramo: 346 -> 313 errores de tipo. Cuatro identificadores se usaban sin existir en el ámbito. No son avisos de tipos: son excepciones en cuanto se ejecuta esa línea. - M3DataView referenciaba ChevronsUp/ChevronsDown sin importarlos de lucide-react: el botón de plegar la cabecera rompe el render en cualquiera de sus dos ramas. - ProfileForm llamaba `logger.error` en su `catch` sin importar el logger, así que el manejador de error moría antes de llamar a `setError` y la pantalla se quedaba muda justo cuando había algo que contar. - ProfileDomainResourcesPanel usaba `itemEffect` y `SystemSuiteCrudOperation` sin importarlos; DomainResourcesPanel, lo segundo. - Las pruebas usaban `global`, que es de Node y no está declarado en un tsconfig de navegador; `globalThis` vale en ambos. Props declaradas de menos, que se descartaban en silencio: - M3DialogAction no admitía `disabled` ni `loading` y el render no los reenviaba a M3Button, que sí los soporta. Las pantallas ya los pasaban: el confirmar seguía activo mientras se enviaba y aceptaba un segundo clic. - IconButton heredaba `size` de ButtonHTMLAttributes, donde no existe (es de input/select), así que los 17 `size="sm"` acababan en el DOM como atributo inválido y el botón conservaba su tamaño normal. - StatusBadge no hace spread de props: su `size` se perdía sin dejar rastro. QueryStateApi y PaginationStateApi sustituyen a los `ReturnType<typeof …>` que cada panel escribía por su cuenta. Chocaban (TS2719) porque el hook infiere el filtro estrecho y el panel lo declaraba `string`. `activeFilter` se lee estrecho y `setActiveFilter` se escribe ancho: la asimetría es lo que hace la asignación válida. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…activo TD-004, tercer tramo: 313 -> 255 errores de tipo. Nueve genéricos exigían `T extends Record<string, unknown>` y ninguno usa la firma de índice: todos se limitan a `keyof T` y `T[K]`. La restricción rechazaba cualquier interfaz —TS no les da firma de índice implícita, solo a los alias de tipo—, así que DataGrid, HierarchicalList, useInlineEdit, useTreeNodes y useLocalOverrides no aceptaban los propios modelos del dominio. `object` es lo que de verdad necesitan. `Translations` estaba definido como `typeof translations.es`. Con `as const` cada idioma tiene sus propios tipos literales, así que el diccionario inglés no era asignable y toda función que recibiera `t` rechazaba la mitad de las llamadas. Es la unión de ambos idiomas: quien recibe `t` lo pinta, y 'Categoría' | 'Category' se pinta igual de bien. De ahí salía el `Record<string, string>` de tenant-list-renders, que además era falso: el diccionario tiene entradas que son funciones. Y el panel pasaba `t` a los dos renderizadores de fila, que ni lo declaran ni lo usan —solo pintan datos—, así que JavaScript lo descartaba en cada render. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…, flags, suites)
TD-004, cuarto tramo: 255 -> 227 errores de tipo.
Las pruebas de plantillas de permisos y de perfiles construían sus datos con
`code`, `name`, `status` y `createdAt`, y afirmaban sobre `.name`. Ninguno de los
cuatro campos existe en el contrato: una plantilla se identifica por rol y suite y
lleva `version`; un perfil es la terna inquilino-usuario-rol dentro de una suite. Las
pruebas pasaban porque el mock devolvía exactamente la forma inventada, así que
confirmaban la invención en vez de comprobar el contrato. Es la misma trampa que la
deriva `menus`/`nodes` del grafo de autorización.
Los payloads de creación iban por el mismo camino: `create` recibe
{tenantId, roleId, systemSuiteId}, no {code, name}; y `addItem` recibe el arco
exclusivo completo (destino, acción y el par permitir/denegar).
Los mocks compartidos omitían campos obligatorios: los flags, el código y el nombre
de su suite; los recursos de dominio, sus operaciones CRUD y sus acciones propias
—que es justo lo que pintan los paneles del árbol—. El servidor simulado servía
formas que el esquema real rechazaría.
El mock del store de sesión declaraba `selector?: never`, lo que hacía inllamable al
propio selector que la prueba le pasa.
Verificado: vitest 1697/1697, build OK, eslint 0 errores.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… de menos TD-004, quinto tramo: 227 -> 201 errores de tipo. `ListToolbar` invoca `onSearchSubmit()` sin argumento —ya hizo `preventDefault` por su cuenta— y los paneles le pasan `handleQuerySubmit`, que empieza con `e.preventDefault()`. Pulsar Enter en el buscador lanzaba «Cannot read properties of undefined». El evento pasa a ser opcional, que es lo que ya exigían sus dos formas de uso: `onSubmit` de un formulario y callback pelado. Ocho paneles pasaban `footerElement` a DataList, que su prop llama `telemetryInfo`: el pie de telemetría no se pintaba en ninguno de los ocho. ApiErrorBanner descartaba el `className` del llamante, así que sus márgenes eran siempre los de dentro. DataViewShell exigía `title` y varios paneles no tienen uno propio —la cuenta y la etiqueta las lleva ListToolbar—, de modo que pintaba un `<h2>` vacío; ahora se omite la barra si no hay título. ConfigValueDisplay recortaba con constantes fijas de 30 y 40 caracteres e ignoraba el `truncateAt` que ya le pasaban. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…alse TD-004, sexto tramo: 201 -> 184 errores de tipo. `M3Switch.onChange` entrega el valor como PRIMER argumento y SmartConfigInput leía el segundo: `checked` era siempre `undefined`, así que el interruptor de un parámetro booleano escribía 'false' estuviera donde estuviera. No lo veía nadie porque el componente devuelve el valor por la prop `checked`, que sí llegaba bien: la pantalla se pintaba correcta y lo que se guardaba no. Convivían tres vocabularios de tamaño: 'xs' (36 sitios), 'sm' (19), 'small' (7), 'md' (1) y 'large' (1). Como los componentes no declaraban `size`, los de la segunda familia se descartaban sin ruido. Se estandariza en el de la casa —'xs' | 'sm' | 'md', el que ya usaba CodeBadge— y se reescriben los ocho sitios sueltos. Spinner nunca tuvo `size`: se dimensiona por className. Y M3TextField hereda el `size` de <input>, que es el ancho EN CARACTERES: 'sm' allí no significaba nada. El usuario sustituto de ProfileScreen (sesión ausente) omitía `id` y `profileId`, que el panel de diagnóstico pinta: salían vacíos justo en el modo en que sirven para depurar. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TD-004, séptimo tramo: 184 -> 172 errores de tipo. `userOptions` leía `u.userId` y `u.userName`, y `UserAccount` tiene `userAccountId` y `displayName`: cada opción del desplegable de usuarios se pintaba «undefined (correo)» y llevaba `undefined` como valor. Crear un perfil desde esa pantalla mandaba un `userId` vacío. `FieldSelect` no declaraba `disabled` y cuatro sitios se lo pasaban, así que los selectores de usuario, rol y suite quedaban activos aunque faltara su prerequisito: se podía elegir rol antes que suite y enviar la combinación. `resolvePermissionEffect` devolvía `string` para un valor que va al servidor y solo admite allow/deny/neutral. `ProfileModulePermissionsPanel` recibía `onNodeSelect` y `selectedNodeId`, que no existen en su interfaz y no hacen nada: el panel no tiene noción de selección. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…módulo entero TD-004, octavo tramo: 172 -> 155 errores de tipo. `@infrastructure` estaba aliasado en vite.config.ts pero no en el tsconfig, y un módulo lo usaba: para el empaquetador resolvía y para el comprobador no, así que ese fichero y su cadena de consumidores jamás se comprobaron. Al normalizarlo a `@infra` —el alias de la casa— aparecieron los errores que llevaba tapando. Detrás había tres fallos vivos: - Zod 4 renombró `ZodError.errors` a `.issues`. Dos sitios iteraban `undefined`, así que el formulario del catálogo de parámetros y la validación compartida lanzaban TypeError justo al fallar la validación, que es cuando tienen que informar. - El catálogo leía `pageData.totalPages`, que ese endpoint no devuelve: siempre valía 1 y la lista se quedaba en una sola página hubiera las que hubiera. Ahora se deriva de `totalItems` y el tamaño de página. - `asApiError` se usaba sin importar en app-configuration.service: el `catch` moría con ReferenceError antes de registrar nada. Además, `securityInterceptor` se reexportaba un tipo a sí mismo, `useRef` sin valor inicial (React 19 lo exige) y `setSortBy` repetía la asimetría de varianza que ya tenía `setActiveFilter`: ancho al escribir, estrecho al leer. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TD-004, noveno tramo: 155 -> 138 errores de tipo.
`computeNodeState` y `countEffects` se copiaron del panel de plantillas sin adaptar el
nombre del campo: leían `node.items`, y el nodo del árbol de perfiles guarda
`permissions`. `undefined.map` en la primera llamada.
M3Button hereda `size` de ButtonHTMLAttributes, donde no existe: los 16 sitios que
pasaban `sm` o `xs` lo mandaban al DOM como atributo inválido y el botón conservaba su
altura de 40 dp. SectionHeader recibía `badge` sin declararlo, así que la insignia de
estado no se pintaba.
`useFormValidation` indexaba a ciegas tres estructuras que TypeScript ve como `{}`:
`flatten().fieldErrors` (Zod no infiere las claves desde un ZodTypeAny genérico),
`schema.shape` (solo existe en ZodObject) y las claves de `useFormDirty`.
DetailPanelShell exige `isLoading` e `isEmpty` porque son los que deciden si pinta su
propio esqueleto; tres pantallas pintan el suyo como hijo y los omitían.
Verificado: vitest 1697/1697, build OK, eslint 0 errores.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gia por defecto
TD-004, décimo tramo: 138 -> 122 errores de tipo.
`IdpPanel` arrancaba con `provStrategy = 'OIDC'`, y el backend valida el nombre EXACTO
del enum: la lista es 'GenericOidc', 'Saml2', 'AzureAd'… Quien no tocara el desplegable
recibía «Invalid identity provider strategy». Lo advierte el propio comentario de
idp.constants (G-155); lo que faltaba era que el comprobador lo hiciera cumplir.
`ErrorBoundary` pintaba `{t.errorSupportReference}` sin llamarla: es una función del
diccionario, y React rechaza una función como hijo. La referencia de soporte —lo único
accionable de esa pantalla— no salía nunca.
Tres hooks devolvían miembros que su propia firma no declaraba (`blockReason`,
`handleRestoreRequest`, `requiresFilter`), así que las pantallas que los leen veían
`undefined`. Y `ConsoleTab` no incluía 'branding': la pestaña de identidad visual se
reenganchó al panel pero no al tipo que enumera las pestañas.
`_retry`, la marca propia sobre la configuración de axios que evita el doble reintento,
ahora se declara en vez de colarse.
Verificado: vitest 1697/1697, build OK, eslint 0 errores.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TD-004, undécimo tramo: 122 -> 111 errores de tipo (23 en producción). `useQueryState` solo podía inferir `'all'` como tipo del filtro, así que tras la guarda `activeFilter !== 'all'` TypeScript lo estrechaba a `never` y la comparación siguiente —`d.status === activeFilter`— quedaba muerta. Anotando el filtro como `'all' | DelegationStatus` la comparación vuelve a tener sentido. `GraphNodeAction.effect` estaba declarado `string` en el lector del grafo, y el contrato lo define como el enum `AccessEffect`: `evaluateEffect` recibía cualquier cadena sin que nadie la comprobara. Es el mismo fichero que corrige la deriva `menus`/`nodes`, y el tipo suelto era el resto de aquella herida. TenantSelect ignoraba `disabled` en tres formularios; el interruptor de solo lectura de ConfigValueDisplay no pasaba manejador; `PaginationData.pageSize` exigía una opción del selector (10/25/50) y las bandejas fijan 6, que es legítimo; y el barril exportaba `SpinnerProps`, que no existe, mientras omitía `DetailTab`, que sí. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El commit anterior añadió la prop a la interfaz sin cablearla al render, que es exactamente el defecto que venía a corregir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TD-004, duodécimo tramo: 111 -> 95 errores de tipo (7 en producción). `NAV_PREFETCH_CONFIG.delegations` invocaba `delegation.service.getAll`, que no existe: las delegaciones se consultan por administrador delegante o delegado y este fichero no tiene a quién. Pasar el ratón por ese ítem de navegación lanzaba TypeError. La entrada se retira con la razón escrita en su lugar. La del catálogo de parámetros calentaba 'parameter-catalogs' con ocho elementos de paginación, y ese endpoint no pagina ni usa esa clave: se alinea con ['parameter-definitions', filter], que es la que lee la pantalla. `UserAccountPasswordPanel` pasaba `isLoading` a M3Button, que expone `loading`: el botón de generar contraseña temporal no giraba y seguía pulsable durante la mutación. `ConnectedUserDrawer` pasaba `position` y `width`, y M3Drawer no tiene la primera —siempre abre a la derecha— y llama `maxWidth` a la segunda. `AddButtonInline` declaraba `onClick: () => void` y su único uso necesita el evento para frenar la propagación dentro de una cabecera plegable. `DelegationForm.handleSubmit` exigía evento y el diálogo lo llama sin él. Y `UserAccountProfileCard` mantenía una copia local de `UserCategory`: si el enum del dominio crece, la tarjeta se queda atrás sin que nadie lo note. Verificado: vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ue el servidor rechaza TD-004: código de PRODUCCIÓN a cero errores de tipo (367 al empezar). `permission-cascade` mapeaba el submenú a `'Page'`, y ExclusiveArcTarget (Ums.Domain/Enums/ExclusiveArcTarget.cs) solo tiene SystemSuite, Module, Submodule, Option, Aggregate y Entity. AddTemplateItemCommandValidator valida el nombre contra ese enum, así que habilitar la jerarquía superior de una opción bajo un submenú respondía 400. Menú y submenú van ahora los dos a 'Submodule': el árbol recursivo de MenuNode (ADR-0090) ya no fija cuántos niveles hay, y distinguirlos no aportaba nada que el servidor supiera leer. La jerarquía de recursos de dominio (Agregado → Entidad → DomainMethod) estaba a medias: `HierarchyType` y el payload de `useAddDomainResource` se quedaron en Aggregate|Entity, y ese último tampoco admitía `parentResourceId`, que es justo lo que el formulario pide para un método de dominio. Además: los iconos de lucide no aceptan `title` —el tooltip de estado del árbol no salía—, `rowVersion` es anulable en el esquema y obligatorio en el mutador, y el tipo de `SystemSuiteNode` declaraba `metadata` solo anulable cuando el esquema la hace también opcional, que era lo que impedía casar el ZodLazy con su anotación. Verificado: vitest 1697/1697, build OK, eslint 0 errores, tsc 0 errores en producción (quedan 88 en ficheros de prueba). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… contrato no tiene TD-004: 88 -> 64 errores de tipo, todos ya en ficheros de prueba. Las pruebas del store de sesión creaban usuarios sin `authorizationGraph`, que es obligatorio en AuthUser: un usuario así no es uno que el login pudiera producir. Las de feature flags construían el flag con code/name/description/sortOrder y `flagType: 'boolean'` en minúscula. El contrato es flagCode/flagType/flagTargets/status más el código y el nombre de su suite, y el servidor compara el tipo con el nombre del enum, así que la minúscula sería un 400. Los dobles de UserAccountListPanel se habían quedado cortos frente a QueryStateApi y PaginationStateApi —faltaban seis miembros—, y sus inquilinos y cuentas omitían `isManagementOwner`, `displayName` y `hasActivePassword`. Un doble incompleto prueba una pantalla que no existe: ahora se declaran con el tipo, que es lo que impide que vuelvan a quedarse atrás. Verificado: vitest 1697/1697, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lta no devuelve
TD-004: 64 -> 54 errores de tipo.
Diez pruebas simulaban la respuesta con `getDelegationById`, `getTenantBranding`,
`getFeatureFlags`… y afirmaban sobre esa misma clave. Las consultas reales devuelven el
campo sin el prefijo (`delegationById`, `tenantBranding`, `featureFlags`), que es el
nombre del resolver en el esquema. Pasaban porque el doble devuelve exactamente la clave
que se le da: si producción leyera la clave equivocada, estas pruebas seguirían verdes.
Es la misma trampa que el fixture del grafo de autorización.
`permission-template.graphql` declaraba sus dos respuestas como `{ campo: unknown }`, así
que nada comprobaba la forma y la prueba podía leer `.templateId` de un `unknown`. Ahora
se declaran los DTO que la propia consulta pide campo por campo.
Verificado: 51/51 en las nueve pruebas de consultas GraphQL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TD-004: 54 -> 26 errores de tipo. Los tipos de estado de los stores de notificaciones e i18n no se exportaban, así que cada prueba tipaba su selector como `never` —lo que vuelve inllamable el propio selector que le pasa— y lo salvaba con castings sueltos. Ahora se exportan y el doble declara qué está simulando. `TopAppBar.test` mantenía una copia local del tipo de marca sin `shortName`, justo el campo que una de sus pruebas comprueba. Se usa `SystemSettings['brand']`. Fixtures alineados con su contrato: inquilinos (faltaban `type`, `parentTenantId`, `companyReference`, `isManagementOwner` y sobraba `createdAt`; el payload de creación exige `type`), cuentas (branchId, displayName, identityReference, hasActivePassword, passwordUpdatedAtUtc) y suites (tenantId, description y las tres colecciones). Verificado: vitest 1697/1697, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
538 -> 0. `npm run typecheck` existe y CI lo ejecuta como paso propio, antes de las pruebas: un error de tipo es más barato de leer que el fallo en cascada que provoca. Hasta aquí, `tsBuildInfoFile` sin `incremental` hacía que tsc abortara con TS5111 antes de comprobar nada, y la única verificación de tipos era la que hace Vite al construir, que solo mira lo que entra en el bundle. Lo que la puerta llevaba tapando —además de las 538 anotaciones— son quince defectos vivos, cada uno con su commit en esta rama: el interruptor booleano que siempre guardaba `false`, el Enter del buscador que reventaba, la cascada de permisos que devolvía 400 por un `targetType` inexistente, el alta de proveedor de identidad que devolvía 400 con su valor por defecto, el árbol de recursos que no llegaba a pintar, y diez pruebas de GraphQL que afirmaban sobre claves que ningún resolver devuelve. `tsconfig.node.json` destapó de paso que hay dos vite en el árbol: 6.4.3 fijado por la app —el que construye— y 8.1.5 izado a la raíz por Storybook y vitest. Los tipos del plugin de React resolvían al izado. Se mapea `vite` a la copia de la app, que es la que corre, y la duplicación queda registrada como TD-008: converger es un salto de major del empaquetador y merece su propio cambio. Verificado: typecheck 0, vitest 1697/1697, build OK, eslint 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| _logger.LogError( | ||
| "[NOTIFICACIÓN NO ENTREGADA] Canal={Channel} | Para={Recipient} | Asunto={Subject}", | ||
| notification.Channel, | ||
| notification.Recipient, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resuelve los cuatro puntos de deuda que quedaron registrados al cerrar el PR #14.
TD-004 — El typecheck del front nunca había corrido
tsBuildInfoFilesinincrementalhacía quetscabortara con TS5111 antes de comprobar nada: el proyecto creía tener un typecheck y no lo tenía. La única verificación de tipos era la que hace Vite al construir, que solo mira lo que entra en el bundle — ni las pruebas, ni el código no alcanzado.Encendida la puerta aparecieron 538 errores (367 de producción, 171 de pruebas). Están los 538 cerrados, existe
npm run typechecky CI lo ejecuta como paso propio antes de las pruebas.Lo que la puerta llevaba tapando no eran anotaciones. Quince defectos vivos, cada uno con su commit:
SmartConfigInputleía el segundo argumento deM3Switch.onChangefalse, marcara lo que marcara el interruptorpermission-cascademapeaba submenú a'Page'ExclusiveArcTargetno tienePage: cascadear un permiso por un submenú devolvía 400ListToolbarllamabaonSearchSubmit()sin eventoCannot read properties of undefinedIdpPanelarrancaba conprovStrategy = 'OIDC'ProfileFormleíau.userId/u.userNameundefinedy enviabaundefinedProfileDomainResourcesPanelleíanode.itemspermissions: el árbol reventaba al primer renderM3DataViewreferenciabaChevronsUp/ChevronsDownsin importarProfileFormllamabalogger.errorsin importarsetError: pantalla muda justo cuando había algo que contarZodError.errorsa.issuesundefinedal fallar la validaciónNAV_PREFETCH_CONFIG.delegationsllamabagetAllfooterElementaDataListtelemetryInfo: el pie de telemetría no se pintaba en ningunoM3DialogActionno declarabadisabledniloadingErrorBoundarypintabat.errorSupportReferencesin llamarla@infrastructurealiasado en Vite pero no en tsconfiggetXxxxxx; el doble devolvía la clave que le dieran, así que no comprobaban nadaEsa última fila es la misma trampa que la deriva
menus/nodesdel grafo de autorización: una prueba cuyo doble reproduce el error lo confirma en vez de encontrarlo. Los fixtures de plantillas, perfiles, inquilinos, cuentas, suites y flags estaban igual —describían formas que el contrato no tiene— y quedan alineados con su esquema.TD-005 — Cuerpos de notificación en claro
El adaptador simulado registraba el cuerpo completo. Llevan enlaces de restablecimiento y tokens de aprobación: en el log son credenciales que funcionan durante todo el tiempo de retención. Ahora registra canal, destinatario, asunto y longitud. Fuera de desarrollo se registra
UnconfiguredNotificationAdapter, que falla ruidosamente en vez de dar por enviado lo que nadie recibirá.TD-006 — Migrar una base limpia fallaba
La migración del outbox de MassTransit usa
gen_random_uuid()sin crearpgcrypto. Se añadeCREATE EXTENSION IF NOT EXISTS pgcryptoal inicio de suUp.TD-007 — La cadena de conexión de desarrollo
Apuntaba a un puerto que el compose no publica. Alineados.
TD-008 — Registrado, no resuelto
El typecheck destapó que hay dos vite en el árbol: 6.4.3 fijado por la app —el que construye— y 8.1.5 izado a la raíz por Storybook y vitest. Los tipos del plugin de React resolvían al izado.
tsconfig.node.jsonmapeavitea la copia de la app, que es la que corre; converger en un major es un salto del empaquetador y merece su propio cambio, su propia verificación de build y su propia vuelta atrás.Verificación
npm run typecheck— 0 errores (538 al empezar)vitest— 1697/1697 en 182 ficherosvite build— OKeslint— 0 erroresdotnet build Ums.sln— 0 errores🤖 Generated with Claude Code