# Elementor Builder — Skill oficial del MCP (dev.beand.be)

Skill OBLIGATORIO para trabajar con Elementor vía este MCP. Leelo COMPLETO antes de tocar cualquier página.

Este MCP es un wrapper 1:1 de la REST API **Beand Elementor API** (motor propio) expuesta en el WordPress objetivo por el bridge drop-in. Vos le pegás a las tools del MCP; el MCP le pega al sitio.

## 0. Puesta en marcha (siempre, en este orden)

**Modo recomendado — bridge drop-in (SIN instalar plugin, sin entrar al admin de WP):**
1. Llamá **`setup_bridge`** → devuelve un secret y la URL de descarga de `beand-bridge.php` (UN archivo PHP con el secret ya embebido).
2. Con el acceso FTP/SFTP/SSH que ya tenés al hosting, subilo a `wp-content/mu-plugins/beand-bridge.php` (creá la carpeta `mu-plugins` si no existe). Los mu-plugins cargan solos: sin activación, sin Application Password.
3. Verificá: `GET https://{sitio}/wp-json/beandelementor/v1/bridge-info` → `{"bridge":true,...}`.
4. **`connect_wordpress(site_url, bridge_secret)`**. Para "desinstalar": borrás el archivo y listo, no toca la DB.

**Modo alternativo — plugin completo** (solo si el sitio YA lo tiene): `connect_wordpress(site_url, username, app_password)` con un Application Password. Si querés instalarlo permanente: `plugin_install_info`.

En ambos modos: si el certificado TLS no matchea, `allow_insecure_tls: true`. Con conexión OK: **`list_pages`**, **`list_widgets`** y **`get_kit`** para entender el sitio.

Regla de oro: **tratá suave al hosting** — agrupá operaciones (usá `bulk_patch`), no dispares decenas de requests en ráfaga.

## 1. Workflow estándar (discover → explore → edit → flush → verify)

1. **Discover**: `list_pages` · `list_widgets` · `get_kit` (colores/fuentes globales).
2. **Explore**: `get_structure(page_id)` SIEMPRE primero (liviano: IDs + tipos + hints). Después `get_element(page_id, element_id)` para un elemento puntual. `get_page` (data completa) solo si de verdad hace falta — es pesado. `find_elements` busca por widgetType / elType / texto.
3. **Edit**: `patch_element` (merge: mandá SOLO lo que cambia) · `add_element` · `delete_element` · `duplicate_element` · `move_element` · `bulk_patch` (N cambios en 1 sola carga/guardado de página — preferilo siempre que haya >1 cambio) · `set_column_width` (maneja el quirk de Elementor v4).
4. **Flush**: `flush_css` — OBLIGATORIO tras cualquier cambio visual. Elementor cachea CSS agresivamente.
5. **Verify**: screenshot real de la página (Chrome MCP o `chrome --headless=new --ignore-certificate-errors --screenshot=out.png "URL"`). Recorré TODAS las secciones. Nunca des por hecho sin mirar; chequeá que header/footer no se rompan.

### Escape hatch
Si ninguna tool cubre lo que necesitás, `raw_api(method, path, body)` pega directo a `/wp-json/beandelementor/v1{path}`.

## 2. Estructura de un elemento Elementor

```json
{
  "id": "8-char-hex",
  "elType": "container|widget",
  "widgetType": "heading|text-editor|image|button|icon|form|html|...",
  "isInner": false,
  "settings": { },
  "elements": [ ]
}
```

**IDs: SIEMPRE hex de 8 chars** (ej. `f8703b57`) al crear elementos. IDs null o no-hex hacen al elemento inadresable por la API (ver gotcha §7.1).

### Containers
- Root: `elType: "container"`, `isInner: false` · Anidado: `isInner: true`
- Fila: `flex_direction: "row"`, `flex_wrap: "wrap"` · Columna: `flex_direction: "column"`

### Settings comunes de widgets
- **heading**: `title`, `header_size` (h1-h6), `align`, `title_color`, `typography_typography: "custom"` + `typography_font_family/size/weight`
- **text-editor**: `editor` (HTML), `text_color`, `typography_*`
- **image**: `image: {"url":"...","id":N}`, `image_size`, `width`, `image_border_radius`
- **button**: `text`, `link:{"url":"..."}`, `background_color`, `button_text_color`. Outline: `background_color:"rgba(0,0,0,0)"` + `border_border:"solid"` + `border_width` + `border_color` (NO `button_border_*`). Hover: `button_background_hover_color`, `button_hover_border_color`
- **icon** (stacked): `selected_icon:{"value":"fas fa-star","library":"fa-solid"}`, `view:"stacked"`, `shape:"circle"`. **CRÍTICO**: en stacked, `primary_color` = FONDO y `secondary_color` = ÍCONO (¡invertido!)
- **form**: `form_name`, `form_fields[]`, `email_to`, `button_text`, `show_labels:""` para ocultar labels (usar placeholders)
- **icon-list**: `icon_list[]` de `{"text","selected_icon","link"}`
- **google_maps**: `address`, `zoom:{"size":16}`, `height:{"size":450,"unit":"px"}`
- **nav-menu**: `menu` (slug del menú WP), `layout:"horizontal"`, `pointer`

### Formatos de valores
```json
{"background_background":"classic","background_color":"#2F251F","background_image":{"url":"...","id":19}}
{"top":"20","right":"30","bottom":"20","left":"30","unit":"px","isLinked":false}   // spacing
{"size":30,"unit":"px"}                                                             // size
{"top":"12","right":"12","bottom":"12","left":"12","unit":"px","isLinked":true}     // border radius
```

## 3. Patrones de sección reutilizables

### Hero
```json
{"elType":"container","isInner":false,
 "settings":{"content_width":"full","flex_direction":"column","flex_align_items":"center","flex_justify_content":"center","min_height":{"size":60,"unit":"vh"},"background_background":"classic","background_color":"#DARK","padding":{"top":"80","right":"20","bottom":"80","left":"20","unit":"px","isLinked":false}},
 "elements":[
  {"elType":"widget","widgetType":"heading","settings":{"title":"...","header_size":"h1","align":"center","title_color":"#FFFFFF","typography_typography":"custom","typography_font_size":{"size":65,"unit":"px"},"typography_font_weight":"500"}},
  {"elType":"widget","widgetType":"text-editor","settings":{"editor":"<p><em>Subtitle</em></p>","align":"center","text_color":"#ACCENT_LIGHT"}}]}
```

### Fila de contenido (imagen + texto, zigzag)
Dos inner containers al 50% (`width_tablet: 100%`): uno con `image` (border-radius 12px), otro con `heading` + `text-editor` y `flex_justify_content:"center"`. **Zigzag obligatorio**: filas impares imagen IZQUIERDA, pares imagen DERECHA (intercambiá el orden de los dos inner containers).

### Fila de íconos (servicios/features)
Container row + N inner containers al 25% (`flex_direction:"column"`, `flex_align_items:"center"`) con `icon` stacked circle + `heading` h3 centrado. **Cada ícono ÚNICO y contextual** — jamás el mismo ícono repetido.

### Collage de fotos (3 fotos + texto)
Container row `nowrap`, `min_height` 700px, fondo accent + imagen decorativa (`background_size:"100% 80%"`, `overflow:"hidden"`). Tres inner containers ~33/30/33% con `z_index:2`; fotos escalonadas con distinto `padding-top`; sombras `image_box_shadow`. Anchos sumando ≤96% para no wrappear.

### Página de contacto
Hero + dos columnas (info con icon-list a la izquierda, form a la derecha) + Google Maps sobre fondo accent.

## 4. Buenas prácticas de diseño (aprendidas)

- **Fondos: estrictamente 2 colores** para secciones de contenido (blanco + un accent). Más de 2 = efecto arcoíris feo. Hero y contacto pueden usar un dark como tercera zona. **Nunca** dos secciones adyacentes con el mismo fondo. Patrón: Dark hero → Accent → White → Accent → ... → Dark contact.
- **Formularios**: `show_labels:""`, placeholders descriptivos, `email_to` y `form_name` siempre.
- **Footer**: fondo claro y legible; si cambiás de dark a light, actualizá TODOS los colores de texto. Incluir logo, dirección, teléfono, email, social icons, copyright.
- **Imágenes**: filas de contenido → border-radius 12px; hero → full-width sin radius; íconos circulares → 50%.
- **Responsive**: `width_tablet:{"size":100,"unit":"%"}` en columnas siempre. H1: 65px desktop / 45px tablet / 32px mobile.
- **Sticky header con logo shrink** (requiere Elementor Pro), en el container del header template: `sticky:"top"`, `sticky_on:["desktop","tablet","mobile"]`, `sticky_effects_offset:100` + `custom_css` usando `selector` y la clase `.elementor-sticky--effects`.

## 5. Gotchas de flex layout

- Si N columnas × X% + gaps > 100% → wrappean. `sum(widths) + (N-1)*gap <= 100%`. 4 items al 33% = 132% → 3+1; usá 25%.
- **Elementor v4 (CRÍTICO)**: `width` solo NO alcanza en inner containers. Hay que setear juntos `_flex_size:"custom"` + `_inline_size:25` + `width:{size:25,unit:"%"}`. La tool **`set_column_width`** hace los tres (+ variantes responsive) en una llamada — usala siempre.

## 6. Gotchas críticos de la API

- **RACE CONDITION: jamás PATCH en paralelo sobre la misma página.** Cada PATCH carga la página entera, modifica y guarda: dos en paralelo se pisan. Secuencial por página; paralelismo entre páginas distintas OK. Para muchos cambios en una página: **`bulk_patch`**.
- `get_page` devuelve los elementos bajo la clave `data` (no `elementor_data`).
- `position` es 0-based; `-1` = append. Inserts a nivel raíz: `parent_id: null`.
- PATCH = merge (solo lo que cambia). `move_element` reordena sin remove+add.
- `widget_defaults(name)` devuelve un elemento JSON listo para usar con todos los defaults; `widget_schema(name)` el schema de controles.
- Tras crear una página, agregala al menú vía WP-CLI: `wp menu item add-post {menu} {page_id}`.
- `flush_css` SIEMPRE después de cambios visuales; acepta `post_id` para flushear solo un post.

## 7. HTML → widget Elementor NATIVO (mu-plugin) — workflow completo

Para convertir un bloque HTML a medida (widget `html` en una página) en un **módulo Elementor nativo, visualmente IDÉNTICO y 100% editable** desde el editor:

1. **DISCOVER**: `list_pages` · `list_widgets` (mirá qué hay) · `get_kit`.
2. **LOCALIZAR**: `get_structure(page_id)` → encontrá el container + widget `html` del bloque. `get_element(page_id, element_id)` → traé el HTML/CSS/JS EXACTO.
3. **ANALIZAR**: separá lo EDITABLE (slides, textos, imágenes, links, botones, campos de form) del ESTILO (se hornea en el render).
4. **CREAR mu-plugin PHP** que registre un widget `\Elementor\Widget_Base`:
   - Hook `elementor/widgets/register`; implementá `get_name/get_title/get_icon/get_categories/get_keywords`.
   - `register_controls()`: **REPEATER** para lo repetible (slides/tiles) + TEXT/TEXTAREA/URL/MEDIA para el resto. Cada control con `'default'` = el contenido ACTUAL del bloque.
   - `render()`: reproducí el markup EXACTO (mismo CSS/HTML/JS), scoped con id único `'pref-'.$this->get_id()`, leyendo de `get_settings_for_display()` **CON FALLBACK a los defaults** (`?:` y arrays por defecto) para que nunca renderice vacío aunque `settings` sea `{}`.
   - Cerrá con `$wm->register(new TuClase());`.
5. **SUBIR** el mu-plugin a `wp-content/mu-plugins/` (SFTP/FTP/SSH — esto es del lado del cliente, el MCP no sube archivos). Verificá `php -l`. Confirmá que `list_widgets` incluye tu widget.
6. **REEMPLAZAR** el bloque en la página (**SECUENCIAL**, nunca en paralelo sobre la misma página):
   - `add_element(page_id, parent_id={CONTAINER_ID}, position=N, element={"id":"{HEX8}","elType":"widget","widgetType":"{tu_widget}","settings":{}})`
   - `delete_element(page_id, {HTML_WIDGET_ID_VIEJO})`
   - Si el bloque viejo tenía fondo/slideshow en el container, revertilo con `patch_element`.
7. **`flush_css`** (obligatorio tras cualquier cambio visual).
8. **VERIFICAR**: screenshot headless → debe quedar IDÉNTICO. Chequeá header/footer.

### 7.1 Gotchas del workflow (aprendidos a golpes)

- La ruta `/page/{id}/element/{eid}` valida `element_id` como HEX `[a-f0-9]+`. Si el id NO es hex (ej. `jthdrhtml`), PATCH/DELETE/GET por elemento fallan: usá **`update_page` (PUT) con la data completa** de la página en su lugar.
- IDs hex de 8 chars válidos en cada elemento nuevo.
- **PUT/PATCH puede resetear `_elementor_template_type` y `_wp_page_template`** (header/footer/canvas) → restaurar por wp-cli (`wp post meta update {id} _elementor_template_type header`) o por WP REST (`POST /wp-json/wp/v2/pages/{id} {"template":"elementor_canvas"}`) según el caso. Después de un PUT, verificá que header/footer sigan renderizando.
- **Migraciones**: Elementor guarda `_elementor_data` con slashes escapados (`dominio\/path`) → un search-replace sin escapar NO matchea; corré también el search-replace de `dominio\/path`.
- Si el host bloquea peticiones seguidas: ritmo suave, agrupá con `bulk_patch`, no lo hagas caer.

**Resultado esperado**: mu-plugin con el widget nativo + la página usándolo, idéntica al pixel y editable desde Elementor.

## 8. Claude Design → Elementor (diseños nuevos 100% compatibles)

Cuando un diseño se arma en Claude Design (o cualquier agente de diseño), NO le pidas HTML: pedile directamente el **template Elementor-nativo**. Pipeline:

1. **`get_kit`** del sitio conectado → tené a mano colores y fuentes globales reales.
2. **`design_contract`** → devuelve la instrucción precisa (el "contrato"). Completá sus dos placeholders: `{{PEGAR_GET_KIT_ACA}}` (el kit del paso 1) y `{{PEGAR_BRIEF_ACA}}` (el brief/contenido). Pegale ESO al agente de diseño.
   > Los dos prompts listos para copiar: **`design_prompt`** (Prompt 1 — se pega en Claude Design al crear el diseño; hace que el HTML salga "Elementor-ready") y **`handoff_prompt`** (Prompt 2 — arranca la sesión de conversión sabiendo exactamente qué hacer). También en `/prompt/design` y `/prompt/handoff`.
3. El agente devuelve el diseño (HTML anotado con `data-*`, o JSON nativo). Bloques visualmente complejos vienen marcados con `data-custom`.
4. **Conversión POR BLOQUE — la fidelidad manda.** Para cada sección/bloque elegí un camino:
   - **A) Encaja en widgets core** → reproducilo con widgets nativos (containers flex, heading/text/image/button/icon/form…, repeaters para lo repetible). Vía preferida.
   - **B) No se reproduce FIEL con widgets core** (marcado `data-custom` o con efecto/layout que los core no clavan) → **NO lo simplifiques ni lo metas en un widget `html`**. Horneá ESE bloque como **módulo personalizado de Elementor** siguiendo el **workflow §7**: mu-plugin `Widget_Base` con repeater + controles (defaults = contenido actual) y `render()` con el HTML/CSS/JS exacto, scoped. Subilo a `mu-plugins/`, confirmalo con `list_widgets`, e insertalo como cualquier widget. Queda 100% fiel y editable. Un bloque `data-custom` = un módulo (reutilizable entre páginas).
5. **`validate_template(data, autofix, check_widgets_live)`** — lint estructural: IDs, anchos, widgets prohibidos, formatos. Subí los módulos custom ANTES de validar para que pasen el check live. Si hay errores, corregí (o devolvéselos al agente de diseño con el contrato).
6. Imágenes: los placeholders `{{IMG:descripción}}` se resuelven con `import_media` (o URLs reales) y se reemplazan en el JSON (y en los defaults de los módulos custom).
7. **`create_page`** / **`build_page`** (o `add_section` para inyectar en una página existente) → **`flush_css`** → screenshot de verificación.

Regla de oro: cada bloque termina o como **widget nativo estándar** o como **módulo custom fiel** — nunca como HTML crudo pegado ni como una versión "parecida" degradada.

## 9. Referencia de tools ↔ endpoints REST

| Tool MCP | REST |
|---|---|
| `setup_bridge` | — (genera el drop-in beand-bridge.php con secret) |
| `design_prompt` | — (Prompt 1: pegar en Claude Design) |
| `handoff_prompt` | — (Prompt 2: arrancar la conversión a WP) |
| `design_contract` | — (spec técnica de formato para la conversión) |
| `validate_template` | — (lint local del JSON, sin tocar el sitio) |
| `list_pages` | GET `/pages` |
| `get_page` | GET `/page/{id}` |
| `get_structure` | GET `/page/{id}/structure` |
| `update_page` | PUT `/page/{id}` |
| `create_page` | POST `/page` |
| `build_page` | POST `/build-page` |
| `get_element` | GET `/page/{id}/element/{eid}` |
| `add_element` | POST `/page/{id}/element` |
| `patch_element` | PATCH `/page/{id}/element/{eid}` |
| `delete_element` | DELETE `/page/{id}/element/{eid}` |
| `duplicate_element` | POST `/page/{id}/element/{eid}/duplicate` |
| `move_element` | POST `/page/{id}/element/{eid}/move` |
| `bulk_patch` | POST `/page/{id}/elements/patch-bulk` |
| `set_column_width` | PATCH `/page/{id}/element/{eid}/column-width` |
| `find_elements` | GET `/page/{id}/find` |
| `add_section` | POST `/page/{id}/section` |
| `list_templates` / `create_template` | GET `/templates` / POST `/template` |
| `get_kit` / `update_kit` | GET/PUT `/kit` |
| `list_widgets` | GET `/widgets` |
| `widget_schema` / `widget_defaults` | GET `/widget/{name}/schema` / `/defaults` |
| `import_media` | POST `/media/import` |
| `flush_css` | POST `/flush-css` |
| `raw_api` | cualquier método/path del namespace |
