# Sistema de Gráficas Reutilizables - Serendipia

Sistema modular de visualizaciones D3.js con configuración mínima y diseño consistente.

## 📋 Tabla de Contenidos

- [Introducción](#introducción)
- [Instalación](#instalación)
- [Uso Rápido](#uso-rápido)
- [Gráficas Disponibles](#gráficas-disponibles)
- [Módulos Core](#módulos-core)
- [Ejemplos](#ejemplos)
- [Formato de Datos CSV](#formato-de-datos-csv)
  - [Agregación de Datos antes de Graficar](#agregación-de-datos-antes-de-graficar)
- [Arquitectura](#arquitectura)
- [Configuración](#configuración)
  - [Personalización de Tooltips](#personalización-de-tooltips)
    - [Tooltip con Formato de Fechas](#tooltip-con-formato-de-fechas)
  - [Ejemplos de Colores](#ejemplos-de-colores)
  - [Cambiar Color de Serie Única](#cambiar-color-de-serie-única-sin-multi-serie)
  - [Posición del Label del Eje Y](#posición-del-label-del-eje-y)
  - [Líneas de Referencia](#líneas-de-referencia)
  - [Estadísticas Resumidas](#estadísticas-resumidas)
- [Referencia Completa de API](#referencia-completa-de-api)
  - [LineChart - Parámetros Disponibles](#linechart---parámetros-disponibles)
  - [BarChart - Parámetros Disponibles](#barchart---parámetros-disponibles)
  - [AreaChart - Parámetros Disponibles](#areachart---parámetros-disponibles)
  - [MapHexagon - Parámetros Disponibles](#maphexagon---parámetros-disponibles)
  - [MapEstatalLeaflet - Parámetros Disponibles](#mapestatalleflet---parámetros-disponibles)
  - [Tabla de Referencia Rápida](#tabla-de-referencia-rápida)

---

## Introducción

Este sistema proporciona gráficas reutilizables con:

✅ **Configuración mínima** - Solo declarar variables  
✅ **Carga desde CSV** - Todos los datos se importan de archivos CSV  
✅ **Diseño consistente** - Colores, tipografía y espaciado del tema Serendipia  
✅ **Fuente automática** - Genera pie de fuente + logo Serendipia con un parámetro  
✅ **Responsive** - Se adaptan automáticamente al contenedor  
✅ **Interactivas** - Tooltips, filtros, zoom, animaciones  
✅ **Accesibles** - ARIA labels, navegación por teclado, lectores de pantalla  
✅ **D3.js v7** - API moderna con promises y ES6 modules  

---

## Instalación

### WordPress (Recomendado para este tema)

**Paso 1:** Agrega el shortcode en tu post:
```
[chartsSystem]
```

**Paso 2:** Escribe tu gráfica en el editor HTML:
```html
<div id="mi-grafica"></div>

 <script src="/wp-content/themes/serendipia/js/d3.v7.min.js"></script>  
<script type="module">
  import { createLineChart } from '/wp-content/themes/serendipia/js/D3_helpers/charts/LineChart.js';
  
  // IMPORTANTE: Los datos se cargan desde CSV
  (async function() {
    const datosPath = '/wp-content/themes/serendipia/js/viz/MI_CARPETA/data/';
    const datos = await d3.csv(datosPath + 'datos.csv', d => ({
      fecha: new Date(d.fecha),
      valor: +d.valor,
    }));

    createLineChart({
      container: '#mi-grafica',
      data: datos,
      x: d => d.fecha,
      y: d => d.valor,
      title: 'Mi Gráfica',
      source: 'INEGI, Estadísticas 2024',  // ← Genera automáticamente div de fuente + logo
      showPoints: true,                     // ← Mostrar puntos para facilitar hover
    });
  })();
</script>
```

Ver [INTEGRACION-WORDPRESS.md](INTEGRACION-WORDPRESS.md) para más ejemplos y opciones.

---

### Standalone (fuera de WordPress)

### Opción 1: Importar todo el sistema

```javascript
import * as Charts from './D3_helpers/charts/index.js';

const chart = Charts.createLineChart({
  container: '#mi-grafica',
  data: misDatos,
  x: d => d.fecha,
  y: d => d.valor,
});
```

### Opción 2: Importar solo lo necesario

```javascript
import { createLineChart } from './D3_helpers/charts/LineChart.js';
import { createBarChart } from './D3_helpers/charts/BarChart.js';
```

### Incluir CSS base

```html
<link rel="stylesheet" href="./css/charts-base.css">
```

---

## Uso Rápido

### Gráfica de Líneas

```javascript
import { createLineChart } from './D3_helpers/charts/LineChart.js';

// Cargar datos desde CSV (patrón recomendado)
(async function() {
  const datosPath = '/wp-content/themes/serendipia/js/viz/MI_CARPETA/data/';
  const datos = await d3.csv(datosPath + 'temperatura.csv', d => ({
    fecha: new Date(d.fecha),
    temperatura: +d.temperatura,
  }));

  const grafica = createLineChart({
    container: '#grafica',          // ← div donde se dibuja
    data: datos,                     // ← tus datos (desde CSV)
    x: d => d.fecha,                 // ← accessor para X
    y: d => d.temperatura,           // ← accessor para Y
    title: 'Temperatura 2024',       // ← título (se inserta como <h3> encima de la gráfica)
    source: 'CONAGUA, 2024',         // ← fuente (genera div automáticamente)
    showPoints: true,                // ← ⭐ Mostrar puntos (facilita hover)
    xAxis: { label: 'Mes', format: '%b %Y' },
    yAxis: { label: 'Temperatura (°C)', format: ',.1f' },
  });
})();
```

### Gráfica de Barras

```javascript
import { createBarChart } from './D3_helpers/charts/BarChart.js';

// Cargar datos desde CSV (patrón recomendado)
(async function() {
  const datosPath = '/wp-content/themes/serendipia/js/viz/MI_CARPETA/data/';
  const datos = await d3.csv(datosPath + 'ventas.csv', d => ({
    mes: d.mes,
    ventas: +d.ventas,
  }));

  const grafica = createBarChart({
    container: '#grafica',
    data: datos,
    x: d => d.mes,
    y: d => d.ventas,
    title: 'Ventas mensuales',
    source: 'Datos internos',        // ← fuente (genera div automáticamente)
    orientation: 'vertical',         // ← 'vertical' o 'horizontal'
    showValues: true,                // ← mostrar números en barras
  });
})();
```

---

## Gráficas Disponibles

### ✅ Implementadas

| Gráfica | Archivo | Uso Principal |
|---------|---------|---------------|
| **LineChart** | `LineChart.js` | Series temporales, tendencias |
| **BarChart** | `BarChart.js` | Comparaciones, rankings |
| **AreaChart** | `AreaChart.js` | Volúmenes acumulados |
| **MapHexagon** | `MapHexagon.js` | Mapas electorales, regionales |
| **MapEstatalLeaflet** | `MapEstatalLeaflet.js` | Choroplet estatal interactivo |

### 🔜 Próximamente (FASE 4)

| Gráfica | Archivo | Uso Principal |
|---------|---------|---------------|
| **MapMunicipalLeaflet** | `MapMunicipalLeaflet.js` | Choroplet municipal interactivo |
| **Heatmap** | `Heatmap.js` | Matrices de calor, correlaciones |
| **SankeyDiagram** | `SankeyDiagram.js` | Flujos, migraciones |

---

## Módulos Core

Sistema de composición con 9 módulos base:

### 1. **theme.js** - Sistema de diseño

```javascript
import theme from './theme.js';

theme.colors.primary;           // '#efca33' (amarillo Serendipia)
theme.colors.categorical;       // ['#e74c3c', '#3498db', '#2ecc71', ...]
theme.typography.sizes.large;   // '16px'
theme.spacing.margin.default;   // { top: 20, right: 30, bottom: 40, left: 60 }
```

### 2. **svg.js** - Creación de SVG responsive

```javascript
import { createSvg } from './core/svg.js';

const { svg, g, width, height } = createSvg({
  container: '#grafica',
  margin: { top: 20, right: 30, bottom: 40, left: 60 },
  width: 800,
  height: 400,
});
```

### 3. **tooltip.js** - Tooltips unificados

```javascript
import { createTooltip, applyTooltip } from './core/tooltip.js';

// Método 1: Aplicar tooltip automáticamente a una selección
applyTooltip(selection, d => `Valor: ${d.value}`);

// Método 2: Crear tooltip manualmente para mayor control
const tooltip = createTooltip({
  className: 'tooltip-sp',
  offsetX: 10,
  offsetY: -20,
});

// Mostrar tooltip
selection.on('mouseover', (event, d) => {
  tooltip.show(`Valor: ${d.value}`, event);
});

// Ocultar tooltip
selection.on('mouseout', () => {
  tooltip.hide();
});

// Personalizar contenido del tooltip en gráficas
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  tooltipContent: (d) => `
    <strong>${d.categoria}</strong><br>
    Fecha: ${d.fecha.toLocaleDateString('es-MX', { month: 'long', year: 'numeric' })}<br>
    Valor: ${d3.format(',.2f')(d.valor)}
  `,  // ← Contenido personalizado del tooltip
});
```

### 4. **scales.js** - Escalas D3 con defaults

```javascript
import { createXScale, createYScale } from './core/scales.js';

const xScale = createXScale({
  type: 'time',
  domain: d3.extent(data, d => d.date),
  range: [0, width],
});
```

### 5. **axes.js** - Ejes con formato español

```javascript
import { createXAxis, timeFormatEs } from './core/axes.js';

createXAxis({
  container: g,
  scale: xScale,
  position: height,
  label: 'Mes',
  format: timeFormatEs('%b %Y'),  // ← 'ene. 2024'
});
```

### 6. **filters.js** - Filtros interactivos

```javascript
import { createFilters } from './core/filters.js';

const filters = createFilters({
  selectors: ['#year-select', '#category-select'],
  onChange: (values) => {
    // values = { '#year-select': '2024', '#category-select': 'A' }
    actualizarGrafica(values);
  },
});
```

### 7. **responsive.js** - Responsividad automática

```javascript
import { createResponsive } from './core/responsive.js';

const responsive = createResponsive({
  container: '#grafica',
  onResize: ({ width, height }) => {
    redibujarGrafica(width, height);
  },
  debounce: 150,
});
```

### 8. **animation.js** - Animaciones D3

```javascript
import { animatePath, animateBars } from './core/animation.js';

// Animar líneas (path drawing)
animatePath(lines, { duration: 1000 });

// Animar barras (desde altura 0)
animateBars(bars, d => height - yScale(d.value));
```

### 9. **accessibility.js** - Accesibilidad ARIA

```javascript
import { makeAccessible, createDataTable, addSource } from './core/accessibility.js';

makeAccessible('#grafica svg', {
  title: 'Ventas 2024',
  description: 'Gráfica que muestra las ventas mensuales del año 2024',
});

createDataTable('grafica', data, {
  columns: [
    { key: 'month', label: 'Mes' },
    { key: 'sales', label: 'Ventas' },
  ],
});

// Agregar fuente con logo de Serendipia (generado automáticamente por LineChart/BarChart)
addSource('#grafica', 'INEGI, Estadísticas 2024');
```

### 10. **export.js** - Exportación PNG/SVG/CSV

```javascript
import { exportPNG, exportSVG, exportCSV } from './core/export.js';

// Exportar gráfica como PNG
exportPNG('#grafica svg', {
  filename: 'ventas-2024.png',
  scale: 2,
  backgroundColor: 'white',
});

// Exportar datos como CSV
exportCSV(data, {
  filename: 'datos.csv',
  columns: ['month', 'sales'],
});
```

---

## Ejemplos

### 📁 Archivos de Ejemplos

- **`examples/ejemplos-basicos.js`** - 10 ejemplos con datos en arrays
- **`examples/ejemplos-con-csv.js`** - 10 ejemplos cargando datos desde CSV (⭐ RECOMENDADO)
- **`examples/ejemplo-wordpress-post.html`** - Demo completa simulando post de WordPress
- **`examples/template-wordpress.html`** - Plantilla lista para copiar/pegar
- **`examples/ejemplo-label-yaxis.html`** - Comparación de posiciones del label del eje Y

### Ejemplos incluidos:

1. Líneas simple (serie única)
2. Líneas multi-serie
3. Barras verticales
4. Barras horizontales
5. Barras agrupadas
6. Con filtros y responsive
7. Tooltip personalizado
8. Actualización dinámica
9. Exportar gráfica
10. Accesibilidad completa

**📝 Nota:** En WordPress, **siempre usa CSV** para cargar datos (ver `ejemplos-con-csv.js`)

---

## Formato de Datos CSV

### Estructura Recomendada

#### Para Gráficas de Líneas (Series Temporales)

```csv
fecha,valor,categoria
2024-01-01,1200,Producto A
2024-02-01,1500,Producto A
2024-01-01,800,Producto B
2024-02-01,950,Producto B
```

**Carga:**
```javascript
const datos = await d3.csv(datosPath + 'ventas.csv', d => ({
  fecha: new Date(d.fecha),    // ← Convertir a Date
  valor: +d.valor,              // ← Convertir a número
  categoria: d.categoria,       // ← String (para multi-serie)
}));
```

#### Para Gráficas de Barras (Categóricas)

```csv
categoria,ventas
Electrónica,1200
Ropa,800
Alimentos,950
```

**Carga:**
```javascript
const datos = await d3.csv(datosPath + 'ventas-categoria.csv', d => ({
  categoria: d.categoria,
  ventas: +d.ventas,
}));
```

### Ubicación de Archivos CSV

```
/wp-content/themes/serendipia/js/viz/
└── TU_CARPETA/
    └── data/
        ├── datos.csv
        ├── ventas.csv
        └── ...
```

**Ruta en código:**
```javascript
const datosPath = '/wp-content/themes/serendipia/js/viz/TU_CARPETA/data/';
```

### Agregación de Datos antes de Graficar

El sistema de gráficas recibe los datos **ya procesados**. Si tu CSV tiene datos mensuales
y quieres graficar por año (o cualquier otro agrupamiento), hay que transformarlos
con `d3.rollup()` antes de pasarlos al chart.

#### Una serie — suma por año

```javascript
d3.csv(datosPath + 'ingresos.csv', d => ({
  fecha:  new Date(d.Fecha),
  neto:   +d['Importe Neto'],
})).then(datos => {

  // Suma mensual → total anual
  const porAnio = d3.rollup(
    datos,
    rows => d3.sum(rows, d => d.neto),
    d => d.fecha.getFullYear()         // ← clave de agrupación
  );

  // Convertir Map a array y ordenar
  const datosAnuales = Array.from(porAnio, ([anio, total]) => ({
    anio: String(anio),
    total,
  })).sort((a, b) => a.anio - b.anio);

  createBarChart({
    container: '#grafica',
    data: datosAnuales,
    x: d => d.anio,
    y: d => d.total,
    yAxis: { format: '$,.0f' },
  });

});
```

#### Varias series — barras agrupadas por año

Cuando el CSV tiene varias columnas de valor (p. ej. bruto, descuentos, neto),
primero agrupa por año y luego convierte a **formato largo** (una fila por año×serie):

```javascript
d3.csv(datosPath + 'ingresos.csv', d => ({
  fecha:   new Date(d.Fecha),
  bruto:   +d['Importe bruto'],
  descuentos: +d['Descuentos'],
  neto:    +d['Importe Neto'],
})).then(datos => {

  // Agregar por año
  const porAnio = d3.rollup(
    datos,
    rows => ({
      bruto:      d3.sum(rows, d => d.bruto),
      descuentos: d3.sum(rows, d => d.descuentos),
      neto:       d3.sum(rows, d => d.neto),
    }),
    d => d.fecha.getFullYear()
  );

  // Convertir a formato largo (una fila por año + concepto)
  const datosLargos = [];
  porAnio.forEach((vals, anio) => {
    datosLargos.push({ anio: String(anio), concepto: 'Importe bruto',  valor: vals.bruto });
    datosLargos.push({ anio: String(anio), concepto: 'Descuentos',     valor: vals.descuentos });
    datosLargos.push({ anio: String(anio), concepto: 'Importe neto',   valor: vals.neto });
  });
  datosLargos.sort((a, b) => a.anio - b.anio);

  createBarChart({
    container: '#grafica',
    data: datosLargos,
    x: d => d.anio,
    y: d => d.valor,
    series: d => d.concepto,   // ← activa barras agrupadas
    type: 'grouped',           // o 'stacked'
    colors: ['#e74c3c', '#f39c12', '#3498db'],
    yAxis: { format: '$,.0f', labelPosition: 'top', label: 'Pesos' },
  });

});
```

> **Referencia rápida de `d3.rollup`:**  
> `d3.rollup(datos, fn_reducción, fn_clave)` → devuelve un `Map`.  
> Para convertirlo a array: `Array.from(map, ([clave, valor]) => ({ clave, valor }))`.

---

## Arquitectura

### Patrón de Composición

Las gráficas se construyen componiendo módulos core:

```
LineChart.js
├── theme.js          (colores, tipografía)
├── svg.js            (contenedor responsive)
├── tooltip.js        (interactividad)
├── scales.js         (mapeo datos → píxeles)
├── axes.js           (ejes con labels)
├── filters.js        (dropdowns, sliders)
├── responsive.js     (resize automático)
├── animation.js      (transiciones)
└── accessibility.js  (ARIA, keyboard)
```

### Ventajas vs Herencia

✅ Reutilizar solo los módulos necesarios  
✅ Fácil agregar funcionalidad sin modificar gráficas existentes  
✅ Testing más simple (módulos independientes)  
✅ Menor acoplamiento  

---

## Configuración

### Configuración Global (theme.js)

Modificar `js/D3_helpers/charts/theme.js`:

```javascript
export default {
  colors: {
    primary: '#efca33',        // ← cambiar color principal
    categorical: [...],        // ← paleta de colores
  },
  typography: {
    fontFamily: {
      headings: 'Titillium Web',
      body: 'Open Sans',
    },
  },
  spacing: {
    margin: {
      default: { top: 20, right: 30, bottom: 40, left: 60 },
    },
  },
};
```

### Configuración por Gráfica

Cada gráfica acepta configuración completa:

```javascript
createLineChart({
  // Datos (cargar desde CSV - ver ejemplos arriba)
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  series: d => d.categoria,      // ← multi-serie (opcional)
  
  // Visual
  title: 'Mi gráfica',
  source: 'INEGI, 2024',         // ← Genera automáticamente div de fuente + logo Serendipia
  margin: { top: 30, right: 40, bottom: 50, left: 70 },
  colors: ['#e74c3c', '#3498db'],
  showLegend: true,
  showPoints: true,              // ← Mostrar puntos en líneas (recomendado para hover)
  showGrid: true,
  
  // Ejes
  xAxis: {
    label: 'Fecha',
    format: '%b %Y',
    ticks: 6,
  },
  yAxis: {
    label: 'Valor ($)',
    format: ',.0f',
    ticks: 5,
  },
  
  // Interactividad
  animate: true,
  responsive: true,
  filters: ['#year-filter'],
  tooltipContent: d => `Custom: ${d.value}`,  // ← Personalizar contenido del tooltip
  
  // Accesibilidad
  description: 'Descripción para lectores de pantalla',
  showDataTable: true,
});
```

#### Ejemplos de Colores

**Serie única (sin `series`):**
```javascript
// El color se toma automáticamente de theme.colors.primary (#efca33)
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  // NO especificar 'series'
  // NO especificar 'colors' (se usa el color primario del tema)
});

// Para cambiar el color de serie única:
// Opción 1: Modificar theme.js globalmente
// Opción 2: Usar CSS para sobrescribir el color de las líneas
```

**Multi-serie con colores personalizados:**
```javascript
// Con 2 categorías
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  series: d => d.producto,       // ← Activa multi-serie
  colors: ['#e74c3c', '#3498db'], // ← Rojo y azul
});

// Con 3 categorías
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  series: d => d.region,
  colors: ['#e74c3c', '#3498db', '#2ecc71'], // ← Rojo, azul, verde
});

// Con 1 categoría pero usando colors (aplica el primer color)
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  series: d => d.categoria,      // ← Aunque solo haya una categoría
  colors: ['#9b59b6'],           // ← Morado
});
```

**BarChart funciona igual:**
```javascript
// Barras simples (sin series) - usa theme.colors.primary
createBarChart({
  container: '#grafica',
  data: datos,
  x: d => d.estado,
  y: d => d.poblacion,
  // Sin 'series' = color primario del tema
});

// Barras agrupadas con colores personalizados
createBarChart({
  container: '#grafica',
  data: datos,
  x: d => d.estado,
  y: d => d.poblacion,
  series: d => d.año,            // ← Multi-serie
  colors: ['#e74c3c', '#3498db'], // ← Colores personalizados
  type: 'grouped',
});
```

#### Cambiar Color de Serie Única (sin multi-serie)

Si quieres cambiar el color de una gráfica de serie única (sin `series`), tienes 3 opciones:

**Opción 1: Modificar el tema globalmente** (afecta todas las gráficas de serie única)
```javascript
// En theme.js
export default {
  colors: {
    primary: '#9b59b6',  // ← Cambiar de #efca33 a morado
    // ...
  }
};
```

**Opción 2: Usar multi-serie con una sola categoría** (para un color específico por gráfica)
```javascript
// Agregar una columna constante en tu CSV
const datos = await d3.csv(datosPath + 'datos.csv', d => ({
  fecha: new Date(d.fecha),
  valor: +d.valor,
  categoria: 'Serie 1',  // ← Constante para todos los registros
}));

createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  series: d => d.categoria,  // ← Activa el sistema de colores
  colors: ['#9b59b6'],       // ← Tu color personalizado
  showLegend: false,         // ← Ocultar leyenda (solo una categoría)
});
```

**Opción 3: CSS personalizado** (sobrescribir estilos)
```css
/* En tu CSS personalizado */
#mi-grafica .line {
  stroke: #9b59b6 !important;
}

#mi-grafica .point {
  fill: #9b59b6 !important;
}
```

#### Posición del Label del Eje Y

Por defecto, el label del eje Y aparece vertical a la izquierda. Puedes cambiarlo a horizontal arriba del eje:

**Label vertical a la izquierda (default):**
```javascript
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.ingresos,
  yAxis: {
    label: 'Ingresos (millones de pesos)',
    labelPosition: 'left',  // ← Default (puede omitirse)
  },
});
```

**Label horizontal arriba del eje:**
```javascript
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.ingresos,
  yAxis: {
    label: 'Ingresos (millones de pesos)',
    labelPosition: 'top',  // ← Horizontal arriba del eje Y
  },
});
// El margen superior se ajusta automáticamente para dar espacio al label
```

**También funciona en BarChart:**
```javascript
createBarChart({
  container: '#grafica',
  data: datos,
  x: d => d.estado,
  y: d => d.poblacion,
  yAxis: {
    label: 'Población (miles)',
    labelPosition: 'top',  // ← Horizontal arriba
  },
});
```

### Líneas de Referencia

Agrega líneas verticales u horizontales para señalar fechas, eventos o umbrales.

**Línea vertical** (marcar una fecha o momento):
```javascript
createLineChart({
  container: '#mi-grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  referenceLines: [
    {
      axis: 'x',
      value: new Date('2024-06-01'),
      label: 'Inicio de gobierno',
      color: '#e74c3c',
    }
  ],
});
```

**Línea horizontal** (marcar un umbral o promedio):
```javascript
referenceLines: [
  {
    axis: 'y',
    value: 500000,
    label: 'Promedio histórico',
    color: '#9b59b6',
  }
]
```

**Varias líneas combinadas:**
```javascript
referenceLines: [
  { axis: 'x', value: new Date('2023-03'), label: 'Decreto', color: '#e74c3c' },
  { axis: 'x', value: new Date('2023-09'), label: 'Reforma', color: '#e67e22' },
  { axis: 'y', value: 1000000, label: 'Meta anual', color: '#2ecc71', dashed: false },
]
```

**Opciones de cada línea:**

| Propiedad | Tipo | Default | Descripción |
|-----------|------|---------|-------------|
| `axis` | `'x'` \| `'y'` | — | Vertical (x) o horizontal (y) |
| `value` | Date \| number | — | Posición de la línea |
| `label` | string | `''` | Etiqueta junto a la línea |
| `color` | string | gris del tema | Color en hex |
| `dashed` | boolean | `true` | Línea punteada o sólida |

> Las líneas se dibujan **debajo de los datos** para no tapar las líneas de la gráfica.

---

### Estadísticas Resumidas

Muestra valores calculados sobre los datos (total, top N) en HTML encima de la gráfica,
configurable desde el mismo objeto de configuración del chart. Funciona igual en **LineChart**, **BarChart** y **AreaChart**.

**Valor estático (texto libre):**
```javascript
stats: [
  { label: 'Período', value: '2018–2021' },
]
```

**Total general:**
```javascript
createBarChart({
  container: '#mi-grafica',
  data: datosLargos,
  x: d => d.etiqueta,
  y: d => d.monto,
  stats: [
    {
      type: 'total',
      label: 'Total recaudado',
      format: '$,.0f',
    },
  ],
});
```

**Top N valores más altos:**
```javascript
stats: [
  {
    type: 'top',
    n: 3,                          // cuántos mostrar
    label: 'Top 3 meses',
    format: '$,.0f',
    labelFn: d => d.etiqueta,      // texto que aparece junto al valor
  }
]
```

**Combinados (total + top 3):**
```javascript
stats: [
  { type: 'total', label: 'Total recaudado', format: '$,.0f' },
  { type: 'top', n: 3, label: 'Top 3 meses', format: '$,.0f', labelFn: d => d.etiqueta },
]
```

**Total por categoría (datos con series):**
```javascript
stats: [
  { type: 'total', label: 'Total Registros', format: '$,.0f', filter: d => d.tipo === 'Registros' },
  { type: 'total', label: 'Total Sanciones', format: '$,.0f', filter: d => d.tipo === 'Sanciones' },
]
```

> Cuando se usa `filter`, el valor muestra automáticamente el porcentaje con respecto al total general entre paréntesis. Ej: `$1,234,567 (45.3%)`

**Promedio mensual (datos simples):**
```javascript
stats: [
  { type: 'average', label: 'Promedio mensual', format: '$,.0f' },
]
```

**Promedio mensual (datos con series / barras apiladas):**

En datos de formato largo (múltiples filas por período), usar `groupBy` para que primero sume por período y luego promedia:
```javascript
stats: [
  // Promedio del total por mes (registros + sanciones juntos)
  { type: 'average', label: 'Promedio mensual total', format: '$,.0f', groupBy: d => d.etiqueta },
  // Promedio por categoría (fila por fila, ya son datos de una sola serie)
  { type: 'average', label: 'Promedio mensual registros', format: '$,.0f', filter: d => d.tipo === 'Registros' },
  { type: 'average', label: 'Promedio mensual sanciones', format: '$,.0f', filter: d => d.tipo === 'Sanciones' },
]
```

> Sin `groupBy`, el promedio divide entre el número de filas. Con `groupBy`, agrupa primero (sumando), y luego promedia los totales por grupo — el resultado correcto para datos apilados.

**Opciones de cada stat:**

| Propiedad | Tipo | Aplica a | Descripción |
|-----------|------|----------|-------------|
| `value` | string \| number | (sin `type`) | Valor estático, se muestra tal cual |
| `type` | `'total'` \| `'average'` \| `'top'` | todos | Tipo de estadística calculada |
| `label` | string | todos | Título que aparece encima del valor |
| `format` | string | todos | Formato D3 (`'$,.0f'`, `',.2f'`, etc.) |
| `filter` | Function | `total`, `average` | Filtra datos antes de calcular: `d => d.tipo === 'X'`. En `total` también muestra el % del total general |
| `groupBy` | Function | solo `average` | Agrupa por período antes de promediar: `d => d.etiqueta`. Útil para datos en formato largo (series) |
| `n` | number | solo `top` | Cuántos valores mostrar (default: 3) |
| `labelFn` | Function | solo `top` | Texto del nombre: `d => d.etiqueta` |

---

### Personalización de Tooltips

Los tooltips muestran automáticamente información relevante, pero puedes personalizarlos:

#### Tooltip por Defecto (LineChart)

Si no especificas `tooltipContent`, se genera automáticamente:

```javascript
// Para puntos en gráfica de líneas:
// "Categoría: ..."
// "X: fecha/valor"
// "Y: valor formateado"
```

#### Tooltip Personalizado

```javascript
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.ventas,
  series: d => d.producto,
  
  // Función que retorna HTML del tooltip
  tooltipContent: (d) => {
    return `
      <div style="padding: 8px;">
        <strong style="color: #efca33;">${d.producto}</strong><br>
        <span style="font-size: 12px;">
          ${d.fecha.toLocaleDateString('es-MX', { 
            year: 'numeric', 
            month: 'long', 
            day: 'numeric' 
          })}
        </span><br>
        <strong>Ventas:</strong> $${d3.format(',.2f')(d.ventas)}
      </div>
    `;
  },
});
```

**Resultado del tooltip:**
```
Producto A
5 de marzo de 2024
Ventas: $1,234.56
```

#### Tooltip con Formato de Datos

```javascript
createBarChart({
  container: '#grafica',
  data: datos,
  x: d => d.estado,
  y: d => d.poblacion,
  
  tooltipContent: (d) => {
    return `
      <strong>${d.estado}</strong><br>
      Población: ${d3.format(',')(d.poblacion)} habitantes<br>
      ${d.porcentaje ? `Porcentaje: ${d.porcentaje}%` : ''}
    `;
  },
});
```

#### Tooltip con Formato de Fechas

Para gráficas con fechas, puedes usar varios formatos:

```javascript
createLineChart({
  container: '#grafica',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,
  
  tooltipContent: (d) => {
    // Opción 1: Formato completo en español
    const fechaFormato1 = d.fecha.toLocaleDateString('es-MX', { 
      year: 'numeric', 
      month: 'long', 
      day: 'numeric' 
    });
    // Resultado: "5 de marzo de 2024"
    
    // Opción 2: Formato corto
    const fechaFormato2 = d.fecha.toLocaleDateString('es-MX');
    // Resultado: "05/03/2024"
    
    // Opción 3: Formato personalizado con D3
    const fechaFormato3 = d3.timeFormat('%d de %B de %Y')(d.fecha);
    // Resultado: "05 de marzo de 2024"
    
    // Opción 4: Solo mes y año
    const fechaFormato4 = d.fecha.toLocaleDateString('es-MX', { 
      year: 'numeric', 
      month: 'short' 
    });
    // Resultado: "mar. 2024"
    
    return `
      <strong>${d.categoria || 'Valor'}</strong><br>
      ${fechaFormato1}<br>
      Valor: ${d3.format(',.2f')(d.valor)}
    `;
  },
});
```

**Ejemplos de formatos de fecha:**

```javascript
// Diferentes patrones de formato
tooltipContent: (d) => {
  // 1. Completo: "5 de marzo de 2024"
  const fecha1 = d.fecha.toLocaleDateString('es-MX', { 
    year: 'numeric', month: 'long', day: 'numeric' 
  });
  
  // 2. Corto: "05/03/2024"
  const fecha2 = d.fecha.toLocaleDateString('es-MX');
  
  // 3. Mes y año: "marzo 2024"
  const fecha3 = d.fecha.toLocaleDateString('es-MX', { 
    year: 'numeric', month: 'long' 
  });
  
  // 4. Día y mes: "5 de marzo"
  const fecha4 = d.fecha.toLocaleDateString('es-MX', { 
    month: 'long', day: 'numeric' 
  });
  
  // 5. Con D3 timeFormat (más control):
  const fecha5 = d3.timeFormat('%d/%m/%Y')(d.fecha);        // "05/03/2024"
  const fecha6 = d3.timeFormat('%B %Y')(d.fecha);           // "March 2024" (inglés)
  const fecha7 = d3.timeFormat('%d de %b. %Y')(d.fecha);    // "05 de Mar. 2024"
  
  return `<strong>Fecha:</strong> ${fecha1}<br>Valor: ${d.valor}`;
}
```

**Formatos D3 timeFormat más usados:**

| Código | Resultado | Descripción |
|--------|-----------|-------------|
| `%d/%m/%Y` | 05/03/2024 | Día/Mes/Año |
| `%d de %B` | 05 de marzo | Día de Mes |
| `%B %Y` | marzo 2024 | Mes Año (requiere locale español) |
| `%d-%m-%Y` | 05-03-2024 | Con guiones |
| `%Y-%m-%d` | 2024-03-05 | ISO format |
| `%d/%m/%y` | 05/03/24 | Año corto |

**Para usar meses en español con D3:**
```javascript
import { timeFormatDefaultLocale } from 'd3-time-format';

// Configurar locale español (puedes hacerlo en theme.js)
timeFormatDefaultLocale({
  dateTime: '%A, %e de %B de %Y, %X',
  date: '%d/%m/%Y',
  time: '%H:%M:%S',
  periods: ['AM', 'PM'],
  days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
  shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
  months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
  shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
});

// Ahora d3.timeFormat usará español
const formato = d3.timeFormat('%d de %B de %Y');
formato(new Date(2024, 2, 5)); // "5 de marzo de 2024"
```

#### Estilos CSS del Tooltip

El tooltip usa la clase `.tooltip-sp` definida en `charts-base.css`:

```css
.tooltip-sp {
  position: absolute;
  background: rgba(0, 0, 0, 0.8);
  color: #ffffff;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 14px;
  max-width: 250px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}

.tooltip-sp strong {
  color: #efca33;  /* Amarillo Serendipia */
  font-weight: 700;
}
```

Puedes sobrescribir estos estilos agregando CSS en tu tema:

```css
/* Personalizar tooltip */
.tooltip-sp {
  background: rgba(239, 202, 51, 0.95) !important;
  color: #000 !important;
  font-size: 16px !important;
}
```

---

## Referencia Completa de API

### LineChart - Parámetros Disponibles

```javascript
createLineChart({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#mi-grafica',        // Selector CSS del contenedor
  data: datos,                      // Array de datos (desde CSV)
  x: d => d.fecha,                  // Función accessor para eje X (Date o número)
  y: d => d.valor,                  // Función accessor para eje Y (número)
  
  // ============================================================
  // SERIES (Multi-líneas)
  // ============================================================
  series: d => d.categoria,         // Función accessor para categorías (opcional)
                                    // Si se omite: gráfica de serie única
                                    // Si se incluye: gráfica multi-serie con colores
  
  // ============================================================
  // VISUAL
  // ============================================================
  title: 'Gráfica de líneas',      // Título de la gráfica → se inserta como <h3>
                                    // antes de stats, leyenda y SVG
  source: 'INEGI, 2024',            // Fuente de datos (genera footer automático)
  margin: {                         // Márgenes personalizados
    top: 20, 
    right: 30, 
    bottom: 40, 
    left: 60
  },
  colors: ['#e74c3c', '#3498db'],   // Array de colores personalizados (hex)
                                    // Default: theme.colors.categorical
                                    // ⚠️ Solo aplica si hay 'series' (multi-líneas)
                                    // Para serie única: modifica theme.colors.primary
                                    // Ejemplos:
                                    //   - Un color: ['#2ecc71']
                                    //   - Dos colores: ['#e74c3c', '#3498db']
                                    //   - Tres+ colores: ['#e74c3c', '#3498db', '#2ecc71', ...]
  showLegend: true,                 // Mostrar leyenda (solo si hay series)
                                    // Default: true
  showPoints: false,                // ⭐ Mostrar puntos en las líneas (para hover)
                                    // Default: false
                                    // ⚠️ Recomendado: true (facilita interacción)
  showGrid: true,                   // Mostrar grilla en el fondo
                                    // Default: true
  
  // ============================================================
  // EJES
  // ============================================================
  xAxis: {
    label: 'Fecha',                 // Etiqueta del eje X
    format: '%b %Y',                // Formato de tiempo español
                                    // Opciones: '%b %Y' (ene. 2024), '%d/%m/%Y', etc.
                                    // Para números: ',.0f', ',.2f', etc.
    ticks: 6,                       // Número de ticks (opcional)
  },
  yAxis: {
    label: 'Valor ($)',             // Etiqueta del eje Y
    labelPosition: 'left',          // Posición del label: 'left' (vertical) o 'top' (horizontal)
                                    // Default: 'left' (label vertical a la izquierda)
                                    // 'top': label horizontal arriba del eje
                                    // ⚠️ Si usas 'top', el margen superior se ajusta automáticamente
    format: ',.0f',                 // Formato numérico D3
                                    // ',.0f' = 1,234 (miles sin decimales)
                                    // ',.2f' = 1,234.56 (con 2 decimales)
                                    // '$.2s' = $1.2k (notación compacta)
    ticks: 5,                       // Número de ticks (opcional)
  },
  
  // ============================================================
  // INTERACTIVIDAD
  // ============================================================
  animate: true,                    // Animar líneas al cargar
                                    // Default: true
  responsive: false,                // Redimensionar automático
                                    // Default: false (evita loops)
                                    // ⚠️ Cambiar a true solo si necesario
  tooltipContent: (d) => {          // Función personalizada para tooltip
    return `
      <strong>${d.categoria}</strong><br>
      ${d.fecha.toLocaleDateString('es-MX', { month: 'long', year: 'numeric' })}<br>
      Valor: ${d3.format(',.0f')(d.valor)}
    `;
  },
  filters: ['#year-filter'],        // Array de selectores CSS de filtros
                                    // Requiere crear <select> en HTML
  
  // ============================================================
  // LÍNEAS DE REFERENCIA
  // ============================================================
  referenceLines: [                 // Líneas para señalar eventos o umbrales
    {                               // Default: [] (ninguna)
      axis: 'x',                    // 'x' = vertical, 'y' = horizontal
      value: new Date('2024-06-01'),// Valor en la escala (Date para X temporal,
                                    // number para X numérico o Y)
      label: 'Inicio de gobierno',  // Etiqueta junto a la línea (opcional)
      color: '#e74c3c',             // Color en hex (opcional)
      dashed: true,                 // Línea punteada (default: true)
    },
    { axis: 'y', value: 500000, label: 'Promedio', color: '#9b59b6' },
  ],
  
  // ============================================================
  // ESTADÍSTICAS RESUMIDAS
  // ============================================================
  stats: [                          // Valores calculados en HTML encima de la gráfica
    {                               // Default: [] (ninguna)
      type: 'total',                // 'total' = suma de todos los valores Y
      label: 'Total recaudado',     // Título del stat
      format: '$,.0f',              // Formato D3 del valor
      // filter: d => d.tipo === 'X', // Opcional: filtra antes de sumar. Muestra % del total general
    },
    {
      type: 'average',              // 'average' = promedio de los valores Y
      label: 'Promedio mensual',    // Título del stat
      format: '$,.0f',              // Formato D3 del valor
      // filter: d => d.tipo === 'X', // Opcional: filtra antes de promediar
      // groupBy: d => d.etiqueta,  // Opcional: agrupa por período antes de promediar
    },                              //   útil en datos con series (formato largo)
    {
      type: 'top',                  // 'top' = N valores más altos
      n: 3,                         // Cuántos mostrar (default: 3)
      label: 'Top 3 meses',         // Título del bloque
      format: '$,.0f',              // Formato D3 del valor
      labelFn: d => d.etiqueta,     // Texto del nombre en cada fila
    },
  ],
  
  // ============================================================
  // ACCESIBILIDAD
  // ============================================================
  description: 'Descripción...',    // Texto para lectores de pantalla
  showDataTable: false,             // Mostrar tabla HTML accesible
                                    // Default: false
});
```

### BarChart - Parámetros Disponibles

```javascript
createBarChart({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#mi-grafica',        // Selector CSS del contenedor
  data: datos,                      // Array de datos (desde CSV)
  x: d => d.categoria,              // Función accessor para categorías (eje X o Y)
  y: d => d.valor,                  // Función accessor para valores (eje Y o X)
  
  // ============================================================
  // SERIES (Barras agrupadas/apiladas)
  // ============================================================
  series: d => d.subcategoria,      // Función accessor para agrupar barras (opcional)
  
  // ============================================================
  // TIPO DE GRÁFICA
  // ============================================================
  orientation: 'vertical',          // 'vertical' o 'horizontal'
                                    // vertical: barras hacia arriba
                                    // horizontal: barras hacia derecha
  type: 'grouped',                  // 'grouped' o 'stacked' (solo con series)
                                    // grouped: barras lado a lado
                                    // stacked: barras apiladas
  
  // ============================================================
  // VISUAL
  // ============================================================
  title: 'Gráfica de barras',      // Título de la gráfica
  source: 'INEGI, 2024',            // Fuente de datos (genera footer automático)
  margin: {                         // Márgenes personalizados
    top: 20, 
    right: 30, 
    bottom: 40, 
    left: 60
  },
  colors: ['#e74c3c', '#3498db'],   // Array de colores personalizados
                                    // ⚠️ Solo aplica si hay 'series' (barras agrupadas/apiladas)
                                    // Para barras simples: modifica theme.colors.primary
  showLegend: true,                 // Mostrar leyenda (solo si hay series)
  showValues: false,                // ⭐ Mostrar números encima/dentro de barras
                                    // Default: false
  showGrid: true,                   // Mostrar grilla
  
  // ============================================================
  // EJES
  // ============================================================
  xAxis: {
    label: 'Categoría',             // Etiqueta del eje
    format: '',                     // Formato (para numérico si es horizontal)
    ticks: null,                    // Número de ticks (opcional)
  },
  yAxis: {
    label: 'Valor',                 // Etiqueta del eje
    format: ',.0f',                 // Formato numérico D3
    ticks: 5,                       // Número de ticks
  },
  
  // ============================================================
  // INTERACTIVIDAD
  // ============================================================
  animate: true,                    // Animar barras al cargar
  responsive: false,                // Redimensionar automático
  tooltipContent: (d) => {          // Función personalizada para tooltip
    return `
      <strong>${d.categoria}</strong><br>
      Valor: ${d3.format(',.0f')(d.valor)}
    `;
  },
  filters: ['#category-filter'],    // Array de selectores de filtros
  
  // ============================================================
  // ACCESIBILIDAD
  // ============================================================
  description: 'Descripción...',    // Texto para lectores de pantalla
  showDataTable: false,             // Mostrar tabla HTML accesible
});
```

### Métodos Disponibles (Todas las gráficas)

```javascript
const chart = createLineChart({ ... });

// Actualizar datos (re-renderiza la gráfica)
chart.updateData(nuevosDatos);

// Obtener datos actuales
const data = chart.getData();

// Obtener contenedor
const container = chart.getContainer();

// Destruir gráfica (limpia eventos y DOM)
chart.destroy();
```

### AreaChart - Parámetros Disponibles

```javascript
createAreaChart({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#mi-div',
  data: datos,
  x: d => d.fecha,
  y: d => d.valor,

  // ============================================================
  // TIPO DE ÁREA
  // ============================================================
  type: 'overlapping',  // 'overlapping' | 'stacked'
  showLine: true,       // Línea encima del área
  showPoints: false,    // Puntos en cada dato
  fillOpacity: 0.4,     // Transparencia del relleno (0–1)

  // ============================================================
  // MULTI-SERIE (igual que LineChart)
  // ============================================================
  series: d => d.categoria,  // Si hay varias áreas
  colors: ['#e74c3c', '#3498db', '#2ecc71'],

  // ============================================================
  // LEYENDA INTERACTIVA
  // ============================================================
  // showLegend: true habilita la leyenda. Al hacer clic en cada
  // categoría se oculta / muestra esa serie en la gráfica.
  // No se puede ocultar la última serie visible.
  showLegend: true,

  // ============================================================
  // EJE X — TICKS MENSUALES AUTOMÁTICOS
  // ============================================================
  // Cuando los datos son fechas y no se especifica xAxis.ticks,
  // AreaChart calcula automáticamente el intervalo mensual:
  //   ≤ 12 meses → every 1   ≤ 24 → every 2
  //   ≤ 48      → every 3   > 48  → every 6
  // Para sobrescribir, pasa ticks manualmente:
  xAxis: {
    label: 'Mes',
    format: '%b %Y',   // formato D3 para fechas
    ticks: 3,          // override: cada 3avo tick (o un intervalo D3)
  },
  yAxis: { label: 'Valor', format: ',.0f' },

  // ============================================================
  // STATS — igual que LineChart / BarChart
  // ============================================================
  stats: [
    { label: 'Período', value: '2018–2021' },                          // estático
    { type: 'total',   label: 'Total eventos' },                        // suma Y
    { type: 'total',   label: 'Asesinatos', filter: d => d.tipo === 'Asesinato' }, // filtrado + %
    { type: 'average', label: 'Promedio mensual',
      groupBy: d => d.fecha.toISOString().slice(0, 7) },                // promedio de grupos
    { type: 'top',     label: 'Tipo más frecuente',
      n: 1, labelFn: d => d.categoria },                                // top N
  ],

  // ============================================================
  // OPCIONES ESTÁNDAR
  // ============================================================
  title: 'Mi gráfica de área',
  source: 'INEGI, 2024',
  referenceLines: [{ value: 100, label: 'Meta' }],
  animate: true,
  responsive: false,
  showGrid: true,
  tooltipContent: d => `<strong>${d.valor}</strong>`,
  filters: ['#category-filter'],
});
```

**Diferencias con `stacked`:**
- Con `type:'stacked'`, las áreas se apilan (cada área parte del límite de la anterior)
- El eje Y muestra el total acumulado máximo
- Requiere `series` para tener sentido
- Los datos pueden estar en formato largo (una fila por serie×periodo)

**Ordenamiento automático:**
Los datos se ordenan por X al inicializar y en `updateData()`, para que las líneas nunca zigzagueen aunque el CSV venga desordenado.

---

### MapHexagon - Parámetros Disponibles

```javascript
createMapHexagon({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#mapa',
  data: datos,
  id: d => d.cve,       // Clave INEGI numérica (1–32)
  value: d => d.valor,  // Valor a colorear

  // ============================================================
  // ESCALA DE COLOR
  // ============================================================
  colorScale: 'sequential',    // 'sequential' | 'categorical'
  colorRange: ['#ffffdc', '#ffd316'],  // Min → Max (2 o 3 colores)
  colorDomain: [0, 500],       // Rango manual. Default: extent de los datos
  nullColor: '#e0e0e0',        // Color para estados sin datos

  // ============================================================
  // SVG BASE
  // ============================================================
  svgPath: '/wp-content/themes/serendipia/js/D3_helpers/charts/hexbinmap.svg',
  // Solo cambiar si usas una ruta diferente al default

  // ============================================================
  // CONTENIDO Y TOOLTIP
  // ============================================================
  title: 'Título del mapa',
  source: 'INEGI, 2024',
  description: 'Descripción accesible',
  tooltipContent: (d, cve) => {
    // d = objeto de datos del estado (o undefined si sin datos)
    // cve = string '1'-'32'
    return `<strong>${d.nombre}</strong>: ${d.valor}`;
  },

  // ============================================================
  // LEYENDA Y STATS
  // ============================================================
  showLegend: true,
  stats: [{ type: 'total', label: 'Total nacional' }],
  // ⚠️ Las stats se recalculan automáticamente al cambiar el filtro.
  // Usa type:'total' / type:'top' para que los valores sean dinámicos.

  // ============================================================
  // FILTROS — el mapa genera los <select> automáticamente
  // ============================================================
  // ⚠️ Pasa TODOS los datos al chart (sin pre-filtrar).
  //    El chart aplica el default internamente y actualiza al cambiar.
  //   label  → texto visible junto al select
  //   field  → accessor del dato a filtrar
  //   default → valor preseleccionado (opcional; si se omite, el primero de la lista)
  filters: [
    { label: 'Año', field: d => d.anio, default: 2021 },
  ],
});
```

**Claves INEGI (CVE) para los 32 estados:**

| CVE | Estado | CVE | Estado | CVE | Estado | CVE | Estado |
|-----|--------|-----|--------|-----|--------|-----|--------|
| 1 | Aguascalientes | 9 | Ciudad de México | 17 | Morelos | 25 | Sinaloa |
| 2 | Baja California | 10 | Durango | 18 | Nayarit | 26 | Sonora |
| 3 | Baja California Sur | 11 | Guanajuato | 19 | Nuevo León | 27 | Tabasco |
| 4 | Campeche | 12 | Guerrero | 20 | Oaxaca | 28 | Tamaulipas |
| 5 | Coahuila | 13 | Hidalgo | 21 | Puebla | 29 | Tlaxcala |
| 6 | Colima | 14 | Jalisco | 22 | Querétaro | 30 | Veracruz |
| 7 | Chiapas | 15 | Estado de México | 23 | Quintana Roo | 31 | Yucatán |
| 8 | Chihuahua | 16 | Michoacán | 24 | San Luis Potosí | 32 | Zacatecas |

---

### MapEstatalLeaflet - Parámetros Disponibles

> **Requisitos:** Leaflet CSS/JS + `topojson-client.min.js` + D3 v7 cargados en la página antes de este módulo.

```javascript
createMapEstatalLeaflet({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#mapa-estados',    // Selector CSS del contenedor
  data: datos,
  id:    d => d.cve,             // CVE numérica INEGI (1–32)
  value: d => d.tasa,            // Valor a colorear

  // ============================================================
  // ESCALA DE COLOR
  // ============================================================
  colorScale: 'sequential',      // 'sequential' | 'categorical'
  colorRange: ['#ffffdc', '#fc4e2a'],  // Min → Max (2 o 3 colores)
  colorDomain: [0, 100],         // Rango manual. Default: extent de los datos
  nullColor: '#e0e0e0',          // Color para estados sin datos
  fillOpacity: 0.85,             // Opacidad del relleno (0–1)
  borderColor: '#ffffff',        // Color de bordes entre estados
  borderWeight: 1,               // Grosor de bordes

  // ============================================================
  // CONFIGURACIÓN DEL MAPA
  // ============================================================
  mapHeight: '500px',            // Altura del mapa (CSS)
  mapCenter: [23.6345, -102.5528], // Centro del mapa
  mapZoom: 5,                    // Zoom inicial
  backgroundColor: '#f0f0f0',    // Fondo cuando showTileLayer=false

  // ============================================================
  // CAPA DE MOSAICOS (tile layer)
  // ============================================================
  showTileLayer: false,          // Default: false (fondo limpio para choroplets)
  tileLayerUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
  tileLayerAttr: '&copy; OpenStreetMap contributors',

  // ============================================================
  // TOPOJSON
  // ============================================================
  topoJsonPath: '/wp-content/themes/serendipia/js/D3_helpers/charts/mx_tj.json',
  // Solo cambiar si usas un TopoJSON diferente
  // La geometría debe tener objects.states con feature.properties.state_code (1–32)

  // ============================================================
  // CONTENIDO Y TOOLTIP
  // ============================================================
  title: 'Título del mapa',
  source: 'INEGI, 2024',
  description: 'Descripción accesible del mapa',
  tooltipContent: (d, feature) => {
    // d       = objeto de datos del estado (o undefined si sin datos)
    // feature = objeto GeoJSON: feature.properties.state_code, .state_name
    return `<strong>${feature.properties.state_name}</strong>: ${d?.tasa ?? 'Sin datos'}`;
  },

  // ============================================================
  // LEYENDA Y STATS
  // ============================================================
  showLegend: true,
  stats: [{ type: 'average', label: 'Promedio nacional', format: ',.1f' }],
  // ⚠️ Las stats se recalculan automáticamente al cambiar el filtro.

  // ============================================================
  // FILTROS — el mapa genera los <select> automáticamente
  // ============================================================
  // ⚠️ Pasa TODOS los datos al chart (sin pre-filtrar).
  //    El chart aplica el default internamente y actualiza al cambiar.
  //   label  → texto visible junto al select
  //   field  → accessor del dato a filtrar
  //   default → valor preseleccionado (opcional; si se omite, el primero de la lista)
  filters: [
    { label: 'Año', field: d => d.anio, default: 2021 },
  ],
});
```

**Uso básico en WordPress:**
```html
<div id="mapa-estados" style="max-width:700px"></div>

<link rel="stylesheet" href="/wp-content/themes/serendipia/js/leaflet/leaflet.css">
<script src="/wp-content/themes/serendipia/js/leaflet/leaflet.js"></script>
<script src="/wp-content/themes/serendipia/js/topojson-client.min.js"></script>
<script src="/wp-content/themes/serendipia/js/d3.v7.min.js"></script>
<script type="module">
  import { createMapEstatalLeaflet } from '/wp-content/themes/serendipia/js/D3_helpers/charts/MapEstatalLeaflet.js';

  const datos = await d3.csv('/wp-content/themes/serendipia/js/viz/MI_CARPETA/data/estados.csv',
    d => ({ cve: +d.cve, valor: +d.valor })
  );

  createMapEstatalLeaflet({
    container: '#mapa-estados',
    data: datos,
    id: d => d.cve,
    value: d => d.valor,
    colorRange: ['#ffffdc', '#ffd316'],
    title: 'Mi mapa estatal',
    source: 'Fuente, 2024',
  });
</script>
```

**Métodos públicos adicionales:**
```javascript
const mapa = createMapEstatalLeaflet({ ... });

mapa.getMap();               // Instancia de L.map (para usar API Leaflet directamente)
mapa.updateData(nuevosDatos); // Re-colorea el mapa con nuevos datos
mapa.destroy();              // Elimina el mapa y limpia el DOM
```

---

### MapMunicipalLeaflet - Parámetros Disponibles

> **Requisitos:** Leaflet CSS/JS + `topojson-client.min.js` + D3 v7 cargados en la página antes de este módulo.
> El mismo `mx_tj.json` del mapa estatal incluye el objeto `municipalities` con los 2,469 municipios.

```javascript
createMapMunicipalLeaflet({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#mapa-mun',
  data: datos,
  id:    d => d.cvegeo,   // CVEGEO de 5 dígitos: string "01001" o número 1001
                          // = CVE_ENT (2 dígitos) + CVE_MUN (3 dígitos)
  value: d => d.valor,

  // ============================================================
  // ESCALA DE COLOR
  // ============================================================
  colorScale: 'sequential',       // 'sequential' | 'categorical'
  colorRange: ['#fff7bc', '#d95f0e'],  // Min → Max (2 o 3 colores)
  colorDomain: [0, 500],          // Rango manual. Default: extent de los datos
  nullColor: '#e0e0e0',           // Color para municipios sin datos

  // ============================================================
  // CONFIGURACIÓN DEL MAPA
  // ============================================================
  mapHeight: '520px',             // Altura del mapa (CSS)
  mapCenter: [23.6345, -102.5528],// Centro del mapa
  mapZoom: 5,                     // Zoom inicial
  backgroundColor: '#f0f0f0',    // Fondo cuando showTileLayer=false

  // ============================================================
  // BORDES — delgados por defecto (2,469 polígonos)
  // ============================================================
  borderColor: '#aaa',            // Color de bordes (default: '#aaa', más suave que estatal)
  borderWeight: 0.3,              // Grosor (default: 0.3 — delgado para alta densidad)
  fillOpacity: 0.85,

  // ============================================================
  // CAPA DE MOSAICOS
  // ============================================================
  showTileLayer: false,           // Default: false
  tileLayerUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',

  // ============================================================
  // TOPOJSON
  // ============================================================
  topoJsonPath: '/wp-content/themes/serendipia/js/D3_helpers/charts/mx_tj.json',
  // El TopoJSON usa objects.municipalities con:
  //   feature.properties.state_code  (número 1–32)
  //   feature.properties.mun_code    (número sin ceros)
  //   feature.properties.mun_name    (nombre del municipio)
  // La clave interna se genera como: state_code.padStart(2) + mun_code.padStart(3)

  // ============================================================
  // CONTENIDO Y TOOLTIP
  // ============================================================
  title: 'Título del mapa',
  source: 'Fuente, 2024',
  tooltipContent: (d, feature) => {
    // d       = objeto de datos del municipio (o undefined si sin datos)
    // feature = GeoJSON: feature.properties.state_code, .mun_code, .mun_name
    return d
      ? `<strong>${d.mun}</strong>, ${d.estado}<br>${d3.format(',')(d.valor)}`
      : `<strong>${feature.properties.mun_name}</strong><br>Sin datos`;
  },

  // ============================================================
  // LEYENDA Y STATS
  // ============================================================
  showLegend: true,
  stats: [
    { type: 'total', label: 'Total nacional', format: ',.0f' },
    { type: 'top',   label: 'Municipio con más casos', n: 1, labelFn: d => d.mun },
  ],
  // ⚠️ Las stats se recalculan automáticamente al cambiar el filtro.

  // ============================================================
  // FILTROS — el mapa genera los <select> automáticamente
  // ============================================================
  // ⚠️ Pasa TODOS los datos al chart (sin pre-filtrar).
  filters: [
    { label: 'Año', field: d => d.anio, default: 2024 },
  ],
});
```

**Formato del CVEGEO:**

| Formato | Ejemplo | Acepta |
|---------|---------|--------|
| string 5 dígitos | `"01001"` | ✅ |
| número | `1001` | ✅ (se normaliza internamente a `"01001"`) |
| número | `21043` | ✅ (→ `"21043"`) |

**Uso básico en WordPress:**
```html
<div id="mapa-mun" style="max-width:800px"></div>

<link rel="stylesheet" href="/wp-content/themes/serendipia/js/leaflet/leaflet.css">
<script src="/wp-content/themes/serendipia/js/leaflet/leaflet.js"></script>
<script src="/wp-content/themes/serendipia/js/topojson-client.min.js"></script>
<script src="/wp-content/themes/serendipia/js/d3.v7.min.js"></script>
<script type="module">
import { createMapMunicipalLeaflet } from '/wp-content/themes/serendipia/js/D3_helpers/charts/index.js';

(async () => {
  const data = await d3.csv('/wp-content/themes/serendipia/js/viz/tomas_clandestinas/2025/data/tomas_municipios_clave.csv',
    d => ({ cvegeo: d.CVEGEO, valor: +d.tomas, mun: d.NOM_MUN, anio: +d.year })
  );

  createMapMunicipalLeaflet({
    container: '#mapa-mun',
    data,
    id:    d => d.cvegeo,
    value: d => d.valor,
    colorRange: ['#fff7bc', '#d95f0e'],
    title: 'Tomas clandestinas por municipio',
    source: 'Pemex, 2025',
    filters: [{ label: 'Año', field: d => d.anio, default: 2023 }],
  });
})();
</script>
```

**Métodos públicos:**
```javascript
const mapa = createMapMunicipalLeaflet({ ... });

mapa.getMap();                // Instancia de L.map
mapa.updateData(nuevosDatos); // Re-colorea con nuevos datos
mapa.destroy();               // Elimina el mapa y limpia el DOM
```

---

### Heatmap - Parámetros Disponibles

```javascript
createHeatmap({
  // ============================================================
  // REQUERIDOS
  // ============================================================
  container: '#heatmap',
  data: datos,
  row:   d => d.estado,    // Accessor para filas (eje Y)
  col:   d => d.anio,      // Accessor para columnas (eje X)
  value: d => d.tasa,      // Accessor para el valor numérico (color de celda)

  // ============================================================
  // ESCALA DE COLOR
  // ============================================================
  colorRange: ['#ffffdc', '#efca33'],  // Min → Max (2 o 3 colores)
                                        // 3 colores: pasa por el tono del medio
  colorDomain: [0, 100],   // Rango manual. Default: extent automático de los datos
  nullColor: '#e0e0e0',    // Color para celdas sin datos (null / undefined)

  // ============================================================
  // VISUAL
  // ============================================================
  title: 'Tasa por estado y año',   // Título como <h3>
  source: 'INEGI, 2024',            // Fuente (genera footer + logo)
  showValues: false,                 // ⭐ Mostrar el valor dentro de cada celda
  valueFormat: ',.1f',              // Formato D3 para los valores mostrados
  showLegend: true,                  // Barra de color (min → max) en la esquina superior
  margin: { top: 30, right: 40, bottom: 80, left: 110 },  // Márgenes (px)

  // ============================================================
  // EJES
  // ============================================================
  xAxis: {
    label: 'Año',          // Etiqueta del eje X
    rotate: true,          // Rotar etiquetas 40° (default: true)
  },
  yAxis: {
    label: 'Estado',       // Etiqueta del eje Y
  },

  // ============================================================
  // INTERACTIVIDAD
  // ============================================================
  animate: true,           // Fade-in escalonado de celdas al cargar
  responsive: false,       // Auto-redimensionar
  tooltipContent: (d) => { // Función personalizada para tooltip
    return `<strong>${d.estado}</strong><br>${d.anio}: ${d.tasa}%`;
  },
  filters: ['#anio-filter'],  // Selectores CSS de <select> externos

  // ============================================================
  // ESTADÍSTICAS
  // ============================================================
  stats: [
    { label: 'Período', value: '2015–2024' },
    { type: 'total',   label: 'Total nacional' },
    { type: 'average', label: 'Promedio por celda' },
  ],

  // ============================================================
  // ACCESIBILIDAD
  // ============================================================
  description: 'Mapa de calor de tasas por estado y año.',
});
```

**Uso básico en WordPress:**
```html
<div id="heatmap"></div>

<script src="/wp-content/themes/serendipia/js/d3.v7.min.js"></script>
<script type="module">
import { createHeatmap } from '/wp-content/themes/serendipia/js/D3_helpers/charts/Heatmap.js';

const datos = await d3.csv('/ruta/a/datos.csv', d => ({
  estado: d.estado,
  anio: d.anio,
  tasa: +d.tasa,
}));

createHeatmap({
  container: '#heatmap',
  data: datos,
  row: d => d.estado,
  col: d => d.anio,
  value: d => d.tasa,
  colorRange: ['#ffffdc', '#efca33'],
  title: 'Tasa por estado',
  source: 'INEGI, 2024',
  showValues: true,
});
</script>
```

**Métodos públicos:**
```javascript
const chart = createHeatmap({ ... });

chart.updateData(nuevosDatos); // Re-renderiza con nuevos datos
chart.getData();               // Retorna los datos actuales
chart.getContainer();          // Retorna el elemento DOM del contenedor
chart.destroy();               // Elimina la gráfica y limpia el DOM
```

---

### SankeyDiagram - Parámetros Disponibles

> **Requisito:** `d3-sankeyv0.12.3.min.js` cargado en la página **antes** de este módulo.
>
> ```html
> <script src="/wp-content/themes/serendipia/js/d3-sankeyv0.12.3.min.js"></script>
> ```

```javascript
createSankeyDiagram({
  // ============================================================
  // DATOS — elige UNA forma
  // ============================================================

  // Forma A: array de filas + accessors (la más cómoda con CSV)
  data: datos,
  source: d => d.origen,    // Accessor nodo origen
  target: d => d.destino,   // Accessor nodo destino
  value:  d => d.flujo,     // Accessor valor del flujo

  // Forma B: nodes y links directos
  nodes: [
    { id: 'A', name: 'Producción' },
    { id: 'B', name: 'Distribución' },
    { id: 'C', name: 'Consumidores' },
  ],
  links: [
    { source: 'A', target: 'B', value: 300 },
    { source: 'B', target: 'C', value: 250 },
  ],

  // ============================================================
  // LAYOUT
  // ============================================================
  nodeWidth: 20,        // Ancho de los rectángulos (px). Default: 20
  nodePadding: 12,      // Separación vertical entre nodos (px). Default: 12
  align: 'justify',     // 'justify' | 'left' | 'right' | 'center'
                        // Default: 'justify' (distribución uniforme)
  iterations: 6,        // Iteraciones del algoritmo de relajación. Default: 6

  // ============================================================
  // VISUAL
  // ============================================================
  title: 'Flujos de presupuesto',   // Título como <h3>
  source: 'SHCP, 2024',             // Fuente (genera footer + logo)
  colors: ['#e74c3c', '#3498db'],   // Colores para los nodos (categórico)
                                     // Default: theme.colors.categorical
  linkOpacity: 0.45,                 // Opacidad de los flujos (0–1). Default: 0.45
  nodeLabel: node => node.name,      // Función de texto del label. Default: node.name
  labelPosition: 'auto',             // 'auto' | 'right' | 'left'
                                     // auto: nodos izq → label a la derecha, y viceversa
  margin: { top: 30, right: 140, bottom: 30, left: 140 },  // Márgenes (px)

  // ============================================================
  // INTERACTIVIDAD
  // ============================================================
  animate: true,         // Fade-in de flujos y nodos al cargar. Default: true
  responsive: false,     // Auto-redimensionar. Default: false
  tooltipContent: (d, type) => {  // Tooltip personalizado
    // type === 'node': d es el nodo (con d.name, d.value, d.sourceLinks, d.targetLinks)
    // type === 'link': d es el link (con d.source, d.target, d.value)
    if (type === 'node') return `<strong>${d.name}</strong>: ${d3.format(',.0f')(d.value)}`;
    return `${d.source.name} → ${d.target.name}: ${d3.format(',.0f')(d.value)}`;
  },

  // ============================================================
  // ESTADÍSTICAS
  // ============================================================
  // Las stats se calculan sobre los links (flujos).
  // Usa value: d => d.value para referirse al valor de cada flujo.
  stats: [
    { label: 'Total flujos', type: 'total' },
    { label: 'Promedio por flujo', type: 'average' },
  ],

  // ============================================================
  // ACCESIBILIDAD
  // ============================================================
  description: 'Flujos de presupuesto entre sectores.',
});
```

**Uso básico en WordPress:**
```html
<div id="sankey" style="max-width:800px"></div>

<script src="/wp-content/themes/serendipia/js/d3.v7.min.js"></script>
<script src="/wp-content/themes/serendipia/js/d3-sankeyv0.12.3.min.js"></script>
<script type="module">
import { createSankeyDiagram } from '/wp-content/themes/serendipia/js/D3_helpers/charts/SankeyDiagram.js';

const datos = await d3.csv('/ruta/a/flujos.csv', d => ({
  origen:  d.origen,
  destino: d.destino,
  flujo:   +d.flujo,
}));

createSankeyDiagram({
  container: '#sankey',
  data: datos,
  source: d => d.origen,
  target: d => d.destino,
  value:  d => d.flujo,
  title: 'Flujos presupuestales',
  source: 'SHCP, 2024',
  animate: true,
});
</script>
```

**Métodos públicos:**
```javascript
const diagram = createSankeyDiagram({ ... });

diagram.updateData(nuevosDatos); // Re-renderiza con nuevos datos (Forma A)
diagram.getData();               // Retorna { nodes, links } del layout actual
diagram.getContainer();          // Retorna el elemento DOM del contenedor
diagram.destroy();               // Elimina el diagrama y limpia el DOM
```

---

### Tabla de Referencia Rápida

| Parámetro | LineChart | BarChart | Tipo | Default | Descripción |
|-----------|-----------|----------|------|---------|-------------|
| **container** | ✅ Requerido | ✅ Requerido | string | - | Selector CSS del contenedor |
| **data** | ✅ Requerido | ✅ Requerido | Array | - | Datos desde CSV |
| **x** | ✅ Requerido | ✅ Requerido | Function | - | Accessor para eje X |
| **y** | ✅ Requerido | ✅ Requerido | Function | - | Accessor para eje Y |
| **series** | Opcional | Opcional | Function | null | Accessor para multi-serie |
| **title** | Opcional | Opcional | string | 'Gráfica de...' | Título (renderizado como `<h3>` encima de stats y leyenda) |
| **source** | Opcional | Opcional | string | '' | Fuente (genera footer) |
| **showPoints** | ⭐ Opcional | - | boolean | false | Mostrar puntos (hover) |
| **showValues** | - | ⭐ Opcional | boolean | false | Mostrar números en barras |
| **orientation** | - | Opcional | string | 'vertical' | 'vertical' o 'horizontal' |
| **type** | - | Opcional | string | 'grouped' | 'grouped' o 'stacked' |
| **showGrid** | Opcional | Opcional | boolean | true | Mostrar grilla |
| **showLegend** | Opcional | Opcional | boolean | true | Mostrar leyenda |
| **animate** | Opcional | Opcional | boolean | true | Animar al cargar |
| **responsive** | Opcional | Opcional | boolean | false | Auto-redimensionar |
| **colors** | Opcional | Opcional | Array | theme.colors | Colores personalizados (solo con series) |
| **margin** | Opcional | Opcional | Object | {...} | Márgenes personalizados |
| **xAxis** | Opcional | Opcional | Object | {...} | Config eje X |
| **yAxis** | Opcional | Opcional | Object | {...} | Config eje Y |
| **tooltipContent** | Opcional | Opcional | Function | null | Personalizar tooltip |
| **filters** | Opcional | Opcional | Array | [] | Selectores de filtros |
| **description** | Opcional | Opcional | string | '' | Accesibilidad |
| **showDataTable** | Opcional | - | boolean | false | Tabla accesible |
| **referenceLines** | Opcional | - | Array | [] | Líneas de referencia (eventos/umbrales) |
| **stats** | Opcional | Opcional | Array | [] | Estadísticas resumidas encima de la gráfica |

---

## CSS Clases Disponibles

Definidas en `css/charts-base.css`:

- `.sp-chart-container` - Contenedor responsive
- `.tooltip-sp` - Tooltip estándar
- `.sp-chart-legend` - Leyenda
- `.sp-chart-controls` - Controles (filtros, botones)
- `.sr-only` - Ocultar visualmente (accesibilidad)

---

## Compatibilidad con Código Existente

Este sistema **NO modifica** las gráficas existentes en `js/viz/`.

✅ `mainV7.js` sigue disponible y funcional  
✅ Todas las gráficas antiguas siguen funcionando  
✅ Migración gradual según necesidad  
✅ Coexisten ambos sistemas  

---

## Próximos Pasos

### FASE 3 (Completada)
- [x] LineChart.js
- [x] BarChart.js
- [x] AreaChart.js (overlapping + stacked)
- [x] MapHexagon.js (mapa hexagonal de México, 32 estados)
- [x] MapEstatalLeaflet.js (choropleth estatal con Leaflet + TopoJSON)
- [x] MapMunicipalLeaflet.js (municipal)
- [x] Heatmap.js (matriz de calor — filas × columnas con escala secuencial)
- [x] SankeyDiagram.js (flujos — requiere d3-sankeyv0.12.3.min.js)

### FASE 4 (En progreso)
- [ ] Documentación completa con ejemplos en vivo
- [ ] Tests unitarios (Jest + D3.js)
- [ ] Storybook o página de demos
- [ ] Performance benchmarks

---

## Soporte

Para preguntas o problemas:
1. Revisar `examples/ejemplos-basicos.js`
2. Consultar código de gráficas existentes en `js/viz/` como referencia
3. Ver utilidades en `mainV7.js` (compatibles con este sistema)

---

**Versión:** 1.0.0  
**Última actualización:** Marzo 2026  
**Autor:** Equipo Serendipia
