# DASH_FRAMEWORK_GUIDE.md

## 📚 Introducción a Dash para Desarrolladores Web

Esta guía explica las **diferencias fundamentales** entre el desarrollo con Dash y el desarrollo web tradicional (HTML/CSS/JavaScript).

## 🎯 Conceptos Fundamentales

### Dash NO es HTML/CSS/JavaScript Tradicional

**Diferencia clave**: Dash genera HTML desde Python en el servidor. No manipulas DOM directamente.

```python
# ❌ Pensamiento web tradicional (NO funciona en Dash)
# "Voy a usar JavaScript para cambiar el DOM"
# document.getElementById('mi-div').innerHTML = 'Nuevo contenido'

# ✅ Pensamiento Dash (correcto)
# "Voy a usar un callback Python para actualizar el componente"
@callback(
    Output('mi-div', 'children'),
    Input('mi-boton', 'n_clicks')
)
def actualizar_contenido(n_clicks):
    return 'Nuevo contenido'
```

### Arquitectura de Componentes

#### Jerarquía de Componentes
```python
# En Dash, todo es un árbol de componentes Python
app.layout = html.Div([  # Componente raíz
    html.H1('Título'),    # Hijo 1
    dcc.Graph(...),       # Hijo 2
    html.Div([            # Hijo 3 con sus propios hijos
        html.P('Texto'),
        dbc.Button('Click')
    ])
])
```

#### Componentes vs HTML
```python
# HTML tradicional
# <div class="container">
#   <h1>Título</h1>
#   <p id="parrafo">Texto</p>
# </div>

# Equivalente en Dash
html.Div(
    className='container',
    children=[
        html.H1('Título'),
        html.P('Texto', id='parrafo')
    ]
)
```

## 🔄 Estado y Reactividad

### Estado en Dash vs Frontend Tradicional

**Web Tradicional**: Estado en el cliente (JavaScript variables, localStorage, etc.)
**Dash**: Estado en el servidor (Python) o en dcc.Store (cliente temporal)

```python
# ✅ Estado en servidor (persistente durante sesión)
@callback(
    Output('resultado', 'children'),
    Input('entrada', 'value'),
    State('estado-servidor', 'data')  # Estado mantenido en servidor
)
def procesar(entrada, estado_actual):
    nuevo_estado = calcular(entrada, estado_actual)
    return nuevo_estado

# ✅ Estado en cliente (dcc.Store)
layout = html.Div([
    dcc.Store(id='estado-cliente'),  # Almacena en navegador
    dcc.Input(id='entrada'),
    html.Div(id='salida')
])
```

### Callbacks: El Corazón de Dash

Los callbacks son funciones Python que se ejecutan cuando cambian las propiedades de los componentes.

```python
# Anatomía de un callback
@callback(
    Output('componente-salida', 'propiedad'),  # Qué actualizar
    [Input('componente-entrada', 'propiedad')],  # Qué dispara la actualización
    [State('componente-estado', 'propiedad')],  # Datos adicionales (no disparan)
    prevent_initial_call=True  # No ejecutar al cargar
)
def mi_callback(valor_input, valor_state):
    # Lógica Python pura
    resultado = procesar(valor_input, valor_state)
    return resultado
```

## 🎨 Estilos: CSS vs Styles en Dash

### Especificidad y Prioridad

```python
# Orden de prioridad (de menor a mayor):
# 1. CSS externo (assets/style.css)
# 2. Clases Bootstrap (className='btn btn-primary')
# 3. Estilos inline (style={'color': 'red'})

# ❌ PROBLEMA: CSS externo puede ser sobrescrito
# assets/style.css: .mi-clase { display: none; }
html.Div(className='mi-clase')  # Puede no funcionar si Bootstrap interfiere

# ✅ SOLUCIÓN: Estilos inline para comportamiento crítico
html.Div(style={'display': 'none' if ocultar else 'block'})  # Siempre funciona
```

### Responsive Design en Dash

```python
# ❌ Media queries CSS tradicionales pueden fallar con estilos inline

# ✅ Usar sistema de grid de Bootstrap
dbc.Row([
    dbc.Col(
        contenido,
        width=12,    # móvil: ancho completo
        md=6,        # tablet: medio ancho
        lg=4         # desktop: tercio de ancho
    )
])
```

## ⚡ Performance: Cliente vs Servidor

### Diferencias Clave de Performance

**Web Tradicional**:
- JavaScript ejecuta en cliente
- Rápido para interacciones simples
- Puede ser lento con mucha data

**Dash**:
- Python ejecuta en servidor
- Red latency en cada interacción
- Mejor para procesamiento pesado

```python
# ⚠️ ANTIPATRÓN: Callback muy frecuente
@callback(
    Output('salida', 'children'),
    Input('input-texto', 'value')  # Se dispara en CADA tecla
)
def actualizar(texto):
    return procesar_pesado(texto)  # Sobrecarga el servidor

# ✅ MEJOR: Usar debounce
dcc.Input(
    id='input-texto',
    debounce=True  # Espera a que usuario termine de escribir
)

# ✅ MEJOR AÚN: Botón explícito para procesar
dbc.Button('Procesar', id='btn-procesar')
```

## 🔧 Debugging: Diferente a Web Tradicional

### Dónde Buscar Errores

1. **Backend (Python)**: Consola donde ejecutas la app
2. **Frontend (Dash/React)**: Browser console (F12)
3. **Callbacks**: Ambos lugares

```python
# ✅ Debugging efectivo
import structlog
logger = structlog.get_logger(__name__)

@callback(...)
def mi_callback(valor):
    logger.debug("callback_inicio", valor=valor)  # Log servidor

    try:
        resultado = procesar(valor)
        logger.info("callback_exito", resultado=resultado)
        return resultado
    except Exception as e:
        logger.error("callback_error", error=str(e))
        return "Error: Ver logs"  # Usuario ve esto
```

### Errores Comunes y Soluciones

```python
# ERROR 1: "ID not found in layout"
# Causa: Callback referencia ID que no existe
# Solución: python frontend/utils/validate_callbacks.py

# ERROR 2: "Duplicate callback outputs"
# Causa: Dos callbacks actualizan mismo Output
# Solución: Usar dcc.Store intermedio o allow_duplicate=True

# ERROR 3: "Callback error updating X"
# Causa: Callback retorna tipo incorrecto
# Solución: Verificar tipo de retorno esperado
```

## 📊 Datos: Servidor vs Cliente

### Flujo de Datos en Dash

```
Usuario → Input → Callback (Servidor) → Output → Usuario
         ↑                                        ↓
         └────────────────────────────────────────┘
```

### Manejo de Datos Grandes

```python
# ❌ MALO: Pasar grandes datasets por callbacks
@callback(
    Output('tabla', 'data'),
    Input('boton', 'n_clicks')
)
def cargar_todo():
    return df.to_dict('records')  # 100MB de datos!

# ✅ BUENO: Paginar o filtrar en servidor
@callback(
    Output('tabla', 'data'),
    [Input('pagina', 'active_page'),
     Input('filtro', 'value')]
)
def cargar_pagina(pagina, filtro):
    df_filtrado = df[df['columna'].contains(filtro)]
    inicio = (pagina - 1) * 100
    fin = inicio + 100
    return df_filtrado.iloc[inicio:fin].to_dict('records')
```

## 🚫 Antipatrones Comunes de Dash

### 1. Pensar en Manipulación DOM
```python
# ❌ NO puedes hacer esto
# "Quiero agregar un elemento dinámicamente con JavaScript"

# ✅ En su lugar: Callback que retorna componentes
@callback(
    Output('contenedor', 'children'),
    Input('agregar-btn', 'n_clicks'),
    State('contenedor', 'children')
)
def agregar_elemento(n_clicks, children_actual):
    if n_clicks:
        children_actual = children_actual or []
        nuevo_elemento = html.Div(f'Elemento {n_clicks}')
        children_actual.append(nuevo_elemento)
    return children_actual
```

### 2. Callbacks Síncronos Pesados
```python
# ❌ Bloquea toda la app
@callback(...)
def proceso_lento():
    time.sleep(30)  # BLOQUEA TODO
    return resultado

# ✅ Usar background callbacks o dcc.Interval
from dash import DiskcacheManager
import diskcache
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)

@callback(
    ...,
    background=True,
    manager=background_callback_manager
)
def proceso_lento():
    # Ahora no bloquea
    return resultado
```

### 3. Estado Global Mutable
```python
# ❌ NUNCA usar variables globales mutables
datos_globales = []  # PELIGRO: Compartido entre usuarios!

@callback(...)
def actualizar():
    datos_globales.append(algo)  # BUG: Afecta a TODOS los usuarios

# ✅ Usar dcc.Store o base de datos
@callback(
    Output('store', 'data'),
    Input('trigger', 'value'),
    State('store', 'data')
)
def actualizar(trigger, datos_actuales):
    datos_actuales = datos_actuales or []
    datos_actuales.append(algo)
    return datos_actuales  # Específico por sesión
```

## 🎯 Mejores Prácticas

### 1. Organización de Código
```python
# ✅ Estructura recomendada
frontend/
├── app.py              # Inicialización
├── layouts/            # Layouts de páginas
│   ├── dashboard.py
│   └── admin.py
├── callbacks/          # Callbacks organizados
│   ├── dashboard_callbacks.py
│   └── admin_callbacks.py
└── components/         # Componentes reutilizables
    ├── kpi_card.py
    └── data_table.py
```

### 2. Callbacks Eficientes
```python
# ✅ Principios para callbacks eficientes
# 1. Mínima lógica en callbacks
# 2. Precalcular cuando sea posible
# 3. Cachear resultados frecuentes
# 4. Usar prevent_initial_call cuando apropiado
# 5. Evitar callbacks encadenados largos
```

### 3. Testing
```python
# ✅ Estrategia de testing
# 1. Unit tests para lógica Python pura
# 2. Integration tests para callbacks
# 3. E2E tests con Selenium/Playwright para flujos completos
```

## 📚 Recursos Adicionales

### Documentación Oficial
- [Dash Documentation](https://dash.plotly.com/)
- [Dash Bootstrap Components](https://dash-bootstrap-components.opensource.faculty.ai/)
- [Plotly Graphing](https://plotly.com/python/)

### Patrones Avanzados
- [Dash Enterprise Patterns](https://dash.plotly.com/performance)
- [Callback Patterns](https://dash.plotly.com/advanced-callbacks)
- [Clientside Callbacks](https://dash.plotly.com/clientside-callbacks)

### xFarma Específico
- Ver `CALLBACK_PATTERNS.md` para patrones de callbacks
- Ver `API_ENDPOINTS.md` para integración backend
- Ver `frontend/CLAUDE.md` para guías de desarrollo

## 🎓 Resumen: Mentalidad Dash

1. **No es manipulación DOM**: Es generación de componentes
2. **No es JavaScript**: Es Python ejecutando en servidor
3. **No es estado cliente**: Es estado servidor con callbacks
4. **No es CSS puro**: Es combinación de CSS + estilos inline
5. **No es async inmediato**: Es request/response con latencia

Pensar en Dash como una **aplicación Python que genera UI**, no como una **página web con interactividad**.

---
> Última actualización: 2025-01-29
> Guía fundamental para entender Dash vs desarrollo web tradicional
