from pathlib import Path

from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.platypus import (
    BaseDocTemplate, Frame, PageBreak, PageTemplate, Paragraph, Spacer, Table,
    TableStyle, Image, KeepTogether, Flowable, NextPageTemplate,
)


ROOT = Path('/Users/montano/Desktop/deltanet-cursos')
TMP = ROOT / 'tmp/pdfs'
OUT = ROOT / 'output/pdf/guia-instalacion-academycore.pdf'
LOGO = TMP / 'academycore-logo.png'
COVER = TMP / 'cover-illustration.png'
SETUP = TMP / 'setup-illustration.png'
CP_DB = TMP / 'cpanel-01-base-datos.png'
CP_UPLOAD = TMP / 'cpanel-02-cargar-archivo.png'
CP_EXTRACT = TMP / 'cpanel-03-extraer-archivo.png'
SETUP_WELCOME = TMP / 'setup-01-bienvenida-crop.png'
SETUP_REQUIREMENTS = TMP / 'setup-02-requisitos-crop.png'
SETUP_IDENTITY = TMP / 'setup-04-identidad-crop.png'
SETUP_DATABASE = TMP / 'setup-05-bd-crop.png'
SETUP_ADMIN = TMP / 'setup-10-admin-crop.png'

NAVY = colors.HexColor('#102C49')
BLUE = colors.HexColor('#1F66AF')
PALE_BLUE = colors.HexColor('#EDF5FC')
SKY = colors.HexColor('#DCEBFA')
INK = colors.HexColor('#23384D')
MUTED = colors.HexColor('#687D92')
LINE = colors.HexColor('#D8E3EC')
PANEL = colors.HexColor('#F7FAFD')
GREEN = colors.HexColor('#247B5B')
GOLD = colors.HexColor('#C99738')
RED = colors.HexColor('#B85245')
WHITE = colors.white


class AccentBar(Flowable):
    def __init__(self, width=52, height=4, color=BLUE):
        super().__init__()
        self.width = width
        self.height = height
        self.color = color

    def draw(self):
        self.canv.setFillColor(self.color)
        self.canv.roundRect(0, 0, self.width, self.height, self.height / 2, fill=1, stroke=0)


class StepBadge(Flowable):
    def __init__(self, number, size=18):
        super().__init__()
        self.number = str(number)
        self.size = size
        self.width = size
        self.height = size

    def draw(self):
        self.canv.setFillColor(BLUE)
        self.canv.circle(self.size / 2, self.size / 2, self.size / 2, fill=1, stroke=0)
        self.canv.setFillColor(WHITE)
        self.canv.setFont('Helvetica-Bold', 7)
        self.canv.drawCentredString(self.size / 2, self.size / 2 - 2.4, self.number)


def make_styles():
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(
        name='GuideKicker', parent=styles['Normal'], fontName='Helvetica-Bold',
        fontSize=7.6, leading=10, textColor=BLUE, spaceAfter=5, tracking=1.1,
    ))
    styles.add(ParagraphStyle(
        name='GuideTitle', parent=styles['Title'], fontName='Helvetica-Bold',
        fontSize=25, leading=29, textColor=NAVY, spaceAfter=9,
    ))
    styles.add(ParagraphStyle(
        name='GuideHeading', parent=styles['Heading1'], fontName='Helvetica-Bold',
        fontSize=17, leading=21, textColor=NAVY, spaceBefore=2, spaceAfter=9,
    ))
    styles.add(ParagraphStyle(
        name='GuideSubheading', parent=styles['Heading2'], fontName='Helvetica-Bold',
        fontSize=10.4, leading=14, textColor=INK, spaceBefore=8, spaceAfter=5,
    ))
    styles.add(ParagraphStyle(
        name='GuideBody', parent=styles['BodyText'], fontName='Helvetica',
        fontSize=8.7, leading=13.4, textColor=INK, spaceAfter=6,
    ))
    styles.add(ParagraphStyle(
        name='GuideSmall', parent=styles['BodyText'], fontName='Helvetica',
        fontSize=7.4, leading=10.5, textColor=MUTED,
    ))
    styles.add(ParagraphStyle(
        name='GuideCallout', parent=styles['BodyText'], fontName='Helvetica',
        fontSize=8.2, leading=12.2, textColor=INK,
    ))
    styles.add(ParagraphStyle(
        name='GuideCode', parent=styles['Code'], fontName='Courier',
        fontSize=7.3, leading=10.2, textColor=colors.HexColor('#244565'),
    ))
    styles.add(ParagraphStyle(
        name='CoverTitle', parent=styles['Title'], fontName='Helvetica-Bold',
        fontSize=32, leading=36, textColor=NAVY,
    ))
    styles.add(ParagraphStyle(
        name='CoverSubtitle', parent=styles['BodyText'], fontName='Helvetica',
        fontSize=11, leading=16, textColor=MUTED,
    ))
    return styles


S = make_styles()


def p(text, style='GuideBody'):
    return Paragraph(text, S[style])


def list_rows(items):
    rows = []
    for item in items:
        rows.append([p('<font color="#1F66AF">•</font>', 'GuideBody'), p(item, 'GuideBody')])
    table = Table(rows, colWidths=[0.35 * cm, 15.4 * cm], hAlign='LEFT')
    table.setStyle(TableStyle([
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 0),
        ('RIGHTPADDING', (0, 0), (-1, -1), 0),
        ('TOPPADDING', (0, 0), (-1, -1), 1.5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 1.5),
    ]))
    return table


def section_title(kicker, title, intro=None):
    story = [p(kicker.upper(), 'GuideKicker'), p(title, 'GuideHeading'), AccentBar(), Spacer(1, 9)]
    if intro:
        story += [p(intro, 'GuideBody'), Spacer(1, 4)]
    return story


def callout(title, body, tone='blue'):
    border = {'blue': BLUE, 'green': GREEN, 'gold': GOLD, 'red': RED}[tone]
    body_cell = Table([[p(f'<b>{title}</b><br/>{body}', 'GuideCallout')]], colWidths=[16.45 * cm])
    body_cell.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), PANEL),
        ('BOX', (0, 0), (-1, -1), 0.7, LINE),
        ('LINEBEFORE', (0, 0), (0, -1), 3, border),
        ('LEFTPADDING', (0, 0), (-1, -1), 10),
        ('RIGHTPADDING', (0, 0), (-1, -1), 10),
        ('TOPPADDING', (0, 0), (-1, -1), 8),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
    ]))
    return body_cell


def info_table(headers, rows, widths):
    data = [[p(f'<b>{header}</b>', 'GuideSmall') for header in headers]]
    data += [[p(cell, 'GuideSmall') for cell in row] for row in rows]
    table = Table(data, colWidths=widths, repeatRows=1, hAlign='LEFT')
    table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), NAVY),
        ('TEXTCOLOR', (0, 0), (-1, 0), WHITE),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('GRID', (0, 0), (-1, -1), 0.45, LINE),
        ('BACKGROUND', (0, 1), (-1, -1), WHITE),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [WHITE, PANEL]),
        ('LEFTPADDING', (0, 0), (-1, -1), 7),
        ('RIGHTPADDING', (0, 0), (-1, -1), 7),
        ('TOPPADDING', (0, 0), (-1, -1), 6),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
    ]))
    return table


def code_block(text):
    lines = [[p(line.replace(' ', '&nbsp;'), 'GuideCode')] for line in text.splitlines()]
    table = Table(lines, colWidths=[16.45 * cm], hAlign='LEFT')
    table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#F2F6FA')),
        ('BOX', (0, 0), (-1, -1), 0.65, LINE),
        ('LEFTPADDING', (0, 0), (-1, -1), 8),
        ('RIGHTPADDING', (0, 0), (-1, -1), 8),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
    ]))
    return table


def screenshot_figure(path, caption, width=16.45 * cm, height=6.8 * cm):
    image = Image(str(path), width=width, height=height, kind='proportional')
    image.hAlign = 'CENTER'
    caption_row = [p(f'<font color="#1F66AF">CAPTURA DE REFERENCIA</font><br/>{caption}', 'GuideSmall')]
    figure = Table([[image], caption_row], colWidths=[width], hAlign='LEFT')
    figure.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#F4F8FC')),
        ('BOX', (0, 0), (-1, -1), .55, LINE),
        ('LEFTPADDING', (0, 0), (-1, -1), 5), ('RIGHTPADDING', (0, 0), (-1, -1), 5),
        ('TOPPADDING', (0, 0), (-1, 0), 5), ('BOTTOMPADDING', (0, 0), (-1, 0), 5),
        ('TOPPADDING', (0, 1), (-1, 1), 5), ('BOTTOMPADDING', (0, 1), (-1, 1), 5),
    ]))
    return KeepTogether([figure])


def screenshot_pair(left_path, left_caption, right_path, right_caption, height=4.8 * cm):
    w = 8.04 * cm
    left = [Image(str(left_path), width=w, height=height, kind='proportional'), p(left_caption, 'GuideSmall')]
    right = [Image(str(right_path), width=w, height=height, kind='proportional'), p(right_caption, 'GuideSmall')]
    pair = Table([[left, right]], colWidths=[8.2 * cm, 8.2 * cm], hAlign='LEFT')
    pair.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#F4F8FC')),
        ('BOX', (0, 0), (-1, -1), .55, LINE), ('INNERGRID', (0, 0), (-1, -1), .45, LINE),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 5), ('RIGHTPADDING', (0, 0), (-1, -1), 5),
        ('TOPPADDING', (0, 0), (-1, -1), 5), ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
    ]))
    return KeepTogether([pair])


def install_step(number, title, text):
    table = Table([[StepBadge(number), p(f'<b>{title}</b><br/>{text}', 'GuideSmall')]], colWidths=[0.85 * cm, 15.6 * cm])
    table.setStyle(TableStyle([
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 0),
        ('RIGHTPADDING', (0, 0), (-1, -1), 0),
        ('TOPPADDING', (0, 0), (-1, -1), 4),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
        ('LINEBELOW', (0, 0), (-1, 0), 0.35, LINE),
    ]))
    return table


def draw_header_footer(canvas, doc):
    canvas.saveState()
    page = canvas.getPageNumber()
    if page > 1:
        canvas.setStrokeColor(LINE)
        canvas.setLineWidth(.45)
        canvas.line(1.65 * cm, A4[1] - 1.2 * cm, A4[0] - 1.65 * cm, A4[1] - 1.2 * cm)
        canvas.setFont('Helvetica-Bold', 7)
        canvas.setFillColor(NAVY)
        canvas.drawString(1.65 * cm, A4[1] - .92 * cm, 'ACADEMYCORE')
        canvas.setFont('Helvetica', 6.7)
        canvas.setFillColor(MUTED)
        canvas.drawRightString(A4[0] - 1.65 * cm, A4[1] - .92 * cm, 'Guía de instalación y puesta en marcha')
        canvas.line(1.65 * cm, 1.22 * cm, A4[0] - 1.65 * cm, 1.22 * cm)
        canvas.setFont('Helvetica', 6.7)
        canvas.setFillColor(MUTED)
        canvas.drawString(1.65 * cm, .88 * cm, 'Hecho con ')
        canvas.setFillColor(colors.HexColor('#D85062'))
        canvas.drawString(3.05 * cm, .88 * cm, '♥')
        canvas.setFillColor(MUTED)
        canvas.drawString(3.3 * cm, .88 * cm, ' por DeltaNet  ·  deltanet.com.mx')
        canvas.linkURL('https://deltanet.com.mx', (4.65 * cm, .72 * cm, 8.25 * cm, 1.05 * cm), relative=0, thickness=0)
        canvas.drawRightString(A4[0] - 1.65 * cm, .88 * cm, f'Página {page}')
    canvas.restoreState()


def draw_internal_chrome_end(canvas, doc):
    if canvas.getPageNumber() > 1:
        draw_header_footer(canvas, doc)


def draw_cover(canvas, doc):
    # Si ReportLab vuelve al primer template en un salto manual, conserva la
    # identidad de páginas internas en lugar de repetir el fondo de portada.
    if canvas.getPageNumber() > 1:
        draw_header_footer(canvas, doc)
        return
    canvas.saveState()
    canvas.setTitle('Guía profesional de instalación - AcademyCore')
    canvas.setAuthor('DeltaNet')
    canvas.setSubject('Instalación AcademyCore en hosting cPanel y setup guiado')
    w, h = A4
    canvas.setFillColor(colors.HexColor('#F5F8FC'))
    canvas.rect(0, 0, w, h, fill=1, stroke=0)
    canvas.setFillColor(NAVY)
    canvas.rect(0, 0, 1.15 * cm, h, fill=1, stroke=0)
    canvas.setFillColor(colors.HexColor('#E1EEF9'))
    canvas.circle(w - 1.1 * cm, h - 1.0 * cm, 2.1 * cm, fill=1, stroke=0)
    canvas.restoreState()


def build_pdf():
    doc = BaseDocTemplate(
        str(OUT), pagesize=A4, rightMargin=1.65 * cm, leftMargin=1.65 * cm,
        topMargin=1.6 * cm, bottomMargin=1.5 * cm,
    )
    frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
    doc.addPageTemplates([
        PageTemplate(id='cover', frames=[frame], onPage=draw_cover, onPageEnd=draw_internal_chrome_end, autoNextPageTemplate='main'),
        PageTemplate(id='main', frames=[frame], onPage=draw_header_footer, onPageEnd=draw_internal_chrome_end, autoNextPageTemplate='main'),
    ])

    story = []
    # Cover
    story.append(Spacer(1, .4 * cm))
    story.append(Image(str(LOGO), width=5.2 * cm, height=1.0 * cm, kind='proportional'))
    story.append(Spacer(1, 1.3 * cm))
    story.append(p('DOCUMENTACIÓN TÉCNICA Y OPERATIVA', 'GuideKicker'))
    story.append(p('Guía profesional de instalación', 'CoverTitle'))
    story.append(p('AcademyCore - Plataforma de cursos Laravel', 'CoverTitle'))
    story.append(Spacer(1, .35 * cm))
    story.append(p('Proceso completo desde la preparación del servidor y cPanel hasta la configuración guiada, la validación de licencia y la puesta en marcha.', 'CoverSubtitle'))
    story.append(Spacer(1, .8 * cm))
    cover_table = Table([[p('<b>DIRIGIDA A</b><br/>Administradores, equipos TI y proveedores de hosting.', 'GuideSmall'),
                          p('<b>PLATAFORMA</b><br/>Laravel 13 · PHP 8.3 · MySQL/MariaDB o SQLite.', 'GuideSmall'),
                          p('<b>EDICIÓN</b><br/>Agosto de 2026 · instalación guiada AcademyCore.', 'GuideSmall')]], colWidths=[5.45 * cm] * 3)
    cover_table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), WHITE),
        ('BOX', (0, 0), (-1, -1), .6, LINE),
        ('INNERGRID', (0, 0), (-1, -1), .45, LINE),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 8), ('RIGHTPADDING', (0, 0), (-1, -1), 8),
        ('TOPPADDING', (0, 0), (-1, -1), 8), ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
    ]))
    story.append(cover_table)
    story.append(Spacer(1, .8 * cm))
    story.append(Image(str(COVER), width=16.9 * cm, height=8.45 * cm, kind='proportional'))
    story.append(Spacer(1, .25 * cm))
    story.append(p('Hecho con <font color="#D85062">♥</font> por <b>DeltaNet</b>  ·  <font color="#1F66AF">www.deltanet.com.mx</font>', 'GuideSmall'))
    story.append(NextPageTemplate('main'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 2: map
    story.extend(section_title('01 · Visión general', 'Antes de iniciar: mapa de implementación',
        'AcademyCore se instala mediante un asistente web de 12 pasos. La preparación correcta de hosting y dominio evita la mayoría de incidencias posteriores.'))
    overview_rows = [
        [p('<b>1. Hosting y dominio</b><br/>Configurar PHP, SSL, directorio público y acceso a archivos.', 'GuideSmall'),
         p('<b>2. Base de datos</b><br/>Crear base, usuario y contraseña en cPanel.', 'GuideSmall'),
         p('<b>3. Asistente AcademyCore</b><br/>Validar servidor, licencia y servicios.', 'GuideSmall'),
         p('<b>4. Puesta en marcha</b><br/>Ingresar, personalizar y comprobar correos.', 'GuideSmall')]
    ]
    overview = Table(overview_rows, colWidths=[4.1 * cm] * 4)
    overview.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), PALE_BLUE), ('BOX', (0, 0), (-1, -1), .7, LINE),
        ('INNERGRID', (0, 0), (-1, -1), .5, LINE), ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 8), ('RIGHTPADDING', (0, 0), (-1, -1), 8),
        ('TOPPADDING', (0, 0), (-1, -1), 10), ('BOTTOMPADDING', (0, 0), (-1, -1), 10),
    ]))
    story.append(overview)
    story.append(Spacer(1, 12))
    story.append(p('Contenido de la guía', 'GuideSubheading'))
    toc = [
        ('01', 'Requisitos y planeación', 'PHP, extensiones, permisos y datos que debes tener a la mano.'),
        ('02', 'Preparación en cPanel', 'Dominio, versión de PHP, SSL, archivos y base de datos.'),
        ('03', 'Instalación desde el setup', 'Los 12 pasos del asistente AcademyCore, explicados uno a uno.'),
        ('04', 'Configuración posterior', 'Primer acceso, identidad, correo, seguridad y validación final.'),
        ('05', 'Solución de problemas', 'Diagnóstico rápido de errores de permisos, base de datos y URL.'),
    ]
    toc_rows = [[p(f'<b>{n}</b>', 'GuideSmall'), p(f'<b>{title}</b><br/>{desc}', 'GuideSmall')] for n, title, desc in toc]
    toc_table = Table(toc_rows, colWidths=[1.0 * cm, 15.3 * cm])
    toc_table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (0, -1), NAVY), ('TEXTCOLOR', (0, 0), (0, -1), WHITE),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), .4, LINE),
        ('ROWBACKGROUNDS', (1, 0), (-1, -1), [WHITE, PANEL]),
        ('LEFTPADDING', (0, 0), (-1, -1), 8), ('RIGHTPADDING', (0, 0), (-1, -1), 8),
        ('TOPPADDING', (0, 0), (-1, -1), 7), ('BOTTOMPADDING', (0, 0), (-1, -1), 7),
    ]))
    story.append(toc_table)
    story.append(Spacer(1, 12))
    story.append(callout('Principio de seguridad', 'Nunca compartas claves de cPanel, base de datos, SMTP o licencia en capturas públicas. Mantén una copia segura de las credenciales antes de ejecutar el asistente.', 'gold'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 3 requirements
    story.extend(section_title('02 · Requisitos', 'Servidor, software y accesos necesarios',
        'Confirma estas condiciones antes de subir archivos. AcademyCore valida varias de ellas en su segundo y tercer paso, pero resolverlas desde cPanel acelera la instalación.'))
    story.append(info_table(['Componente', 'Mínimo recomendado', 'Dónde revisarlo'], [
        ['PHP', '<b>8.3</b> o superior. Usar PHP 8.3/8.4 estable.', 'cPanel → MultiPHP Manager o Select PHP Version.'],
        ['Base de datos', 'MySQL 8+, MariaDB 10.5+ o SQLite para entornos simples.', 'cPanel → MySQL Databases.'],
        ['Extensiones PHP', 'ctype, curl, dom, fileinfo, filter, hash, mbstring, openssl, pdo, session, tokenizer y xml.', 'cPanel → Select PHP Extensions.'],
        ['HTTPS', 'Certificado SSL activo en el dominio final.', 'cPanel → SSL/TLS Status.'],
        ['Espacio', 'Mínimo 1 GB libre; 5 GB+ para contenido multimedia inicial.', 'Panel del proveedor o Disk Usage.'],
        ['Acceso', 'Administrador de archivos y, de preferencia, SSH/Terminal.', 'cPanel → File Manager / Terminal.'],
    ], [3.2 * cm, 7.25 * cm, 6.0 * cm]))
    story.append(Spacer(1, 10))
    story.append(p('Información que debes preparar', 'GuideSubheading'))
    story.append(list_rows([
        '<b>Dominio definitivo:</b> por ejemplo, <font color="#1F66AF">https://cursos.tudominio.com</font>. Evita instalar inicialmente con una URL temporal si el sitio se publicará en otro dominio.',
        '<b>Licencia DeltaNet:</b> clave de activación y, si aplica, URL del servidor de licencias entregada con tu compra.',
        '<b>Datos de base de datos:</b> nombre de base, usuario y contraseña creados para esta instalación.',
        '<b>Correo remitente:</b> cuenta institucional desde la que saldrán altas, restablecimientos y avisos.',
        '<b>Cuenta principal:</b> nombre, correo y contraseña robusta del superadministrador.',
    ]))
    story.append(Spacer(1, 8))
    story.append(callout('Recomendación de hosting', 'Para un LMS con archivos y varios usuarios, utiliza hosting Linux con cPanel, PHP 8.3, base de datos MySQL/MariaDB y soporte de tareas programadas. Para catálogos grandes, considera VPS o hosting administrado.', 'blue'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 4 cPanel deployment
    story.extend(section_title('03 · cPanel', 'Preparar dominio, directorio público y archivos',
        'Estas instrucciones cubren la instalación en un dominio principal o subdominio. Los nombres de menús pueden variar ligeramente según el proveedor.'))
    story.append(screenshot_figure(
        CP_UPLOAD,
        'cPanel File Manager: abre <b>Upload</b> desde la carpeta del proyecto y selecciona el archivo ZIP entregado. La apariencia puede variar según el proveedor.',
        height=6.45 * cm,
    ))
    story.append(Spacer(1, 8))
    cpanel_steps = [
        ('1', 'Crear o seleccionar el dominio', 'En <b>Domains</b>, agrega el dominio o subdominio. Activa SSL y confirma que abre por HTTPS antes de continuar.'),
        ('2', 'Definir el document root', 'El dominio debe apuntar a la carpeta <b>public</b> del proyecto, por ejemplo <b>/home/USUARIO/academycore/public</b>. No expongas la raíz completa del proyecto.'),
        ('3', 'Seleccionar PHP 8.3', 'En <b>MultiPHP Manager</b> o <b>Select PHP Version</b>, asigna PHP 8.3 o superior al dominio. Habilita las extensiones indicadas en la página anterior.'),
        ('4', 'Subir el proyecto', 'En <b>File Manager</b>, crea una carpeta fuera de public_html o dentro de ella con acceso controlado. Sube y extrae el paquete de AcademyCore conservando toda la estructura.'),
        ('5', 'Configurar permisos de escritura', 'Asegura escritura para <b>storage</b> y <b>bootstrap/cache</b>. Usa 775 normalmente; el propietario debe coincidir con el usuario de la cuenta cPanel.'),
    ]
    story.extend([install_step(n, title, text) for n, title, text in cpanel_steps])
    story.append(Spacer(1, 8))
    story.append(callout('Importante: raíz pública', 'Si tu proveedor no permite apuntar el document root a <b>public</b>, solicita soporte al hosting. No muevas el contenido de <b>public</b> a la raíz sin revisar primero las rutas de index.php y los riesgos de seguridad.', 'red'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 5 database and terminal
    story.extend(section_title('04 · Base de datos', 'Crear la conexión en cPanel y preparar Laravel',
        'El asistente guarda la configuración por ti, pero la base de datos debe existir y el usuario debe tener privilegios completos antes de ejecutar la instalación.'))
    story.append(screenshot_figure(
        CP_DB,
        'cPanel <b>MySQL Database Wizard</b>: escribe un nombre para una base exclusiva de AcademyCore. Conserva el prefijo que cPanel agrega al nombre.',
        height=5.75 * cm,
    ))
    story.append(Spacer(1, 7))
    story.append(p('Crear base de datos en cPanel', 'GuideSubheading'))
    story.extend([install_step(n, title, text) for n, title, text in [
        ('1', 'Abrir MySQL Databases', 'En cPanel, entra a <b>MySQL Databases</b> o <b>MySQL Database Wizard</b>.'),
        ('2', 'Crear la base', 'Crea una base exclusiva, por ejemplo <b>usuario_academycore</b>. El prefijo del usuario cPanel suele añadirse automáticamente.'),
        ('3', 'Crear usuario', 'Genera un usuario de base con contraseña única, larga y almacenada en un gestor seguro.'),
        ('4', 'Asignar privilegios', 'Asocia el usuario a la base y selecciona <b>ALL PRIVILEGES</b>. No uses una base compartida con otra aplicación.'),
    ]])
    story.append(Spacer(1, 7))
    story.append(callout('Privilegios obligatorios', 'Al terminar el asistente de MySQL, asigna el usuario creado a la base y marca <b>ALL PRIVILEGES</b>. AcademyCore necesita crear tablas, índices y registros iniciales durante el paso 9.', 'blue'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 6 cPanel files and Laravel finishing
    story.extend(section_title('04 · Archivos y estructura', 'Extraer el proyecto y proteger la carpeta pública',
        'Después de cargar el ZIP, extrae el paquete dentro de la carpeta del proyecto. Mantén la estructura Laravel completa: app, bootstrap, config, public, resources, routes, storage y vendor.'))
    story.append(screenshot_figure(
        CP_EXTRACT,
        'cPanel File Manager: selecciona el archivo ZIP y utiliza <b>Extract</b>. Extrae dentro de la carpeta de AcademyCore, no directamente en una carpeta compartida.',
        height=5.45 * cm,
    ))
    story.append(Spacer(1, 8))
    story.append(p('Uso recomendado de Terminal / SSH', 'GuideSubheading'))
    story.append(p('Si tu hosting ofrece Terminal, ejecuta estos comandos desde la carpeta raíz del proyecto antes de abrir el setup. Si el paquete ya incluye vendor, Composer puede no ser necesario.', 'GuideBody'))
    story.append(code_block('cd /home/USUARIO/academycore\ncomposer install --no-dev --optimize-autoloader\nphp artisan storage:link'))
    story.append(Spacer(1, 7))
    story.append(info_table(['Dato para el setup', 'Ejemplo', 'Nota'], [
        ['Motor', 'mysql o mariadb', 'Selecciona el que corresponda a tu hosting.'],
        ['Servidor', 'localhost o 127.0.0.1', 'Tu proveedor puede indicar un host remoto.'],
        ['Puerto', '3306', 'Mantén el valor del hosting si es distinto.'],
        ['Base de datos', 'usuario_academycore', 'Incluye el prefijo cuando cPanel lo agregue.'],
        ['Usuario', 'usuario_academy', 'Usa el usuario creado exclusivamente para AcademyCore.'],
    ], [3.6 * cm, 4.5 * cm, 8.35 * cm]))
    story.append(Spacer(1, 8))
    story.append(callout('No edites manualmente el .env durante el setup', 'El asistente genera el archivo de entorno con URL, conexión, correo, almacenamiento y licencia. Solo modifica .env después de instalar si el soporte técnico te lo indica.', 'blue'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Pages 6-7 wizard
    story.extend(section_title('05 · Setup AcademyCore', 'Completar el asistente de instalación',
        'Abre el dominio configurado. Si AcademyCore aún no está instalado, el sistema mostrará automáticamente el asistente. Completa los pasos en orden y no cierres el navegador durante la migración.'))
    story.append(screenshot_figure(
        SETUP_WELCOME,
        'Paso 1 del setup de AcademyCore: el asistente presenta los doce pasos y permite iniciar una instalación guiada.',
        height=7.0 * cm,
    ))
    story.append(Spacer(1, 7))
    story.append(p('Pasos 1 a 6: validación e infraestructura', 'GuideSubheading'))
    setup_steps_a = [
        ('1', 'Inicio', 'Lee el resumen y selecciona <b>Comenzar configuración</b>.'),
        ('2', 'Servidor', 'Revisa que PHP 8.3 y cada extensión estén marcados como disponibles. Corrige cualquier requisito pendiente en cPanel.'),
        ('3', 'Permisos', 'Confirma escritura en storage, framework, bootstrap/cache y la carpeta del proyecto. Ajusta propietario/permisos si falla.'),
    ]
    story.extend([install_step(n, title, text) for n, title, text in setup_steps_a])
    story.extend([NextPageTemplate('main'), PageBreak()])

    story.extend(section_title('05 · Setup AcademyCore', 'Identidad, licencia y base de datos',
        'Con los requisitos aprobados, captura la identidad pública de la academia y los datos creados previamente en cPanel. Esta información se guardará de forma segura en el entorno de la aplicación.'))
    story.append(screenshot_pair(
        SETUP_IDENTITY,
        '<font color="#1F66AF">PASO 4</font><br/>Nombre, URL HTTPS, licencia, zona horaria, idioma y moneda.',
        SETUP_DATABASE,
        '<font color="#1F66AF">PASO 5</font><br/>Motor, host, puerto, base, usuario y contraseña de MySQL/MariaDB.',
        height=4.65 * cm,
    ))
    story.append(Spacer(1, 9))
    setup_steps_a = [
        ('4', 'Identidad y licencia', 'Ingresa nombre de plataforma, URL pública HTTPS, clave DeltaNet, zona horaria, idioma y moneda. La licencia se valida contra el dominio.'),
        ('5', 'Base de datos', 'Captura el motor, host, puerto, base, usuario y contraseña creados en cPanel.'),
        ('6', 'Conexión', 'Ejecuta la prueba. Si falla, revisa host, prefijos de cPanel, contraseña y privilegios completos.'),
    ]
    story.extend([install_step(n, title, text) for n, title, text in setup_steps_a])
    story.append(Spacer(1, 8))
    story.append(callout('Validación de licencia', 'La licencia se activa una vez y queda vinculada a la URL pública. Antes de validar, confirma que el dominio final responde por HTTPS y no apunta a una URL temporal.', 'gold'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    story.extend(section_title('05 · Setup AcademyCore', 'Completar el asistente de instalación - continuación',
        'La segunda mitad define los servicios operativos, genera la estructura de datos y crea la cuenta que controlará la plataforma.'))
    story.append(screenshot_figure(
        SETUP_ADMIN,
        'Paso 10 del setup: crea la cuenta superadministradora que tendrá control de usuarios, cursos, cobros, roles y configuración.',
        height=5.75 * cm,
    ))
    story.append(Spacer(1, 7))
    story.append(p('Pasos 7 a 12: operación y entrega', 'GuideSubheading'))
    setup_steps_b = [
        ('7', 'Correo', 'Configura SMTP si el proveedor ya entregó credenciales. Puedes elegir registro local para continuar y configurar SMTP después desde el panel.'),
        ('8', 'Archivos', 'Selecciona almacenamiento local para empezar en cPanel. Usa S3 cuando tengas credenciales y una estrategia de almacenamiento externo.'),
        ('9', 'Instalación', 'Confirma la creación de tablas, roles, permisos y llave segura. No cierres la ventana hasta que el proceso termine.'),
        ('10', 'Administrador', 'Crea la cuenta superadministradora. Usa un correo corporativo y contraseña de al menos 10 caracteres con mayúsculas, número y símbolo.'),
        ('11', 'Contenido', 'Elige entre instalación vacía o contenido de demostración. Los ejemplos se pueden eliminar después desde el administrador.'),
        ('12', 'Listo', 'Ingresa al inicio de sesión, conserva las credenciales en un lugar seguro y abre el panel de administración.'),
    ]
    story.extend([install_step(n, title, text) for n, title, text in setup_steps_b])
    story.append(Spacer(1, 9))
    story.append(callout('Configuración recomendada para producción', 'Usa SMTP real antes de invitar usuarios, configura una paleta e identidad, verifica la URL HTTPS y revisa los métodos de pago antes de publicar cursos.', 'green'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 8 post setup
    story.extend(section_title('06 · Puesta en marcha', 'Qué configurar después del primer acceso',
        'Al iniciar sesión como superadministrador, utiliza el panel para convertir la instalación técnica en una academia lista para tus usuarios.'))
    story.append(info_table(['Prioridad', 'Ubicación en el panel', 'Acción recomendada'], [
        ['1', 'Configuración → Identidad', 'Cambiar nombre, logotipo, favicon y descripción institucional.'],
        ['2', 'Configuración → Apariencia e inicio', 'Definir paleta de colores, portada, mensaje principal y medios visuales.'],
        ['3', 'Configuración → Atención', 'Registrar correo de soporte, teléfono, horario y avisos.'],
        ['4', 'Configuración → Cobros', 'Activar y probar Stripe, PayPal, Mercado Pago u offline según tu operación.'],
        ['5', 'Personas → Instructores', 'Crear responsables académicos y asignarlos a los cursos.'],
        ['6', 'Gestión académica → Cursos', 'Crear categorías, cursos, módulos, lecciones y reglas de publicación.'],
        ['7', 'Sistema → Seguridad', 'Revisar auditoría, roles, permisos y política de contraseñas.'],
    ], [1.25 * cm, 5.1 * cm, 10.1 * cm]))
    story.append(Spacer(1, 10))
    story.append(p('Comprobación de salida', 'GuideSubheading'))
    story.append(list_rows([
        'El sitio público abre por <b>HTTPS</b> y el logotipo aparece correctamente.',
        'Puedes iniciar sesión con la cuenta creada en el paso 10.',
        'Puedes crear un instructor y guardar un curso de prueba.',
        'El correo de prueba llega si SMTP está configurado.',
        'El almacenamiento permite subir una imagen de portada y el archivo se muestra.',
        'Los métodos de pago están desactivados o probados antes de publicar cursos de pago.',
    ]))
    story.append(Spacer(1, 9))
    story.append(callout('Respaldo inicial', 'Cuando termines la configuración, descarga una copia de base de datos desde phpMyAdmin y conserva una copia del proyecto. Programa respaldos periódicos desde tu hosting.', 'green'))
    story.extend([NextPageTemplate('main'), PageBreak()])

    # Page 9 troubleshooting and handoff
    story.extend(section_title('07 · Soporte y diagnóstico', 'Resolver incidencias frecuentes',
        'Revisa el síntoma, la causa más habitual y la primera acción segura. Si necesitas ayuda, prepara los datos técnicos sin enviar contraseñas o claves completas.'))
    story.append(info_table(['Síntoma', 'Causa frecuente', 'Primera acción'], [
        ['Pantalla 500 o blanca', 'PHP incompatible, extensión faltante o permisos insuficientes.', 'Cambiar a PHP 8.3, revisar error_log y validar storage/bootstrap/cache.'],
        ['No conecta a la base', 'Host, prefijo de cPanel, contraseña o privilegios incorrectos.', 'Revisar datos en MySQL Databases y repetir el paso 5/6.'],
        ['El dominio muestra archivos', 'Document root apunta a la raíz del proyecto y no a public.', 'Ajustar el directorio público del dominio a /public.'],
        ['No llegan correos', 'SMTP pendiente o credenciales inválidas.', 'Usar registro local temporal y configurar SMTP desde Configuración.'],
        ['No suben imágenes', 'storage sin escritura o enlace público faltante.', 'Revisar permisos y ejecutar php artisan storage:link por Terminal.'],
        ['La licencia no valida', 'URL distinta, SSL incompleto o clave incorrecta.', 'Confirmar URL HTTPS final y revisar la clave con DeltaNet.'],
    ], [4.0 * cm, 5.4 * cm, 7.05 * cm]))
    story.append(Spacer(1, 10))
    story.append(p('Información útil para soporte', 'GuideSubheading'))
    story.append(list_rows([
        'Dominio de instalación y proveedor de hosting.',
        'Paso del asistente donde ocurre el problema y el texto exacto del mensaje.',
        'Versión de PHP seleccionada y motor de base de datos.',
        'Captura del error sin contraseñas, claves de licencia ni datos sensibles.',
        'Fragmento relevante de error_log o storage/logs/laravel.log, ocultando secretos.',
    ]))
    story.append(Spacer(1, 6))
    story.append(p('Las capturas de cPanel son referencias visuales de interfaz. Consulta la documentación oficial para variantes de tu proveedor: <link href="https://docs.cpanel.net/cpanel/databases/mysql-database-wizard/" color="#1F66AF">MySQL Database Wizard</link> y <link href="https://docs.cpanel.net/cpanel/files/file-manager/" color="#1F66AF">File Manager</link>.', 'GuideSmall'))
    story.append(Spacer(1, 12))
    closing = Table([[p('<b>DeltaNet</b><br/>Acompañamiento para la puesta en marcha, licencia y evolución de tu plataforma.', 'GuideCallout'),
                      p('<b>www.deltanet.com.mx</b><br/>Comparte esta guía con tu equipo técnico o proveedor de hosting antes de instalar.', 'GuideCallout')]], colWidths=[8.2 * cm, 8.25 * cm])
    closing.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, -1), PALE_BLUE), ('BOX', (0, 0), (-1, -1), .7, LINE),
        ('INNERGRID', (0, 0), (-1, -1), .45, LINE), ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('LEFTPADDING', (0, 0), (-1, -1), 10), ('RIGHTPADDING', (0, 0), (-1, -1), 10),
        ('TOPPADDING', (0, 0), (-1, -1), 10), ('BOTTOMPADDING', (0, 0), (-1, -1), 10),
    ]))
    story.append(closing)

    doc.build(story)


if __name__ == '__main__':
    OUT.parent.mkdir(parents=True, exist_ok=True)
    build_pdf()
    print(OUT)
