README

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

@libs-core/data-binding

Sistema de vinculación de datos (data binding) para conectar parámetros de componentes y acciones con múltiples fuentes de datos.

📋 Tabla de Contenidos

🎯 Visión General

@libs-core/data-binding es una librería que permite vincular parámetros de componentes embeddables y acciones públicas con diferentes fuentes de datos de forma declarativa y type-safe.

Problema que Resuelve

En un sistema modular con widgets configurables:

Solución

Data-binding proporciona:

  1. Modelo unificado para describir vinculaciones de datos
  2. Sistema de resolvers extensible para diferentes fuentes
  3. Integración con @libs-core/shared-models (sistema de parámetros)
  4. UI de configuración para usuarios finales
  5. Preparación para integración futura con Singularity (workflows)

✨ Características Principales

✅ MVP (Fase 1 - COMPLETADO)

🚀 Futuro (Post-MVP con Singularity)

🏗️ Arquitectura

@libs-core/data-binding/
├── models/                          # Modelos e interfaces
│   ├── data-binding.interface.ts    # DataBinding (extiende ParameterMetadata)
│   ├── binding-source.types.ts      # Tipos de fuentes de datos
│   └── binding-result.interface.ts  # Resultado de resolución
├── services/                        # Servicios principales
│   ├── data-binding.service.ts      # Servicio de resolución principal
│   ├── binding-registry.service.ts  # Registro de bindings
│   └── resolvers/                   # Resolvers por tipo de fuente
│       ├── base.resolver.ts         # Clase base abstracta
│       ├── manual.resolver.ts       # Valores estáticos
│       ├── repository.resolver.ts   # Data-access repositories
│       └── config.resolver.ts       # Configuración del sistema
└── ui/                              # Componentes de interfaz
    └── binding-configurator.component.ts # Editor visual de bindings

Dependencias

dependencies: [
  '@libs-core/shared-models',    // ParameterMetadata, tipos base
  '@libs-core/data-access',      // RepositoryRegistry
  '@libs-core/log-system',       // Logging
  '@libs-core/event-system',     // Eventos (opcional)
  '@angular/core',               // Angular 20
  'rxjs'                         // Observables
]

📖 Casos de Uso

Caso 1: Widget con PageSize Configurable

// Widget UserList con pageSize binding
@Component({
  selector: 'app-user-list-widget',
  template: `<div>Users ({{ pageSize() }} per page)</div>`
})
export class UserListWidgetComponent {
  // Parámetro como DataBinding
  pageSizeBinding = input<DataBinding>({
    name: 'pageSize',
    type: 'number',
    required: false,
    defaultValue: 10,
    source: 'repository',
    sourceConfig: {
      type: 'repository',
      entityName: 'Config',
      method: 'getValue',
      methodParams: ['userList.pageSize'],
      resultPath: 'value'
    }
  });

  private bindingService = inject(DataBindingService);
  pageSize = signal(10);

  ngOnInit() {
    this.bindingService
      .resolve<number>(this.pageSizeBinding())
      .subscribe(value => this.pageSize.set(value));
  }
}

Caso 2: Action con Parámetros desde Config

// Action con userId binding
export class SendNotificationAction implements ExecutableAction {
  async execute(context: ActionContext): Promise<ActionResult> {
    const userIdBinding: DataBinding = {
      name: 'userId',
      type: 'string',
      required: true,
      source: 'repository',
      sourceConfig: {
        type: 'repository',
        entityName: 'User',
        method: 'getCurrentUserId'
      }
    };

    const userId = await firstValueFrom(
      this.bindingService.resolve<string>(userIdBinding)
    );

    // Ejecutar acción con userId
  }
}

Caso 3: Configuración Visual por Usuario Final

// Componente de administración que usa el configurador
@Component({
  template: `
    <app-binding-configurator
      [parameterName]="'pageSize'"
      [parameterType]="'number'"
      [description]="'Número de usuarios por página'"
      (bindingConfigured)="onBindingSaved($event)"
    />
  `
})
export class WidgetConfigComponent {
  onBindingSaved(binding: DataBinding) {
    // Guardar binding en configuración del widget
    this.widgetConfigService.saveBinding('userList', 'pageSize', binding);
  }
}

📦 Instalación

# Crear la librería
npx nx g @nx/angular:lib data-binding --directory=libs/core

# Instalar en un módulo
import { DataBindingService } from '@libs-core/data-binding';

🚀 Uso Básico

1. Definir un Binding

import { DataBinding } from '@libs-core/data-binding';

const binding: DataBinding = {
  name: 'pageSize',
  type: 'number',
  required: false,
  defaultValue: 10,
  description: 'Número de elementos por página',
  source: 'manual',
  sourceConfig: {
    type: 'manual',
    value: 20
  }
};

2. Resolver el Binding

import { DataBindingService } from '@libs-core/data-binding';

export class MyComponent {
  private bindingService = inject(DataBindingService);

  pageSize = signal(10);

  ngOnInit() {
    this.bindingService
      .resolve<number>(this.pageSizeBinding)
      .subscribe(value => this.pageSize.set(value));
  }
}

3. Resolver Múltiples Bindings

const bindings: DataBinding[] = [
  { name: 'pageSize', type: 'number', source: 'config', ... },
  { name: 'sortBy', type: 'string', source: 'manual', ... },
  { name: 'filterBy', type: 'string', source: 'repository', ... }
];

this.bindingService
  .resolveMany(bindings)
  .subscribe(results => {
    this.pageSize.set(results.get('pageSize'));
    this.sortBy.set(results.get('sortBy'));
    this.filterBy.set(results.get('filterBy'));
  });

🎨 UI Components

BindingConfiguratorComponent

Componente visual para configurar data bindings sin escribir código. Permite a usuarios finales configurar la vinculación de parámetros desde diferentes fuentes de datos.

Características

Uso Básico

import { BindingConfiguratorComponent } from '@libs-core/data-binding';

@Component({
  selector: 'app-parameter-config',
  standalone: true,
  imports: [BindingConfiguratorComponent],
  template: `
    <app-binding-configurator
      [parameterName]="'pageSize'"
      [parameterType]="'number'"
      [description]="'Número de elementos por página'"
      [required]="false"
      [currentBinding]="existingBinding"
      (bindingConfigured)="onBindingSaved($event)"
      (cancelled)="onCancelled()"
    />
  `
})
export class ParameterConfigComponent {
  existingBinding?: DataBinding;

  onBindingSaved(binding: DataBinding) {
    console.log('Binding configurado:', binding);
    // Guardar binding en la configuración del widget/acción
  }

  onCancelled() {
    console.log('Configuración cancelada');
    // Cerrar modal o navegar atrás
  }
}

Propiedades (Inputs)

Propiedad Tipo Requerido Descripción
parameterName string ✅ Nombre del parámetro a configurar
parameterType ParameterType ✅ Tipo del parámetro: 'string', 'number', 'boolean', 'object'
description string ❌ Descripción del parámetro (tooltip/help)
currentBinding DataBinding ❌ Binding existente para edición
required boolean ❌ Si el parámetro es obligatorio (default: false)

Eventos (Outputs)

Evento Payload Descripción
bindingConfigured DataBinding Emitido cuando se guarda un binding válido
cancelled void Emitido cuando se cancela la configuración

Fuentes de Datos Soportadas

1. Manual (Valor Estático)

Permite configurar un valor fijo para el parámetro.

// Resultado de configuración manual
{
  name: 'pageSize',
  type: 'number',
  source: 'manual',
  sourceConfig: {
    type: 'manual',
    value: 25
  },
  defaultValue: 10
}

Tipos soportados:

2. Repository (Data-Access)

Vincula el parámetro con un método de repositorio.

// Resultado de configuración de repository
{
  name: 'currentUser',
  type: 'object',
  source: 'repository',
  sourceConfig: {
    type: 'repository',
    entityName: 'User',
    method: 'getCurrentUser',
    methodParams: [],
    valuePath: 'data.user'  // Opcional: extraer valor específico
  }
}

Campos del formulario:

3. Config (Configuración del Sistema)

Lee valores de la configuración global o del tenant actual.

// Resultado de configuración de config
{
  name: 'theme',
  type: 'string',
  source: 'config',
  sourceConfig: {
    type: 'config',
    configPath: 'ui.theme',
    defaultValue: 'light'
  }
}

Campos del formulario:

Flujo de Trabajo

  1. Seleccionar Fuente: Usuario elige entre Manual, Repository o Config
  2. Configurar Parámetros: Formulario dinámico según la fuente seleccionada
  3. Validación en Tiempo Real: El botón "Guardar" se habilita solo cuando la configuración es válida
  4. Probar Binding (opcional): Botón "🧪 Probar Binding" para verificar la resolución
  5. Guardar o Cancelar:
    • Guardar: Emite evento bindingConfigured con el DataBinding completo
    • Cancelar: Emite evento cancelled sin guardar cambios

Ejemplo Completo: Widget Configurable

@Component({
  selector: 'app-widget-settings',
  standalone: true,
  imports: [BindingConfiguratorComponent],
  template: `
    <h2>Configurar Parámetros del Widget</h2>

    <!-- Configurar PageSize -->
    <app-binding-configurator
      [parameterName]="'pageSize'"
      [parameterType]="'number'"
      [description]="'Número de usuarios por página'"
      [required]="false"
      [currentBinding]="widgetConfig.bindings.pageSize"
      (bindingConfigured)="saveBinding('pageSize', $event)"
      (cancelled)="closeModal()"
    />

    <!-- Configurar Tema -->
    <app-binding-configurator
      [parameterName]="'theme'"
      [parameterType]="'string'"
      [description]="'Tema visual del widget'"
      [currentBinding]="widgetConfig.bindings.theme"
      (bindingConfigured)="saveBinding('theme', $event)"
      (cancelled)="closeModal()"
    />
  `
})
export class WidgetSettingsComponent {
  private widgetConfigService = inject(WidgetConfigService);
  widgetConfig = signal<WidgetConfig>(this.widgetConfigService.getConfig());

  saveBinding(paramName: string, binding: DataBinding) {
    this.widgetConfigService.updateBinding(paramName, binding);
    this.widgetConfig.set(this.widgetConfigService.getConfig());
    this.showSuccess(`Binding "${paramName}" guardado correctamente`);
  }

  closeModal() {
    // Cerrar modal de configuración
  }

  showSuccess(message: string) {
    // Mostrar notificación de éxito
  }
}

Estados del Componente

El componente maneja los siguientes estados internos:

Validaciones

El componente valida automáticamente:

Estilos y Temas

El componente usa CSS Variables para personalización:

.binding-configurator {
  --surface-color: #ffffff;
  --border-color: #e0e0e0;
  --primary-color: #007bff;
  --text-primary: #212529;
  /* ... más variables disponibles */
}

Soporte completo para dark mode con @media (prefers-color-scheme: dark).

📚 Documentación Técnica

🔮 Roadmap

✅ Fase 1: MVP (COMPLETADO - 2025-01-05)

Estado Actual:

⚠️ Nota Importante sobre Ejemplos:

Los componentes de ejemplo (UserListWidgetComponent, BindingUsageExampleComponent) están disponibles internamente para demostración y testing, pero NO se exportan en la API pública de la librería.

Razón: ng-packagr/TypeScript tiene limitaciones con la exportación de ejemplos que importan desde otras bibliotecas del monorepo. Exportar estos ejemplos causa errores de compilación del tipo:

Cannot destructure property 'pos' of 'file.referencedFiles[index]' as it is undefined

Cómo usar los ejemplos:

Esta misma limitación aplica a todos los módulos del monorepo que usan data-binding.

✅ Fase 2: Integración y Refinamiento (COMPLETADO - 2025-01-11)

🚀 Fase 3: Integración con Singularity (Futuro)

🌟 Fase 4: Características Avanzadas (Futuro)

🤝 Contribución

Esta librería sigue los estándares de GoGrab Space:

📝 Licencia

Propiedad de GoGrab Team