npm package: @reinaldor507/kaas-builder-ds
Description: Kit de herramientas para crear presentaciones multimedia interactivas con React y API backend
Author: Shamuel Vargas
Latest version: 1.7.0

# 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

```bash
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

```tsx
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

```tsx
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

```tsx
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:

```tsx
// 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

```tsx
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"

```tsx
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

```tsx
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:

```tsx
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)

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
// 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

```typescript
// 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

```tsx
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

```tsx
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

```tsx
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

```tsx
// 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

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

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

  return (
    <div className="custom-kaas-theme">
      <App 
        template={template}
        onTemplateChange={setTemplate}
      />
    </div>
  )
}
```

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

```tsx
// 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

```tsx
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
}
```

---

## 🔗 Usar Endpoints Personalizados (Sin JSON Estáticos)

Si no quieres que el paquete cargue JSON estáticos, puedes pasar tus propios endpoints:

### Con App Component

```tsx
import React, { useState } from 'react'
import { App, type Template } from 'kaas-builder-ds'

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

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

  const handleExportTemplate = async (template: Template) => {
    try {
      // Enviar a tu propio endpoint
      const response = await fetch('https://tu-api.com/templates/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(template)
      })
      
      if (response.ok) {
        alert('✅ Template guardado')
      }
    } catch (error) {
      console.error('Error:', error)
    }
  }

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

export default MyAppWithEndpoints
```

### Con KaasBuilder Component (Integración Avanzada)

Si usas el componente `KaasBuilder` con integración API:

```tsx
import React from 'react'
import { KaasBuilder, type KaasBuilderConfig } from 'kaas-builder-ds'
import 'kaas-builder-ds/dist/kaas-builder-ds.css'

function MyAdvancedApp() {
  const config: KaasBuilderConfig = {
    // Configuración API base
    api: {
      baseURL: 'https://tu-api.com',
      entityId: 201,
      templateId: 29,
      timeout: 30000,
      headers: {
        'Authorization': `Bearer ${localStorage.getItem('token')}`
      }
    },

    // Endpoints personalizados (opcional)
    endpoints: {
      fetchTemplateUrl: 'https://tu-api.com/templates/fetch',
      updateTemplateUrl: 'https://tu-api.com/templates/update',
      fetchMediaFilesUrl: 'https://tu-api.com/media/list'
    },

    // Callbacks
    onTemplateChange: (template) => {
      console.log('Template cambió:', template)
    },

    onTemplateExport: async (template) => {
      const response = await fetch('https://tu-api.com/templates/export', {
        method: 'POST',
        headers: { 
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        },
        body: JSON.stringify(template)
      })
      return response.json()
    },

    onError: (error) => {
      console.error('Error:', error)
      // Mostrar toast/modal de error
    },

    // Opciones de UI
    readOnly: false,
    showToolbar: true,
    showPropertiesPanel: true,
    showComponentsPanel: true
  }

  return (
    <KaasBuilder {...config} />
  )
}

export default MyAdvancedApp
```

### Estructura de Respuesta Esperada

El paquete espera que tu endpoint devuelva un `TemplateResponse`:

```typescript
interface TemplateResponse {
  id: number
  name: string
  status: 'ACTIVE' | 'INACTIVE' | 'DRAFT'
  costumer?: string
  resourcePathImg?: string
  resourceDownload?: string
  destDownload?: string
  useTracking?: boolean
  screens: Array<{
    name: string
    ref?: string
    showKeyboard: boolean
    style: Record<string, any>
    errorModal: Record<string, any>
    playlist: Array<{
      style: Record<string, any>
      resources: Array<{
        media: string        // URL de imagen/video
        time: number         // Duración en segundos
      }>
    }>
  }>
  screenSaver: any[]
  printerVoucherData: {
    copies: number
    printerName: string
  }
  inactiveTime: Record<string, any>
  keyboard: Record<string, any>
  outModal: Record<string, any>
  showModalProps: Record<string, any>
  kaasServices: Record<string, any>
}
```

### Ejemplo Backend (Node.js/Express)

```javascript
// Endpoint GET para traer template
app.get('/api/templates/fetch', async (req, res) => {
  try {
    const template = await Database.getTemplate(req.query.templateId)
    res.json({
      id: template.id,
      name: template.name,
      status: template.status,
      costumer: 'Mi Cliente',
      screens: template.screens,
      screenSaver: [],
      // ... resto de campos
    })
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

// Endpoint POST para guardar template
app.post('/api/templates/save', async (req, res) => {
  try {
    const template = req.body
    await Database.updateTemplate(template.id, template)
    res.json({ success: true, message: 'Template guardado' })
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})
```

---

### Control de Versiones

```tsx
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.**