/** * Contratos e Interfaces del Módulo Dashboard Comercial (BMAD 6) * * Este archivo es la fuente única de verdad para las entidades que viajan entre * el frontend (React) y el backend (Dashboard Module / Proxy). */ /** * Categoría de un widget. Es una CLAVE opaca que declara el host, no un nombre de negocio. * * Antes era una unión con `'Ventas' | 'Compras' | 'Inventario' | 'Operaciones'`. Eso documentaba el * vocabulario de UN producto (Siesa Comercial) dentro de una librería compartida: un host de * Financiero o Nómina leía este tipo y concluía, con razón, que el módulo no era para él. */ export type DomainCategory = string; /** * Un término del vocabulario del host: qué se GUARDA y qué se MUESTRA. * * La separación es lo que hace posible el i18n. Antes las categorías eran `string[]`, o sea * que la etiqueta visible ERA la clave persistida: un widget guardaba `categoria: 'Ventas'` y la * pantalla pintaba `Ventas` en cualquier idioma, para siempre. Con `{ id, label }` el host guarda * una clave estable y traduce la etiqueta por su cuenta. * * Lo mismo aplica a los roles: el `id` puede ser el código de permiso del RBAC del host y el * `label` su nombre legible. */ export interface DashboardVocabularyTerm { /** Clave estable. Es lo que se persiste en `roles_permitidos` / `categoria`. */ id: string; /** Texto visible, ya traducido por el host. */ label: string; /** * 🔴 **Solo en las CATEGORÍAS, y solo desde F3: la vertical que esta categoría determina.** * * Es la pareja que permite al Constructor dejar de preguntar la vertical aparte. Opcional porque * un host anterior a F3 no la publica —y entonces el Constructor lo DICE en vez de inventarse una— * y porque los términos de rol y de sensibilidad no tienen nada que poner aquí. */ role?: string; } export type WidgetPresentationType = 'kpi' | 'chart_line' | 'chart_bar_vertical' | 'chart_bar_horizontal' | 'chart_donut' | 'table' | 'image' | 'custom'; export type DashboardPeriod = 'Hoy' | 'Semana' | 'Mes'; /** * Función de fetch inyectada por el host (Master Data, Core Frontend, etc.). * El host es responsable de agregar autenticación, headers JWT y cualquier * lógica de interceptores. El Dashboard usa esta función para TODOS sus requests. * * @example — Con axios del host * const fetcher: DashboardFetcher = (url, opts) => * axiosInstance.request({ url, method: opts?.method ?? 'GET', data: opts?.body }) * .then(r => r.data); * * @example — Con fetch nativo del host * const fetcher: DashboardFetcher = (url, opts) => * fetch(url, { * method: opts?.method ?? 'GET', * headers: { Authorization: `Bearer ${getToken()}`, 'Content-Type': 'application/json' }, * body: opts?.body ? JSON.stringify(opts.body) : undefined, * }).then(r => r.json()); */ export type DashboardFetcher = (url: string, options?: { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; body?: unknown; }) => Promise; /** * Punto de dato genérico para gráficas de Recharts. * Todos los widgets de gráfica deben usar este tipo en lugar de `any[]`. */ export type ChartDataPoint = Record; /** * Field mapping semántico del widget (estándar v2). * Define el rol de cada campo para el chart, en lugar de un mapeo plano de renombrado. * Almacenado en cmp_kpi_dashboard_widgets.field_mapping (JSONB). */ export interface WidgetFieldMapping { /** Campo del endpoint que actúa como eje X / dimensión de agrupación. */ category?: string; /** Campos del endpoint que se grafican como series / métricas. */ values?: string[]; /** Labels de visualización para leyenda y tooltip. No cambia el dataKey de Recharts. */ aliases?: Record; /** Mapeo de renombrado de campos del endpoint (campo_api → nombre_canónico). Solo para APIs con nombres no estándar. */ rename?: Record; /** Campos conocidos del endpoint (schema hints). Permite mostrar todos los campos disponibles sin que el widget haya cargado datos. */ fields?: { name: string; type: 'string' | 'number' | 'date' | 'boolean'; label: string; }[]; } /** * Representa la definición base de un widget en el catálogo (tabla `dashboard_widgets`). * Gestionado por administradores o a través del Widget Builder. */ export interface WidgetDefinition { widget_id: string; nombre: string; descripcion: string; categoria: DomainCategory; tipo: WidgetPresentationType; icono: string; roles_permitidos: string[]; col_min: number; col_max: number; altura_default: number; cols_default: number; orden_default: number; activo: boolean; version: string; componente_ruta?: string; config_schema?: Record; endpoint_url?: string; endpoint_method?: 'GET' | 'POST'; endpoint_headers?: Record; endpoint_params?: Record; field_mapping?: WidgetFieldMapping; refresh_policy?: number; es_privado?: boolean; is_internal?: boolean; propietario_id?: string; /** * Lo que **este** usuario puede hacer con **este** widget, ya resuelto por el servidor. * * Llega en el DTO del catalogo (`permissions` en `WidgetDefinitionDTO`) cuando el host evalua * acceso por instancia. Es la unica fuente de verdad para pintar acciones: si el servidor * restringe por ACL y el kit vuelve a decidir cruzando `roles_permitidos`, los dos discrepan y * gana el que pinte — botones que no hacen nada, o acciones ausentes para quien si puede. * * Es `undefined` en dos casos legitimos, y por eso no es obligatorio: un host cuyo backend no * evalua acceso por instancia (el comportamiento historico), y las respuestas que no pasan por * una evaluacion de acceso. Ver `resolveWidgetPermissions`. */ permissions?: WidgetPermissions; } /** * Los cuatro verbos del usuario actual sobre un widget, tal como los emite el servidor. * * Las claves son `snake_case` porque son las del JSON y **no** se renombran al entrar: el resto de * este modulo ya trata el DTO como su tipo de dominio (`roles_permitidos`, `es_privado`), y una capa * de traduccion solo para cuatro campos añadiria un sitio mas donde perderlos en silencio. */ export interface WidgetPermissions { /** Verlo en el catalogo **y** poder pedir su dato al proxy. */ can_view: boolean; /** Editar la definicion del widget. */ can_edit: boolean; /** Eliminarla. */ can_delete: boolean; /** Otorgar acceso a personas nombradas. */ can_share: boolean; /** * Cambiar la EXPOSICION del widget: `es_privado`, `roles_permitidos`, `is_internal`. * * Es una autoridad distinta de `can_edit` -editar un titulo no mueve a quien alcanza el widget- y * el backend la exige por separado en el PATCH del catalogo. El formulario no debe ofrecer esos * campos cuando esto es `false`: los mandaria igual y el PATCH acabaria en 403 despues de que la * persona lleno el paso. */ can_publish: boolean; } /** Configuracion publica del whitelist de rutas internas (GET /api/dashboard/widgets/allowed-internal-routes) */ export interface AllowedInternalRoutes { enforce_whitelist: boolean; allowed_prefixes: string[]; allowed_hosts: string[]; } /** Fuente de datos disponible en el dropdown del Widget Builder (GET /api/dashboard/widgets/data-sources) */ export interface DataSourceOption { name: string; url: string; description?: string; category?: string; method: 'GET' | 'POST'; suggested_type?: string; default_field_mapping?: Record; } /** * Representa la configuración específica de un widget para un usuario particular (tabla `dashboard_preferences`). */ export interface UserWidgetPreference { id: string; user_id: string; widget_id: string; activo: boolean; orden: number; col_span: number; row_span: number; pos_x: number; pos_y: number; config_json?: Record; modificado_en: string; } export interface LayoutUpdatePayload { userId: string; period: DashboardPeriod; layout: UserWidgetPreference[]; } /** * Payload enviado al endpoint `/api/dashboard/proxy/fetch` */ export interface FetchProxyPayload { widgetId: string; period: DashboardPeriod; userParams?: Record; } /** * Props estandarizadas que recibe CADA componente de widget que sea montado en el grid. */ export interface WidgetProps { preference: UserWidgetPreference; definition: WidgetDefinition; isEditMode: boolean; period: DashboardPeriod; onConfigChange?: (newConfig: Record) => void; } /** Descriptor de un campo en el schema del envelope. */ export interface SchemaField { name: string; type: 'string' | 'number' | 'date' | 'boolean'; label: string; format?: 'currency' | 'percent' | 'integer' | 'decimal'; } /** Respuesta estándar de un endpoint interno de dashboard. */ export interface EndpointEnvelope { data: Record[]; schema: { fields: SchemaField[]; }; meta: { generatedAt: string; period: string; rowCount: number; }; } /** Configuracion de mapeo discriminada por tipo de widget */ /** Funciones de agregación disponibles para KPIs y gráficos */ export type AggregationType = 'none' | 'sum' | 'average' | 'count' | 'min' | 'max' | 'last' | 'first'; /** Familias de tipos de widget — los tipos de una misma familia son intercambiables */ export type ChartFamily = 'scalar' | 'categorical' | 'tabular'; export declare const TYPE_FAMILIES: Record; /** * Retorna los tipos hermanos (excluye el actual). * Si valuesCount > 1, excluye donut (solo acepta 1 valor). */ export declare function getCompatibleTypes(tipo: string, valuesCount?: number): string[]; /** * Contrato unificado de datos para todos los charts categóricos. * Almacenado en config_schema. Agnóstico del tipo de chart — * el mismo contrato sirve para barras, líneas y donut. */ export interface ChartDataContract { categoryField: string; valueFields: string[]; aggregation?: AggregationType; } /** * Override de field mapping por usuario. * Almacenado en preference.config_json.fieldMapping. * Se mergea sobre el contrato del widget (config_schema). */ export interface FieldMappingOverride { category?: string; values?: string[]; aliases?: Record; } /** * Extrae un ChartDataContract normalizado de config_schema. * Soporta: forma directa (categoryField+valueFields), MappingConfig (Builder), o null. */ export declare function extractChartContract(configSchema: Record | undefined | null): ChartDataContract | null; export type MappingConfig = { type: 'kpi'; valueField: string; labelField?: string; aggregation: 'sum' | 'average' | 'count' | 'last' | 'min' | 'max'; valuePrefix?: string; valueSuffix?: string; } | { type: 'chart_bar_vertical'; xField: string; yField: string; groupByField?: string; aggregation?: AggregationType; } | { type: 'chart_bar_horizontal'; xField: string; yField: string; groupByField?: string; aggregation?: AggregationType; } | { type: 'chart_line'; xField: string; yField: string; seriesFields?: string[]; aggregation?: AggregationType; } | { type: 'chart_donut'; categoryField: string; valueField: string; aggregation?: AggregationType; } | { type: 'table'; visibleColumns: string[]; sortField?: string; sortDirection?: 'asc' | 'desc'; }; //# sourceMappingURL=types.d.ts.map