/**
* 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