npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@reinaldor507/kaas-builder-ds

v1.6.0

Published

Kit de herramientas para crear presentaciones multimedia interactivas con React y API backend

Readme

KAAS Builder DS

Librería profesional de React para crear presentaciones multimedia interactivas. 100% controlada desde tu frontend - el paquete solo renderiza la UI.

🚀 Características

  • Arquitectura Frontend-First: Todo estado y lógica en tu aplicación
  • Canvas interactivo con drag & drop, snap-to-grid, guidelines
  • Elementos multimedia: Sliders, imágenes, videos, texto, reloj
  • Programación temporal: Horarios semanales y por fechas específicas
  • Calendario interactivo para selección de fechas
  • Sistema de resoluciones: Múltiples tamaños de pantalla
  • Botones controlados desde frontend: Preview, Debug, Guardar, Resoluciones
  • Exportación a JSON para guardar en tu backend
  • API Client flexible para cualquier endpoint
  • TypeScript nativo con tipos completos

📦 Instalación

pnpm add kaas-builder-ds

🔧 Uso Principal - Arquitectura Controlada

La librería es completamente controlada - tú manejas el estado y ella solo renderiza. No tiene estado interno.

Setup Básico

import React, { useState } from 'react'
import { App, type Template } from 'kaas-builder-ds'
import 'kaas-builder-ds/dist/kaas-builder-ds.css'

function MyApp() {
  // TÚ manejas el estado del template
  const [template, setTemplate] = useState<Template>({
    customer: 'Mi Cliente',
    resourcePathImg: '/images/',
    resourceDownload: '/downloads/',
    destDownload: '/dest/',
    useTracking: false,
    screens: [{
      id: 'screen-1',
      width: 1920,
      height: 1080,
      backgroundColor: '#ffffff',
      backgroundImage: '',
      elements: [] // Inicia vacío - mostrará selector de plantillas
    }]
  })

  // Callback obligatorio para cambios
  const handleTemplateChange = (newTemplate: Template) => {
    setTemplate(newTemplate)
    
    // Opcional: Auto-guardar en tu backend
    // await saveToDatabase(newTemplate)
  }

  // Callback para cuando el usuario haga clic en "Guardar"
  const handleExportTemplate = async (template: Template) => {
    try {
      // Guardar en tu base de datos
      await fetch('/api/templates', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(template)
      })
      
      alert('Template guardado exitosamente')
    } catch (error) {
      alert('Error al guardar: ' + error.message)
    }
  }

  return (
    <div className="App">
      <App 
        template={template}                      // ✅ Requerido: Tu estado
        onTemplateChange={handleTemplateChange}  // ✅ Requerido: Tu callback
        onExportTemplate={handleExportTemplate}  // ⚡ Opcional: Tu lógica de guardado
      />
    </div>
  )
}

export default MyApp

🎮 Botones Controlados desde Frontend

IMPORTANTE: El paquete NO renderiza botones en la toolbar. Tú controlas TODO desde tu aplicación.

Tabla de Responsabilidades

| Control | Responsabilidad | |---------|-----------------| | Botón Preview | ✅ Tu frontend - muestra el template en modal/tab/iframe | | Botón Debug | ✅ Tu frontend - abre consola o panel con JSON | | Botón Guardar | ✅ Tu frontend - envía JSON a tu backend | | Selector Resolución | ✅ Tu frontend - cambia width/height del screen | | JSON Actualizado | ✅ Accesible en onTemplateChange callback | | Canvas & Edición | ✅ El paquete renderiza todo |

Implementación Completa con Botones

import React, { useState, useRef } from 'react'
import { App, type Template } from 'kaas-builder-ds'
import 'kaas-builder-ds/dist/kaas-builder-ds.css'

function CompleteEditor() {
  const [template, setTemplate] = useState<Template>({
    customer: 'Mi Cliente',
    resourcePathImg: '/images/',
    resourceDownload: '/downloads/',
    destDownload: '/dest/',
    useTracking: false,
    screens: [{
      id: 'screen-1',
      width: 1920,
      height: 1080,
      backgroundColor: '#ffffff',
      backgroundImage: '',
      elements: []
    }]
  })

  const [showDebug, setShowDebug] = useState(false)
  const [isSaving, setIsSaving] = useState(false)

  // 🎯 HANDLER: Cuando el usuario cambia algo en el canvas
  const handleTemplateChange = (newTemplate: Template) => {
    setTemplate(newTemplate)
    console.log('Template actualizado:', newTemplate)
  }

  // 📊 BOTÓN PREVIEW
  const handlePreview = () => {
    const serialized = encodeURIComponent(JSON.stringify(template))
    window.open(
      `/preview?template=${serialized}`,
      'preview_window',
      'width=1920,height=1080'
    )
  }

  // 📋 BOTÓN DEBUG
  const handleDebug = () => {
    console.table({
      'Cliente': template.customer,
      'Pantalla': `${template.screens[0].width}x${template.screens[0].height}`,
      'Elementos': template.screens[0].elements.length,
      'Fondo': template.screens[0].backgroundColor,
      'Timestamp': new Date().toISOString()
    })
    setShowDebug(!showDebug)
  }

  // 💾 BOTÓN GUARDAR
  const handleSave = async () => {
    setIsSaving(true)
    try {
      const response = await fetch('/api/templates', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(template)
      })

      if (response.ok) {
        alert('✅ Template guardado exitosamente')
      } else {
        throw new Error('Error del servidor')
      }
    } catch (error) {
      alert('❌ Error: ' + (error instanceof Error ? error.message : 'Desconocido'))
    } finally {
      setIsSaving(false)
    }
  }

  // 📐 SELECTOR RESOLUCIÓN
  const handleResolutionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const [width, height] = e.target.value.split('x').map(Number)
    setTemplate({
      ...template,
      screens: [{
        ...template.screens[0],
        width,
        height
      }]
    })
  }

  return (
    <div className="editor-container">
      {/* 🎛️ TOOLBAR PERSONALIZADA - TOTALMENTE CONTROLADA POR TI */}
      <div className="custom-toolbar" style={{
        display: 'flex',
        gap: '10px',
        padding: '12px',
        borderBottom: '1px solid #ddd',
        backgroundColor: '#f8f9fa',
        alignItems: 'center'
      }}>
        {/* Botón Preview */}
        <button 
          onClick={handlePreview}
          style={{
            padding: '8px 16px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontWeight: 'bold'
          }}
        >
          ▶️ Preview
        </button>

        {/* Botón Debug */}
        <button 
          onClick={handleDebug}
          style={{
            padding: '8px 16px',
            backgroundColor: '#6c757d',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          🐛 Debug
        </button>

        {/* Botón Guardar */}
        <button 
          onClick={handleSave}
          disabled={isSaving}
          style={{
            padding: '8px 16px',
            backgroundColor: isSaving ? '#ccc' : '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: isSaving ? 'not-allowed' : 'pointer',
            fontWeight: 'bold'
          }}
        >
          {isSaving ? '⏳ Guardando...' : '💾 Guardar'}
        </button>

        {/* Selector Resolución */}
        <select 
          onChange={handleResolutionChange}
          value={`${template.screens[0].width}x${template.screens[0].height}`}
          style={{
            padding: '8px 12px',
            borderRadius: '4px',
            border: '1px solid #ddd',
            backgroundColor: 'white',
            cursor: 'pointer'
          }}
        >
          <option value="1920x1080">🖥️ Full HD (1920x1080)</option>
          <option value="1280x720">📺 HD (1280x720)</option>
          <option value="3840x2160">🎬 4K (3840x2160)</option>
          <option value="1024x768">💻 XGA (1024x768)</option>
        </select>

        {/* Indicador de estado */}
        <span style={{ marginLeft: 'auto', fontSize: '12px', color: '#666' }}>
          {template.screens[0].width}x{template.screens[0].height} · 
          {template.screens[0].elements.length} elementos
        </span>
      </div>

      {/* Panel Debug (opcional) */}
      {showDebug && (
        <div style={{
          position: 'fixed',
          bottom: 20,
          right: 20,
          backgroundColor: '#1e1e1e',
          color: '#00ff00',
          padding: '15px',
          borderRadius: '8px',
          fontFamily: 'monospace',
          fontSize: '12px',
          maxWidth: '400px',
          maxHeight: '300px',
          overflow: 'auto',
          zIndex: 1000,
          boxShadow: '0 4px 12px rgba(0,0,0,0.3)'
        }}>
          <pre>{JSON.stringify(template, null, 2)}</pre>
        </div>
      )}

      {/* El paquete solo renderiza aquí */}
      <div style={{ flex: 1 }}>
        <App 
          template={template}
          onTemplateChange={handleTemplateChange}
        />
      </div>
    </div>
  )
}

export default CompleteEditor

Props del Componente App

interface AppProps {
  // 🟥 REQUERIDO - Estado controlado
  template: Template                           // Tu template actual
  onTemplateChange: (template: Template) => void  // Callback para cambios

  // 🟦 OPCIONAL - Callbacks de acciones
  onExportTemplate?: (template: Template) => void   // Al hacer clic en "Guardar"
  onPreviewTemplate?: (template: Template) => void  // Al hacer clic en "Preview"
  
  // 🟦 OPCIONAL - Control de UI
  readOnly?: boolean              // Solo lectura (default: false)
  showToolbar?: boolean          // Mostrar toolbar (default: true)
  showPropertiesPanel?: boolean  // Mostrar panel izquierdo (default: true)
  showComponentsPanel?: boolean  // Mostrar panel derecho (default: true)
}

Selector Automático de Plantillas

Cuando el template está vacío (sin elementos y sin imagen de fondo), se muestra automáticamente un selector con 9 plantillas predefinidas:

// Si template.screens[0].elements.length === 0 && !backgroundImage
// Se abre automáticamente el modal TemplateSelector
const emptyTemplate: Template = {
  customer: 'Mi Cliente',
  resourcePathImg: '/images/',
  resourceDownload: '/downloads/',
  destDownload: '/dest/',
  useTracking: false,
  screens: [{
    id: 'screen-1',
    width: 1920,
    height: 1080,
    backgroundColor: '#ffffff',
    backgroundImage: '',
    elements: [] // ← Vacío = muestra selector
  }]
}

Plantillas disponibles:

  • 📱 Zone A + Zone B: Layout de 2 zonas horizontales
  • 📱 Zone A + Zone C: Layout de 2 zonas verticales
  • 📱 Zone A + Zone B + Zone C: Layout de 3 zonas
  • 📺 HDMI A: Zona única para contenido HDMI
  • 📺 HDMI B: Zona única alternativa para HDMI
  • 📺 HDMI A + Ticker: HDMI con cintillo inferior
  • 📺 HDMI B + Ticker: HDMI alternativo con cintillo
  • 🔄 Zone A + Zone B + Ticker: 2 zonas con cintillo
  • 🔄 Zone A + Zone C + Ticker: 2 zonas verticales con cintillo

Integración con Base de Datos

1. Guardar Automático al Hacer Cambios

function MyAppWithAutoSave() {
  const [template, setTemplate] = useState<Template>(/* tu template inicial */)
  const [isSaving, setIsSaving] = useState(false)

  const handleTemplateChange = async (newTemplate: Template) => {
    setTemplate(newTemplate)
    
    // Auto-guardar en background
    setIsSaving(true)
    try {
      await fetch('/api/templates/auto-save', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newTemplate)
      })
    } catch (error) {
      console.error('Error en auto-save:', error)
    } finally {
      setIsSaving(false)
    }
  }

  return (
    <div>
      {isSaving && <div style={{position: 'fixed', top: 10, right: 10}}>
        Guardando...
      </div>}
      
      <App 
        template={template}
        onTemplateChange={handleTemplateChange}
      />
    </div>
  )
}

2. Guardar Manual con Botón "Guardar"

function MyAppWithManualSave() {
  const [template, setTemplate] = useState<Template>(/* tu template inicial */)

  const handleTemplateChange = (newTemplate: Template) => {
    setTemplate(newTemplate)
    // Solo actualizar estado local - NO guardar aún
  }

  const handleExportTemplate = async (template: Template) => {
    try {
      const response = await fetch('/api/templates', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${userToken}`
        },
        body: JSON.stringify({
          id: 'template-123',
          template: template,
          updatedAt: new Date().toISOString()
        })
      })
      
      if (response.ok) {
        alert('✅ Template guardado exitosamente')
      } else {
        throw new Error('Error del servidor')
      }
    } catch (error) {
      alert('❌ Error al guardar: ' + error.message)
    }
  }

  return (
    <App 
      template={template}
      onTemplateChange={handleTemplateChange}
      onExportTemplate={handleExportTemplate} // Al hacer clic en "Guardar"
    />
  )
}

3. Cargar Template Existente desde BD

import { useEffect } from 'react'

function MyAppWithDatabaseLoad({ templateId }: { templateId: string }) {
  const [template, setTemplate] = useState<Template | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)

  // Cargar template inicial
  useEffect(() => {
    const loadTemplate = async () => {
      try {
        setLoading(true)
        
        const response = await fetch(`/api/templates/${templateId}`, {
          headers: {
            'Authorization': `Bearer ${userToken}`
          }
        })
        
        if (!response.ok) {
          throw new Error('Template no encontrado')
        }
        
        const templateData = await response.json()
        setTemplate(templateData)
        
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Error desconocido')
      } finally {
        setLoading(false)
      }
    }

    loadTemplate()
  }, [templateId])

  // Estados de carga
  if (loading) return <div>Cargando template...</div>
  if (error) return <div>Error: {error}</div>
  if (!template) return <div>Template no encontrado</div>

  const handleTemplateChange = (newTemplate: Template) => {
    setTemplate(newTemplate)
  }

  const handleSaveTemplate = async (template: Template) => {
    try {
      await fetch(`/api/templates/${templateId}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${userToken}`
        },
        body: JSON.stringify(template)
      })
      
      alert('Template actualizado correctamente')
    } catch (error) {
      alert('Error al actualizar template')
    }
  }

  return (
    <App 
      template={template}
      onTemplateChange={handleTemplateChange}
      onExportTemplate={handleSaveTemplate}
    />
  )
}

Configuración de UI Opcional

Puedes controlar qué partes de la UI mostrar:

function CustomUIApp() {
  const [template, setTemplate] = useState<Template>(/* tu template */)

  return (
    <App 
      template={template}
      onTemplateChange={setTemplate}
      
      // 🎛️ Control granular de UI
      readOnly={false}                 // Permite edición
      showToolbar={true}              // Mostrar barra superior
      showPropertiesPanel={true}      // Mostrar panel izquierdo
      showComponentsPanel={true}      // Mostrar panel derecho
    />
  )
}

Casos de Uso Específicos

Solo Lectura (Display Mode)

function ReadOnlyViewer({ template }: { template: Template }) {
  return (
    <App 
      template={template}
      onTemplateChange={() => {}} // No-op, no se pueden hacer cambios
      readOnly={true}             // Solo lectura
      showComponentsPanel={false} // Ocultar panel de componentes
    />
  )
}

Editor Mínimo

function MinimalEditor({ template, onTemplateChange }: { 
  template: Template, 
  onTemplateChange: (t: Template) => void 
}) {
  return (
    <App 
      template={template}
      onTemplateChange={onTemplateChange}
      showToolbar={false}         // Sin toolbar
      showComponentsPanel={false} // Solo canvas y propiedades
    />
  )
}

Preview con Callback

function EditorWithPreview() {
  const [template, setTemplate] = useState<Template>(/* template */)

  const handlePreview = (template: Template) => {
    // Abrir preview en nueva ventana/modal/iframe
    window.open(
      `/preview?data=${encodeURIComponent(JSON.stringify(template))}`,
      '_blank',
      'width=1920,height=1080'
    )
  }

  return (
    <App 
      template={template}
      onTemplateChange={setTemplate}
      onPreviewTemplate={handlePreview} // Custom preview
    />
  )
}

🎮 Uso del Player (Solo Visualización)

El componente Player es para mostrar templates ya creados (no para editarlos). Ideal para TVs, pantallas digitales, o vistas de cliente.

Player Básico - Template Estático

import React from 'react'
import { Player, type Template } from 'kaas-builder-ds'
import 'kaas-builder-ds/dist/kaas-builder-ds.css'

function MyPlayer() {
  const template: Template = {
    customer: 'Mi Cliente',
    resourcePathImg: '/images/',
    resourceDownload: '/downloads/',
    destDownload: '/dest/',
    useTracking: false,
    screens: [{
      id: 'screen-1',
      width: 1920,
      height: 1080,
      backgroundColor: '#ffffff',
      backgroundImage: '',
      elements: [
        {
          id: 'slider-1',
          type: 'slider',
          x: 0,
          y: 0,
          width: 1920,
          height: 980,
          playlist: [
            {
              type: 'image',
              src: '/images/slide1.jpg',
              duration: 5,
              priority: 1
            },
            {
              type: 'video',
              src: '/videos/video1.mp4',
              duration: 10,
              priority: 1,
              hasAudio: false
            }
          ],
          zIndex: 1
        },
        {
          id: 'ticker-1',
          type: 'ticker',
          x: 0,
          y: 980,
          width: 1920,
          height: 100,
          text: 'Noticias en tiempo real desde la base de datos',
          speed: 50,
          fontSize: 24,
          fontColor: '#ffffff',
          backgroundColor: '#dc2626',
          transparentBackground: false,
          zIndex: 2
        }
      ]
    }]
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Player template={template} />
    </div>
  )
}

export default MyPlayer

Player Dinámico - Desde Base de Datos

import React, { useState, useEffect } from 'react'
import { Player, type Template } from 'kaas-builder-ds'
import 'kaas-builder-ds/dist/kaas-builder-ds.css'

function DatabasePlayer({ templateId }: { templateId: string }) {
  const [template, setTemplate] = useState<Template | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    const loadTemplate = async () => {
      try {
        setLoading(true)
        
        // Cargar desde tu API
        const response = await fetch(`/api/templates/${templateId}`)
        
        if (!response.ok) {
          throw new Error('Template no encontrado')
        }
        
        const templateData: Template = await response.json()
        setTemplate(templateData)
        
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Error desconocido')
      } finally {
        setLoading(false)
      }
    }

    if (templateId) {
      loadTemplate()
    }
  }, [templateId])

  // Estados de carga
  if (loading) {
    return (
      <div style={{ 
        width: '100vw', 
        height: '100vh', 
        display: 'flex', 
        alignItems: 'center', 
        justifyContent: 'center' 
      }}>
        <div>Cargando presentación...</div>
      </div>
    )
  }

  if (error) {
    return (
      <div style={{ 
        width: '100vw', 
        height: '100vh', 
        display: 'flex', 
        alignItems: 'center', 
        justifyContent: 'center' 
      }}>
        <div>Error: {error}</div>
      </div>
    )
  }

  if (!template) {
    return (
      <div style={{ 
        width: '100vw', 
        height: '100vh', 
        display: 'flex', 
        alignItems: 'center', 
        justifyContent: 'center' 
      }}>
        <div>Presentación no encontrada</div>
      </div>
    )
  }

  // Renderizar el player
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Player template={template} />
    </div>
  )
}

export default DatabasePlayer

Player con Auto-actualización

function LivePlayer({ templateId }: { templateId: string }) {
  const [template, setTemplate] = useState<Template | null>(null)

  useEffect(() => {
    const loadTemplate = async () => {
      try {
        const response = await fetch(`/api/templates/${templateId}`)
        const templateData = await response.json()
        setTemplate(templateData)
      } catch (error) {
        console.error('Error cargando template:', error)
      }
    }

    // Cargar inicial
    loadTemplate()

    // Auto-actualizar cada 30 segundos
    const interval = setInterval(loadTemplate, 30000)

    return () => clearInterval(interval)
  }, [templateId])

  if (!template) {
    return <div>Cargando...</div>
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Player template={template} />
    </div>
  )
}

Rutas para Player

// En tu App.tsx principal
import React from 'react'
import { BrowserRouter as Router, Routes, Route, useParams } from 'react-router-dom'
import DatabasePlayer from './components/DatabasePlayer'

function PlayerRoute() {
  const { templateId } = useParams<{ templateId: string }>()
  return <DatabasePlayer templateId={templateId!} />
}

function App() {
  return (
    <Router>
      <Routes>
        {/* Tu aplicación principal */}
        <Route path="/" element={<HomePage />} />
        <Route path="/editor" element={<EditorPage />} />
        
        {/* Player routes */}
        <Route path="/display/:templateId" element={<PlayerRoute />} />
        <Route path="/display" element={<DatabasePlayer templateId="default" />} />
      </Routes>
    </Router>
  )
}

export default App

URLs de ejemplo:

  • https://tuapp.com/display/template-123 - Mostrar template específico
  • https://tuapp.com/display - Mostrar template por defecto

API Backend de Ejemplo

// Express.js endpoint
app.get('/api/templates/:id', async (req, res) => {
  try {
    const { id } = req.params
    
    // Buscar en tu base de datos
    const template = await db.collection('templates').findOne({ id })
    
    if (!template) {
      return res.status(404).json({ error: 'Template no encontrado' })
    }
    
    res.json(template)
  } catch (error) {
    res.status(500).json({ error: 'Error del servidor' })
  }
})

// Endpoint para actualizar
app.put('/api/templates/:id', async (req, res) => {
  try {
    const { id } = req.params
    const { template } = req.body
    
    await db.collection('templates').updateOne(
      { id }, 
      { 
        $set: { 
          template,
          updatedAt: new Date() 
        }
      }
    )
    
    res.json({ success: true })
  } catch (error) {
    res.status(500).json({ error: 'Error al actualizar' })
  }
})

📚 Referencia de Tipos

import type {
  // 🏗️ Componentes principales
  App,                // Editor completo controlado externamente
  Player,             // Reproductor para mostrar templates
  
  // 🎨 Componentes individuales (para uso avanzado)
  Canvas,             // Canvas de edición
  ComponentsPanel,    // Panel de componentes (derecha)
  PropertiesPanel,    // Panel de propiedades (izquierda)
  Toolbar,            // Barra de herramientas (superior)
  JsonPreview,        // Modal de vista previa JSON
  TemplateSelector,   // Modal selector de plantillas
  
  // 📦 Tipos de datos
  Template,           // Template completo con metadata
  Screen,             // Pantalla individual
  CanvasElement,      // Elemento base (union type)
  
  // 🎛️ Elementos específicos
  SliderElement,      // Slider con playlist
  ClockElement,       // Reloj digital
  TickerElement,      // Cintillo de noticias
  PlaylistItem,       // Item de imagen/video en slider
  
  // ⚙️ Configuración
  Resolution,         // Resolución de pantalla (1920x1080, 1280x720)
  AppProps            // Props para componente App
} from 'kaas-builder-ds'

Interface Principal - Template

interface Template {
  customer: string          // Cliente/proyecto
  resourcePathImg: string   // Ruta base para imágenes
  resourceDownload: string  // Ruta de descarga de recursos
  destDownload: string      // Destino de descarga
  useTracking: boolean      // Habilitar tracking/analytics
  screens: Screen[]         // Array de pantallas (usualmente 1)
}

Interface de Pantalla

interface Screen {
  id: string              // ID único de pantalla
  width: number           // Ancho en píxeles (1920 o 1280)
  height: number          // Alto en píxeles (1080 o 720)
  backgroundColor: string // Color de fondo (hex)
  backgroundImage: string // URL de imagen de fondo
  elements: CanvasElement[] // Array de elementos en la pantalla
}

Tipos de Elementos

// Slider con playlist de medios
interface SliderElement {
  id: string
  type: 'slider'
  x: number              // Posición X
  y: number              // Posición Y  
  width: number          // Ancho
  height: number         // Alto
  playlist: PlaylistItem[] // Lista de imágenes/videos
  zIndex: number         // Orden de capas
}

// Reloj digital
interface ClockElement {
  id: string
  type: 'clock'
  x: number
  y: number
  width: number
  height: number
  format: '12h' | '24h'           // Formato de hora
  showDate: boolean               // Mostrar fecha
  fontSize: number                // Tamaño de fuente
  fontColor: string               // Color de texto
  backgroundColor: string         // Color de fondo
  transparentBackground?: boolean // Fondo transparente
  zIndex: number
}

// Cintillo de noticias
interface TickerElement {
  id: string
  type: 'ticker'
  x: number
  y: number
  width: number
  height: number
  text: string                    // Texto del cintillo
  speed: number                   // Velocidad de movimiento (px/s)
  fontSize: number                // Tamaño de fuente
  fontColor: string               // Color de texto
  backgroundColor: string         // Color de fondo
  transparentBackground?: boolean // Fondo transparente
  zIndex: number
}

// Item de playlist (imagen o video)
interface PlaylistItem {
  type: 'image' | 'video'
  src: string           // URL del archivo
  duration: number      // Duración en segundos
  priority: number      // Prioridad de reproducción
  hasAudio?: boolean    // Solo para videos
}

🎨 Personalización de Estilos

CSS Variables Disponibles

/* En tu CSS global o componente */
:root {
  /* 🎨 Canvas */
  --kaas-canvas-bg: #ffffff;
  --kaas-canvas-grid: #f3f4f6;
  --kaas-canvas-border: #e5e7eb;
  
  /* 🖱️ Elementos seleccionados */
  --kaas-selected-border: #3b82f6;
  --kaas-selected-handles: #1d4ed8;
  
  /* 📋 Paneles */
  --kaas-panel-bg: #f9fafb;
  --kaas-panel-border: #e5e7eb;
  --kaas-panel-text: #1f2937;
  
  /* 🔘 Botones */
  --kaas-button-primary: #3b82f6;
  --kaas-button-secondary: #6b7280;
  --kaas-button-hover: #2563eb;
  
  /* 🌙 Modo oscuro (opcional) */
  --kaas-dark-bg: #1f2937;
  --kaas-dark-text: #f9fafb;
  --kaas-dark-border: #374151;
}

Tema Personalizado

function MyAppWithCustomTheme() {
  const [template, setTemplate] = useState<Template>(/* ... */)

  return (
    <div className="custom-kaas-theme">
      <App 
        template={template}
        onTemplateChange={setTemplate}
      />
    </div>
  )
}
/* custom-theme.css */
.custom-kaas-theme {
  --kaas-canvas-bg: #0f172a;
  --kaas-panel-bg: #1e293b;
  --kaas-panel-text: #e2e8f0;
  --kaas-selected-border: #06b6d4;
}

🚀 Ejemplo Completo de Integración

// MiAplicacion.tsx - Ejemplo completo
import React, { useState, useEffect } from 'react'
import { App, Player, type Template } from 'kaas-builder-ds'
import 'kaas-builder-ds/dist/kaas-builder-ds.css'
import './custom-theme.css'

interface TemplateResponse {
  id: string
  template: Template
  createdAt: string
  updatedAt: string
}

function MiAplicacion() {
  const [template, setTemplate] = useState<Template>({
    customer: 'Mi Empresa',
    resourcePathImg: '/uploads/images/',
    resourceDownload: '/downloads/',
    destDownload: '/exports/',
    useTracking: true,
    screens: [{
      id: 'main-screen',
      width: 1920,
      height: 1080,
      backgroundColor: '#ffffff',
      backgroundImage: '',
      elements: []
    }]
  })

  const [isSaving, setIsSaving] = useState(false)
  const [isPreviewMode, setIsPreviewMode] = useState(false)

  // Cargar template inicial (opcional)
  useEffect(() => {
    const loadInitialTemplate = async () => {
      try {
        const response = await fetch('/api/templates/current')
        if (response.ok) {
          const data: TemplateResponse = await response.json()
          setTemplate(data.template)
        }
      } catch (error) {
        console.log('No hay template inicial, usando vacío')
      }
    }

    loadInitialTemplate()
  }, [])

  // Callback para cambios en tiempo real
  const handleTemplateChange = (newTemplate: Template) => {
    setTemplate(newTemplate)
    
    // Opcional: Auto-guardar después de 2 segundos sin cambios
    // implementarAutoSave(newTemplate)
  }

  // Guardar al hacer clic en botón "Guardar"
  const handleSaveTemplate = async (template: Template) => {
    setIsSaving(true)
    try {
      const response = await fetch('/api/templates', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('authToken')}`
        },
        body: JSON.stringify({
          template,
          metadata: {
            savedAt: new Date().toISOString(),
            version: '1.0'
          }
        })
      })

      if (!response.ok) throw new Error('Error al guardar')

      const result = await response.json()
      alert(`✅ Template guardado con ID: ${result.id}`)
      
    } catch (error) {
      alert('❌ Error al guardar: ' + (error as Error).message)
    } finally {
      setIsSaving(false)
    }
  }

  // Preview en nueva ventana
  const handlePreview = (template: Template) => {
    const previewData = encodeURIComponent(JSON.stringify(template))
    const previewUrl = `/preview.html?data=${previewData}`
    
    window.open(
      previewUrl, 
      'preview',
      'width=1920,height=1080,menubar=no,toolbar=no,status=no'
    )
  }

  return (
    <div className="mi-aplicacion">
      <header style={{ padding: '1rem', borderBottom: '1px solid #e5e7eb' }}>
        <h1>Mi Editor de Presentaciones</h1>
        <div style={{ display: 'flex', gap: '1rem', marginTop: '1rem' }}>
          <button 
            onClick={() => setIsPreviewMode(!isPreviewMode)}
            style={{ padding: '0.5rem 1rem' }}
          >
            {isPreviewMode ? 'Volver a Editor' : 'Modo Preview'}
          </button>
          
          {isSaving && <span>Guardando...</span>}
        </div>
      </header>

      <main style={{ height: 'calc(100vh - 120px)' }}>
        {isPreviewMode ? (
          // Modo preview
          <Player template={template} />
        ) : (
          // Modo editor
          <App
            template={template}
            onTemplateChange={handleTemplateChange}
            onExportTemplate={handleSaveTemplate}
            onPreviewTemplate={handlePreview}
          />
        )}
      </main>
    </div>
  )
}

export default MiAplicacion

📋 Resumen de Arquitectura

✅ LO QUE HACE la librería:

  • ✅ Renderiza la interfaz de edición
  • ✅ Maneja interacciones del usuario (drag, resize, etc.)
  • ✅ Proporciona callbacks cuando hay cambios
  • ✅ Muestra selector automático cuando está vacío
  • ✅ Reproduce templates en el Player

❌ LO QUE NO HACE la librería:

  • ❌ NO maneja estado interno del template
  • ❌ NO guarda automáticamente
  • ❌ NO hace llamadas a APIs
  • ❌ NO maneja autenticación
  • ❌ NO persiste datos

🔄 FLUJO DE CONTROL:

  1. Tu app → define template inicial
  2. Librería → renderiza UI con el template
  3. Usuario → interactúa con la UI
  4. Librería → ejecuta callback onTemplateChange
  5. Tu app → recibe nuevo template y decide qué hacer
  6. Tu app → actualiza su estado con el nuevo template
  7. Librería → re-renderiza con el nuevo template

💡 Consejos de Implementación

Auto-guardado Inteligente

function useAutoSave(template: Template) {
  const timeoutRef = useRef<NodeJS.Timeout>()

  const triggerAutoSave = useCallback(async (template: Template) => {
    try {
      await fetch('/api/templates/autosave', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(template)
      })
      console.log('Auto-guardado exitoso')
    } catch (error) {
      console.error('Error en auto-guardado:', error)
    }
  }, [])

  const scheduleAutoSave = useCallback((template: Template) => {
    // Cancelar auto-guardado anterior
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current)
    }

    // Programar nuevo auto-guardado en 3 segundos
    timeoutRef.current = setTimeout(() => {
      triggerAutoSave(template)
    }, 3000)
  }, [triggerAutoSave])

  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current)
      }
    }
  }, [])

  return scheduleAutoSave
}

// Uso:
function MyApp() {
  const [template, setTemplate] = useState<Template>(/* ... */)
  const autoSave = useAutoSave(template)

  const handleTemplateChange = (newTemplate: Template) => {
    setTemplate(newTemplate)
    autoSave(newTemplate) // Auto-guardar después de cambios
  }

  // ... resto del componente
}

Control de Versiones

interface TemplateVersion {
  id: string
  template: Template
  version: number
  createdAt: string
  description: string
}

function MyAppWithVersions() {
  const [currentTemplate, setCurrentTemplate] = useState<Template>()
  const [versions, setVersions] = useState<TemplateVersion[]>([])

  const saveVersion = async (template: Template, description: string) => {
    const version: TemplateVersion = {
      id: crypto.randomUUID(),
      template,
      version: versions.length + 1,
      createdAt: new Date().toISOString(),
      description
    }

    // Guardar en backend
    await fetch('/api/templates/versions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(version)
    })

    setVersions([...versions, version])
  }

  const loadVersion = (version: TemplateVersion) => {
    setCurrentTemplate(version.template)
  }

  // ... resto del componente
}

🎯 Esta librería está diseñada para ser completamente controlada desde tu frontend. Tú decides cuándo y cómo guardar, cargar y manejar los templates.