---
name: Ebook Designer
description: Transforma documentos (PDF, Word, .txt, .md) en eBooks profesionales con diseño editorial de alta calidad. Incluye portada a pantalla completa, encabezados y pies de página, tipografía cuidada, imágenes integradas en el contenido con variedad de layouts, logo de marca, y colores personalizados. Activa este skill cuando el usuario quiera convertir un documento en un eBook o PDF con diseño profesional, cuando mencione "ebook", "diseño de PDF", "formatear documento", "darle diseño a un texto", o cuando suba cualquier documento y quiera que se vea como un libro publicado.
---

# Ebook Designer — v4

Transforma documentos planos en eBooks/PDFs con diseño editorial profesional.

---

## Flujo de trabajo

Al iniciar, Claude debe solicitar al usuario **en español** los siguientes elementos, todos en un solo mensaje:

```
¡Hola! Para generar tu eBook necesito lo siguiente:

1. 📄 El documento con el contenido (PDF, Word, .txt o .md)
2. 🖼️ Las imágenes que quieras incluir (máximo 20)
3. 🏷️ El logo de tu marca o institución
4. 🎨 Color principal (en formato hex, ej: #1B427C)
5. 🎨 Color secundario / acento (en formato hex, ej: #C2A34E)

Cuando tengas todo listo, súbelo y empezamos.
```

No comenzar a generar el eBook hasta tener al menos el documento y los colores.

---

## Stack técnico

```bash
pip install reportlab pillow --break-system-packages -q
```

Usar exclusivamente **ReportLab + Pillow**. No usar fpdf, weasyprint ni otros.

---

## Reglas críticas de diseño (aprendidas en producción)

### ✅ PORTADA — siempre en canvas directo

**NUNCA** construir la portada con tablas de Platypus. El texto se corta por los márgenes del documento.

La portada se dibuja 100% dentro de `on_cover_page(canvas, doc)` usando primitivas de canvas:

```python
def on_cover_page(canvas, doc):
    canvas.saveState()

    # 1. Fondo completo en color primario
    canvas.setFillColor(PRIMARY)
    canvas.rect(0, 0, W, H, fill=1, stroke=0)

    # 2. Foto de fondo en panel derecho (con overlay oscuro para contraste)
    try:
        canvas.drawImage(cover_img_path, W*0.48, 0, width=W*0.52, height=H,
                         preserveAspectRatio=False, mask='auto')
        canvas.setFillColor(HexColor('#0d2a50'))
        canvas.rect(W*0.48, 0, W*0.52, H, fill=1, stroke=0)
    except:
        pass

    # 3. Forma diagonal que cubre el panel izquierdo
    canvas.setFillColor(PRIMARY)
    p = canvas.beginPath()
    p.moveTo(0, 0); p.lineTo(W*0.56, 0)
    p.lineTo(W*0.48, H); p.lineTo(0, H); p.close()
    canvas.drawPath(p, fill=1, stroke=0)

    # 4. Barra vertical de acento (izquierda)
    canvas.setFillColor(ACCENT)
    canvas.rect(13*mm, 0, 4, H, fill=1, stroke=0)

    # 5. Logo
    canvas.drawImage(logo_path, 20*mm, H-38*mm, width=60*mm, height=22*mm,
                     preserveAspectRatio=True, mask='auto')

    # 6. Título en blanco + subtítulo en color acento
    canvas.setFillColor(WHITE)
    canvas.setFont('Helvetica-Bold', 52)
    canvas.drawString(20*mm, H*0.555, 'TÍTULO')

    canvas.setFillColor(ACCENT)
    canvas.setFont('Helvetica-Bold', 52)
    canvas.drawString(20*mm, H*0.483, 'SUBTÍTULO')

    # 7. Tagline, autor, edición
    canvas.setFillColor(WHITE)
    canvas.setFont('Helvetica', 15)
    canvas.drawString(20*mm, H*0.435, 'Tagline aquí')

    # 8. Barra dorada al pie
    canvas.setFillColor(ACCENT)
    canvas.rect(0, 0, W, 9*mm, fill=1, stroke=0)

    canvas.restoreState()


def build_cover(styles, TW):
    # Solo un PageBreak — el contenido visual va en on_cover_page
    return [PageBreak()]
```

---

### ✅ IMÁGENES AL LADO DEL TEXTO — GAP obligatorio de 5mm

La función `img_side` debe usar un espacio mínimo de **5mm** entre la columna de imagen y la columna de texto. Sin este GAP el texto queda pegado a la imagen.

```python
def img_side(path, text_paras, side='r', styles=None, TW=None, img_frac=0.42):
    GAP = 5*mm  # ← CRÍTICO: nunca reducir este valor
    iw = TW * img_frac
    tw = TW * (1 - img_frac) - GAP
    im = safe_img_fixed(path, iw, iw * 0.65)
    if not im:
        return text_paras

    if side == 'r':
        data = [[text_paras, [im]]]
        cw = [tw, iw]
        padding = [
            ('LEFTPADDING',  (0,0),(0,0), 0),
            ('RIGHTPADDING', (0,0),(0,0), GAP),  # GAP entre texto e imagen
            ('LEFTPADDING',  (1,0),(1,0), 0),
            ('RIGHTPADDING', (1,0),(1,0), 0),
        ]
    else:
        data = [[[im], text_paras]]
        cw = [iw, tw]
        padding = [
            ('LEFTPADDING',  (0,0),(0,0), 0),
            ('RIGHTPADDING', (0,0),(0,0), 0),
            ('LEFTPADDING',  (1,0),(1,0), GAP),  # GAP entre imagen y texto
            ('RIGHTPADDING', (1,0),(1,0), 0),
        ]

    base = [
        ('VALIGN',        (0,0),(-1,-1), 'TOP'),
        ('TOPPADDING',    (0,0),(-1,-1), 0),
        ('BOTTOMPADDING', (0,0),(-1,-1), 0),
    ]
    tbl = Table(data, colWidths=cw)
    tbl.setStyle(TableStyle(base + padding))
    return [tbl, Spacer(1, 6*mm)]
```

---

### ✅ HEADER Y FOOTER en páginas interiores

```python
def on_page(canvas, doc):
    canvas.saveState()

    # Header — barra color primario con logo y nombre
    canvas.setFillColor(PRIMARY)
    canvas.rect(0, H - 18*mm, W, 18*mm, fill=1, stroke=0)
    canvas.drawImage(logo_path, 12*mm, H-15*mm, width=28*mm, height=10*mm,
                     preserveAspectRatio=True, mask='auto')
    canvas.setFillColor(ACCENT)
    canvas.setFont('Helvetica-Bold', 8)
    canvas.drawRightString(W - 12*mm, H - 10*mm, 'NOMBRE DEL LIBRO')
    canvas.setFillColor(WHITE)
    canvas.setFont('Helvetica', 7.5)
    canvas.drawRightString(W - 12*mm, H - 14*mm, 'Tagline')

    # Footer — barra color primario con número de página
    canvas.setFillColor(PRIMARY)
    canvas.rect(0, 0, W, 12*mm, fill=1, stroke=0)
    canvas.setFillColor(ACCENT)
    canvas.rect(0, 0, W, 1.5*mm, fill=1, stroke=0)
    canvas.setFillColor(WHITE)
    canvas.setFont('Helvetica', 8)
    canvas.drawCentredString(W/2, 4*mm, f'Nombre Institución  ·  {doc.page}')

    canvas.restoreState()
```

---

### ✅ ENCABEZADOS DE CAPÍTULO

Cada capítulo abre con un bloque visual en color primario que incluye etiqueta, título y subtítulo. Se construye con una Tabla de una sola celda:

```python
def chapter_header(num, title, subtitle, styles, TW):
    label = f'CAPÍTULO {num}' if num else 'INTRODUCCIÓN'
    inner = [
        Spacer(1, 8*mm),
        Paragraph(label,    styles['chapter_label']),
        Spacer(1, 3*mm),
        Paragraph(title,    styles['chapter_title']),
        Spacer(1, 4*mm),
        HRFlowable(width=TW*0.4, thickness=2, color=ACCENT, spaceAfter=4),
        Paragraph(subtitle, styles['chapter_sub']),
        Spacer(1, 8*mm),
    ]
    tbl = Table([[inner]], colWidths=[TW])
    tbl.setStyle(TableStyle([
        ('BACKGROUND',     (0,0),(-1,-1), PRIMARY),
        ('LEFTPADDING',    (0,0),(-1,-1), 12),
        ('RIGHTPADDING',   (0,0),(-1,-1), 12),
        ('TOPPADDING',     (0,0),(-1,-1), 0),
        ('BOTTOMPADDING',  (0,0),(-1,-1), 0),
        ('ROUNDEDCORNERS', [6]),
    ]))
    return [tbl, Spacer(1, 8*mm)]
```

---

### ✅ CAJAS DE CITA

```python
def quote_box(text, styles, TW):
    inner = [
        Spacer(1, 4*mm),
        Paragraph(f'❝ {text} ❞', styles['quote']),
        Spacer(1, 4*mm),
    ]
    tbl = Table([[inner]], colWidths=[TW])
    tbl.setStyle(TableStyle([
        ('BACKGROUND',    (0,0),(-1,-1), LIGHT_GOLD),
        ('LEFTPADDING',   (0,0),(-1,-1), 10),
        ('RIGHTPADDING',  (0,0),(-1,-1), 10),
        ('TOPPADDING',    (0,0),(-1,-1), 0),
        ('BOTTOMPADDING', (0,0),(-1,-1), 0),
        ('ROUNDEDCORNERS',[4]),
        ('BOX',           (0,0),(-1,-1), 1.5, ACCENT),
    ]))
    return [tbl, Spacer(1, 6*mm)]
```

---

## Parámetros de entrada

| # | Elemento | Obligatorio |
|---|----------|-------------|
| 1 | Documento (PDF / DOCX / TXT / MD) | ✅ |
| 2 | Imágenes (máximo 20) | ✅ |
| 3 | Logo (JPG o PNG) | ✅ |
| 4 | Color principal (hex) | ✅ |
| 5 | Color secundario / acento (hex) | ✅ |

---

## Asignación de imágenes

Claude debe analizar el contenido de cada capítulo y asignar la imagen más pertinente temáticamente. No asignar imágenes de forma aleatoria.

Máximo **2 imágenes por capítulo**: una en layout lateral (`img_side`) y una como banner completo (`img_full`) si aplica.

---

## Selección de imagen para la portada

Usar la imagen más impactante visualmente (buena composición, colores que contrasten con el color primario). Idealmente una foto con área oscura o neutra en el lado derecho donde irá el overlay.

---

## Output

```python
output_path = "/mnt/user-data/outputs/ebook_diseñado.pdf"
# Siempre usar:
doc.build(story, onFirstPage=on_cover_page, onLaterPages=on_page)
present_files([output_path])
```

---

## Errores comunes a evitar

| ❌ Error | ✅ Corrección |
|----------|--------------|
| Construir portada con tablas de Platypus | Usar `on_cover_page` con canvas directo |
| GAP entre imagen y texto menor a 5mm | Siempre `GAP = 5*mm` en `img_side` |
| Spacer grande antes del PageBreak en portada | Solo `PageBreak()`, sin Spacer previo |
| Usar fuentes no embebidas | Solo usar fuentes base de ReportLab: Helvetica, Times, Courier |
| Subscripts con caracteres Unicode (₀₁₂) | Usar tags XML de ReportLab: `<sub>` y `<super>` |
