/** * 🔴 **El contrato del diálogo de acceso, y por qué está diseñado POR CAPACIDADES.** * * El kit no sabe —ni debe saber— cómo decide sus permisos el host. Su contrato de lectura * (`WidgetDefinition.permissions`) recibe una *decisión ya tomada*: cinco verbos. Este contrato es la * otra mitad, la de escritura, y tiene el mismo problema al revés: para pintar un formulario hay que * saber qué campos existen. * * La respuesta no es asumir el modelo de un host. Es que **el host declare qué soporta** y el diálogo * pinte solo eso: * * - sin `searchPeople`, el sujeto se teclea en vez de buscarse; * - sin `getPolicy`/`savePolicy`, la pestaña de visibilidad no existe; * - sin `expiry`, no se pinta el vencimiento. * * Un host que solo reparta lectura a personas nombradas obtiene un diálogo de una pestaña y tres * controles, y no ve ni una casilla de algo que su backend no implementa. Añadir una capacidad * mañana es **aditivo**: no rompe a quien no la declare. * * ⚠️ **El kit no habla HTTP.** Todas las operaciones son funciones que provee el host. Quien las * implementa contra su API vive en el host, igual que `IWidgetAccessPolicy` en el backend del * componente: el modelo de acceso es del host, el formulario es del kit. */ import type { DashboardCategoryOption } from '../components/WidgetBuilder/categories'; /** A quién se le concede. `user` es el mínimo; los otros dos solo si el host los declara. */ export type WidgetGrantSubjectType = 'user' | 'permission' | 'module'; /** Los niveles, de menor a mayor. Un host puede declarar un subconjunto. */ export type WidgetGrantLevel = 'viewer' | 'editor' | 'owner'; /** Los estados de visibilidad que el diálogo sabe pintar. */ export type WidgetVisibilityState = 'private' | 'module' | 'restricted'; /** Alcance de la caché del dato. Solo se pinta si el host lo declara. */ export type WidgetDataScopeState = 'shared' | 'perUser'; /** Una persona del directorio del host. `nombre` puede venir vacío: se cae al id. */ export interface WidgetAccessPerson { id: string; nombre?: string; email?: string; } /** Una fila de «quién tiene acceso». */ export interface WidgetGrantRow { subjectType: WidgetGrantSubjectType; subjectId: string; /** * Nombre legible del sujeto, si el host puede resolverlo. Cuando falta se pinta el id — es lo que * pasa hoy en Comercial mientras el directorio de usuarios devuelve marcadores. */ subjectLabel?: string; level: WidgetGrantLevel; grantedAt?: string; grantedBy?: string; /** `null` o ausente = sin vencimiento. */ expiresAt?: string | null; /** * El acceso existe y **no le abre nada** a su destinatario, porque le falta una capacidad de más * arriba. Es la señal que convierte la revisión en una lista de excepciones en vez de una lista * que alguien debe acordarse de mirar. */ inert?: boolean; } /** Lo que el host devuelve al conceder: si quedó inerte, y cualquier aviso que quiera mostrar. */ export interface WidgetGrantResult { inert?: boolean; /** Texto ya resuelto por el host. El kit no traduce mensajes de un modelo que no conoce. */ warning?: string; } /** La política del widget: quién lo alcanza y con qué alcance se cachea su dato. */ export interface WidgetPolicy { visibility: WidgetVisibilityState; dataScope?: WidgetDataScopeState; } /** Una opción de vocabulario del host (módulos o permisos como sujeto). */ export interface WidgetAccessTerm { id: string; label: string; } /** * Lo que el host declara que su modelo soporta. Todo es opcional y los defaults son los mínimos: * conceder lectura a una persona. */ export interface WidgetAccessCapabilities { /** Tipos de sujeto disponibles. Default: `['user']`. */ subjects?: WidgetGrantSubjectType[]; /** Niveles disponibles, de menor a mayor. Default: `['viewer']`. */ levels?: WidgetGrantLevel[]; /** * Módulos ofrecibles cuando `subjects` incluye `module`. * * 🔴 **Desde F5 el diálogo RETIRA este sujeto en todo indicador que declare vertical**, y el * razonamiento es más fuerte que «recortar la lista»: * * Con un indicador en **una sola vertical**, el sujeto se queda con **exactamente un valor * ofrecible** —la suya—, y conceder a su propia vertical **es lo mismo que no haberlo * restringido**. Las otras dos producirían un acceso que se guarda y **no abre nada**. Una opción * con una sola respuesta posible, y que equivale a deshacer lo que acabas de hacer, no es una * opción: es una trampa con forma de formulario. * * 🟢 **Dónde sí sigue teniendo sentido, y por eso el sujeto no desaparece del contrato:** en * un indicador **sin vertical declarada** —los personales que creó alguien antes de F1, a los que * el servidor les vaciaba `roles_permitidos`—. Ahí no hay techo, así que conceder a un módulo * **sí** abre algo. */ modules?: WidgetAccessTerm[]; /** * 🔴 **F5 — la pareja categoría→vertical, la MISMA que consume el Constructor.** * * Con ella el diálogo deriva la vertical de un indicador exactamente igual que el * Builder: de su categoría. Que las dos pantallas coincidan deja de ser disciplina y pasa a ser * una consecuencia — no hay dos sitios donde la vertical se decida, hay uno. * * ⚠️ Sin esto el diálogo cae al comportamiento anterior: ofrece `modules` entero y nombra la * visibilidad «todo el módulo» en genérico. Es aditivo a propósito; un host que no lo declare no * se rompe, solo no mejora. */ categories?: DashboardCategoryOption[]; /** * 🔴 **Permisos ofrecibles cuando `subjects` incluye `permission`.** * * Sin esto el sujeto se teclea, y ése era el estado en que el mecanismo central —*conceder a quien * tenga un permiso*— llegó a la pantalla: un `` vacío, un catálogo de cientos de códigos que * hay que saberse de memoria, y un `403` como premio por equivocarse. * * `allowFreeText` conserva la caja libre como respaldo para un host que no pueda enumerarlos. * * ⚠️ **Qué lista publicar: los permisos de QUIEN ESTÁ CONCEDIENDO**, no el catálogo entero. No es * una simplificación: el servidor exige que uno tenga el permiso que asocia, así que ofrecer * cualquier otro es ofrecer un rechazo. La lista correcta y la lista corta son la misma. */ permissions?: { options?: WidgetAccessTerm[]; allowFreeText?: boolean; }; /** * Una advertencia del host junto al selector de sujeto, por tipo. El kit la pinta y no la * interpreta: **el texto viene ya resuelto**, porque el kit no traduce mensajes de un modelo que no * conoce. Es la misma regla que en `WidgetGrantResult.warning`. * * El caso que la motiva: un permiso del Access Manager autoriza *acciones*, no describe *datos*, y * quien lo tenga por otra razón hereda el acceso sin que nadie lo haya decidido. Eso hay que * decirlo donde se elige, no en un documento. */ subjectHint?: Partial>; /** Vencimiento. Si se omite, el diálogo no lo pinta y el host decide su default. */ expiry?: { defaultDays?: number; allowNoExpiry?: boolean; }; /** Visibilidades ofrecibles. Si se omite, el diálogo usa las tres del modelo. */ visibilities?: WidgetVisibilityState[]; /** * Alcances de caché ofrecibles. * * 🔴 **F5.b.6 — esto SALIÓ del diálogo de acceso el 2026-09-14.** El alcance de caché **no es * visibilidad**: no decide quién ve el indicador, sino de quién es el dato que se guarda. Tenerlo * en la misma ventana que las dos preguntas de acceso invitaba a leerlo como una tercera, y no lo * es. El campo se conserva en el contrato —el host lo sigue declarando y el API lo sigue * aceptando— pero el diálogo ya no lo pinta. */ dataScopes?: WidgetDataScopeState[]; } /** * Las operaciones del host. `listGrants` es la única obligatoria junto con `grant`/`revoke`: sin * ellas no hay nada que compartir y el diálogo no se ofrece. */ export interface WidgetAccessAdapter { capabilities?: WidgetAccessCapabilities; listGrants(widgetId: string): Promise; grant(widgetId: string, input: { subjectType: WidgetGrantSubjectType; subjectId: string; level: WidgetGrantLevel; /** ISO, o `null` para pedir explícitamente sin vencimiento. */ expiresAt?: string | null; }): Promise; revoke(widgetId: string, subjectType: WidgetGrantSubjectType, subjectId: string): Promise; /** Sin esto, el sujeto se teclea. */ searchPeople?(query: string): Promise; /** Sin esto —o sin `savePolicy`— la pestaña de política no se pinta. */ getPolicy?(widgetId: string): Promise; savePolicy?(widgetId: string, policy: WidgetPolicy): Promise<{ warning?: string; } | void>; /** * 🔴 **F5.c — «Hoy lo ve: …». La frase que dice el RESULTADO.** * * Las cerraduras combinadas son difíciles de deducir mirando los controles: los grants SUMAN * dentro del techo, y el techo de vertical manda por encima. Que el diálogo diga en qué * queda convierte el modelo en algo que se lee en vez de calcularse. * * 🔴 Y ataca de frente la trampa que no tiene ninguna otra señal en pantalla: * restringido, sin accesos, no lo ve NADIE. Es el error que alguien comete creyendo que * restringir ya reparte. * * ⚠️ El texto es del HOST, y el kit solo lo pinta. Misma regla que `subjectHint` y que * `WidgetGrantResult.warning`: el kit no conoce este modelo —no sabe qué es una vertical, ni qué * concede un permiso del Access Manager— así que traducirlo aquí sería inventarlo. Devolver * `undefined` es válido: la frase no se pinta. */ /** * 🔴 **El rótulo y la pista de cada visibilidad, resueltos por el HOST.** * * ⚠️ **Se retiró el 2026-09-14 y se repuso el mismo día, y el motivo del viaje de ida y * vuelta vale la pena.** Se retiró porque estaba **muerto**: el diálogo rediseñado no lo * consultaba, así que un host podía implementarlo y no pasaba nada. Pero lo que el diálogo hacía * en su lugar era **peor**: elegía el rótulo él mismo con `widget.propietario_id`, o sea aplicando * una regla del host —*«privado SIN dueño lo ven los administradores»*— que el kit no puede * conocer. Retirarlo convirtió una fuga reparable en una fija. Ahora se consulta de verdad. * * Qué decide el host aquí, y el kit no puede: qué significa `private` cuando el * widget **no tiene dueño** (¿nadie? ¿quien administre?), y si `restricted` deja entrar a alguien * por encima de la lista. Son reglas de su modelo de autorización, no del formulario. * * Devolver `undefined` —o no implementarlo— deja el texto del kit, que es deliberadamente * **neutro**: describe el estado y **no promete quién entra**. */ describeVisibility?(input: { widgetId: string; visibility: WidgetVisibilityState; /** Si el indicador tiene dueño. Sin dueño, «privado» no se lo reserva a nadie. */ tieneDueno: boolean; vertical?: WidgetAccessTerm; }): { label?: string; hint?: string; } | undefined; /** * 🔴 **La nota de «esto lo ve alguien SIEMPRE, sin figurar en la lista».** * * El kit pintaba aquí un literal —*«quienes administran X lo ven siempre»*— y eso es una * **afirmación sobre el modelo del host**: que existe un rol que salta la lista de accesos. En * Comercial es cierto; en un host sin ese bypass sería una **garantía falsa** debajo de la lista, * y ninguna traducción lo arregla porque el kit ya decidió pintarla. * * 🔴 Por eso NO tiene texto por defecto: sin respuesta del host, la nota no se pinta. * Es la disciplina del resto del contrato —el kit no dice lo que no puede saber— y aquí importa * más que en ningún otro sitio, porque lo que se afirmaría es justamente **quién ve el dato a * pesar de lo que diga esta pantalla**. */ describeAlwaysSee?(input: { widgetId: string; vertical?: WidgetAccessTerm; }): string | undefined; describeAudience?(input: { widgetId: string; /** La política EN CURSO en el formulario, no la guardada: la frase sigue lo que se está eligiendo. */ policy: WidgetPolicy | null; grants: WidgetGrantRow[]; /** La vertical del indicador, si se pudo derivar de su categoría. */ vertical?: WidgetAccessTerm; }): string | undefined; } //# sourceMappingURL=types.d.ts.map