yazrkisvs3vvvsv33svrhetrjsvyvsvvyjsvsvs
tavsuysvssvvjvvyjyjkvsvqd
qfogsvvsvsegjdgdfgdskhgdksvqsvshdqsd
<?php
// api/proposals.php v3.3
@set_time_limit(300);
@ini_set('memory_limit', '512M');
ignore_user_abort(true);

header('Content-Type: application/json');

// Capturar cualquier error fatal y devolverlo como JSON (nunca dejar 500 vacío)
register_shutdown_function(function() {
    $e = error_get_last();
    if ($e && in_array($e['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        if (!headers_sent()) header('Content-Type: application/json');
        echo json_encode(['ok' => false, 'error' => 'Error interno: ' . $e['message']]);
    }
});

require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../db.php';
require_once __DIR__ . '/../auth.php';

// Leer input y action ANTES del check de auth (necesario para bypass público)
$input  = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $_GET['action'] ?? $input['action'] ?? '';

// Estas acciones usan token de cliente en lugar de sesión admin
$public_actions = [
    'public', 'vote', 'vote_comment', 'get_comments_public',
    'select_styles',       // solo texto, sin datos sensibles
    'generate',            // validado internamente via client_token si no hay sesión
    'client_regen_inc', 'client_gen_final', 'client_get_final',
    'log_visit',           // registra visita del cliente sin exponer datos
];

if (!in_array($action, $public_actions)) {
    if (!dm_logged_in()) {
        echo json_encode(['ok' => false, 'error' => 'No autenticado']);
        exit;
    }
    // Solo verificar CSRF en requests de escritura (POST), no en lecturas (GET)
    if ($_SERVER['REQUEST_METHOD'] === 'POST') dm_csrf_verify();
    // Liberar el lock de sesión para que otras requests no queden bloqueadas
    session_write_close();
}

// ── Helpers ───────────────────────────────────────────────────────────────────

function generate_token(): string {
    return bin2hex(random_bytes(16));
}

function _call_claude_raw(string $prompt, int $max_tokens, array $extra_messages = []): array {
    $api_key = defined('CLAUDE_API_KEY') ? CLAUDE_API_KEY : '';
    if (!$api_key) return ['text' => '', 'stop_reason' => 'no_key', 'usage' => []];

    $prompt = mb_convert_encoding($prompt, 'UTF-8', 'UTF-8');
    $prompt = iconv('UTF-8', 'UTF-8//IGNORE', $prompt);

    $messages = array_merge([['role' => 'user', 'content' => $prompt]], $extra_messages);
    $body = json_encode([
        'model'      => 'claude-sonnet-4-6',
        'max_tokens' => $max_tokens,
        'messages'   => $messages,
    ]);
    if (!$body) return ['text' => '', 'stop_reason' => 'json_error', 'usage' => []];

    $log_dir = __DIR__ . '/../logs/';
    if (!is_dir($log_dir)) mkdir($log_dir, 0755, true);

    $max_attempts = 3;
    $r = [];
    for ($attempt = 1; $attempt <= $max_attempts; $attempt++) {
        $ch = curl_init('https://api.anthropic.com/v1/messages');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_HTTPHEADER     => [
                'Content-Type: application/json',
                'x-api-key: ' . $api_key,
                'anthropic-version: 2023-06-01',
                'anthropic-beta: output-128k-2025-02-19',
            ],
            CURLOPT_POSTFIELDS     => $body,
            CURLOPT_TIMEOUT        => 300,
            CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        $response = curl_exec($ch);
        $errno    = curl_errno($ch);
        curl_close($ch);

        if ($errno || $response === false) return ['text' => '', 'stop_reason' => 'curl_error', 'usage' => []];

        $r = json_decode($response, true);

        if (!empty($r['error']['type']) && $r['error']['type'] === 'rate_limit_error') {
            file_put_contents($log_dir . 'proposal_error.log',
                date('Y-m-d H:i:s') . " | rate_limit intento:{$attempt} — esperando 65s\n", FILE_APPEND);
            if ($attempt < $max_attempts) { sleep(65); continue; }
            return ['text' => '', 'stop_reason' => 'rate_limit', 'usage' => []];
        }
        break;
    }

    if (!empty($r['error'])) {
        file_put_contents($log_dir . 'proposal_error.log', date('Y-m-d H:i:s') . ' ' . json_encode($r['error']) . "\n", FILE_APPEND);
        if (strpos($r['error']['message'] ?? '', 'credit balance') !== false)
            return ['text' => '__CREDIT_ERROR__', 'stop_reason' => 'credits', 'usage' => []];
        return ['text' => '', 'stop_reason' => 'api_error', 'usage' => []];
    }

    return [
        'text'        => $r['content'][0]['text'] ?? '',
        'stop_reason' => $r['stop_reason'] ?? 'end_turn',
        'usage'       => $r['usage'] ?? [],
    ];
}

function call_claude(string $prompt, int $max_tokens = 32000): string {
    $log_dir = __DIR__ . '/../logs/';
    if (!is_dir($log_dir)) mkdir($log_dir, 0755, true);

    $result = _call_claude_raw($prompt, $max_tokens);
    $text   = $result['text'];

    // Log tokens
    if (!empty($result['usage'])) {
        $in  = $result['usage']['input_tokens']  ?? 0;
        $out = $result['usage']['output_tokens'] ?? 0;
        $ci  = round($in  * 3  / 1000000, 5);
        $co  = round($out * 15 / 1000000, 5);
        file_put_contents($log_dir . 'token_usage.log',
            date('Y-m-d H:i:s') . " | in:{$in} out:{$out} | \${$ci}+\${$co}=\$" . round($ci+$co,5) . "\n", FILE_APPEND);
    }

    return $text;
}

// Genera HTML completo con continuación automática si Claude se trunca
function call_claude_html(string $prompt): string {
    $log_dir = __DIR__ . '/../logs/';
    if (!is_dir($log_dir)) mkdir($log_dir, 0755, true);

    $result = _call_claude_raw($prompt, 32000);
    $text   = $result['text'];
    $total_in  = $result['usage']['input_tokens']  ?? 0;
    $total_out = $result['usage']['output_tokens'] ?? 0;

    // Si se truncó, hacer llamada de continuación
    if ($result['stop_reason'] === 'max_tokens' && !empty($text)) {
        file_put_contents($log_dir . 'proposal_error.log',
            date('Y-m-d H:i:s') . " | TRUNCADO en {$total_out} tokens — solicitando continuación\n", FILE_APPEND);

        $cont = _call_claude_raw($prompt, 32000, [
            ['role' => 'assistant', 'content' => $text],
            ['role' => 'user',      'content' => 'Continúa exactamente desde donde te cortaste. NO repitas nada, continúa el HTML desde el punto exacto de corte hasta </html>.'],
        ]);
        if (!empty($cont['text'])) {
            $text .= $cont['text'];
            $total_in  += $cont['usage']['input_tokens']  ?? 0;
            $total_out += $cont['usage']['output_tokens'] ?? 0;
        }
    }

    // Log tokens totales (incluyendo continuación si hubo)
    $ci = round($total_in  * 3  / 1000000, 5);
    $co = round($total_out * 15 / 1000000, 5);
    file_put_contents($log_dir . 'token_usage.log',
        date('Y-m-d H:i:s') . " | in:{$total_in} out:{$total_out} | \${$ci}+\${$co}=\$" . round($ci+$co,5) . "\n", FILE_APPEND);

    return $text;
}

// Email HTML de notificaciones al equipo DIMARK
function fetch_url_content(string $url): string {
    if (!filter_var($url, FILTER_VALIDATE_URL)) return '';
    $host = parse_url($url, PHP_URL_HOST);
    if (!$host) return '';
    $ip = gethostbyname($host);
    if (preg_match('/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|169\.254\.|0\.|::1$|localhost)/i', $ip)) return '';
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 5,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (compatible; SuperCerebroBot/1.0)',
        CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_HTTPHEADER     => ['Accept: text/html'],
    ]);
    $html = curl_exec($ch);
    $err  = curl_errno($ch);
    curl_close($ch);
    if ($err || !$html) return '';
    $html = preg_replace('/<(script|style|nav|footer|header|svg|noscript)[^>]*>.*?<\/\1>/si', ' ', $html);
    $text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $text = preg_replace('/\s+/', ' ', $text);
    $text = trim($text);
    if (mb_strlen($text) > 5000) $text = mb_substr($text, 0, 5000) . '…';
    return $text;
}

function dm_notify_email(string $subject, string $html_body): bool {
    $to      = defined('DIMARK_EMAIL') ? DIMARK_EMAIL : 'info@dimark.co';
    $headers = implode("\r\n", [
        'MIME-Version: 1.0',
        'Content-Type: text/html; charset=UTF-8',
        'From: SuperCerebro <no-reply@dimark.co>',
        'X-Mailer: PHP/' . PHP_VERSION,
    ]);
    $enc_subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
    return @mail($to, $enc_subject, $html_body, $headers);
}

function extract_colors_from_image(string $filepath): array {
    $ext = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));
    if (!file_exists($filepath)) return [];

    // SVG: extraer colores de fill/stroke con regex
    if ($ext === 'svg') {
        $svg = file_get_contents($filepath);
        preg_match_all('/#([0-9A-Fa-f]{6})\b/', $svg, $m);
        // También buscar rgb()
        preg_match_all('/(?:fill|stroke|color)\s*[=:]\s*["\']?([^"\';\s>]+)/i', $svg, $m2);
        $found = array_unique($m[0] ?? []);
        $hex = [];
        foreach ($found as $c) {
            $h = ltrim($c, '#');
            $r = hexdec(substr($h, 0, 2));
            $g = hexdec(substr($h, 2, 2));
            $b = hexdec(substr($h, 4, 2));
            $isNeutral = (abs($r-$g)<25 && abs($g-$b)<25 && abs($r-$b)<25);
            if (!$isNeutral && !($r>235&&$g>235&&$b>235) && !($r<20&&$g<20&&$b<20)) {
                $hex[] = $c;
            }
        }
        return array_values(array_slice(array_unique($hex), 0, 4));
    }

    if (!extension_loaded('gd')) return [];
    $img = null;
    try {
        switch ($ext) {
            case 'jpg': case 'jpeg': $img = imagecreatefromjpeg($filepath); break;
            case 'png':  $img = imagecreatefrompng($filepath); break;
            case 'gif':  $img = imagecreatefromgif($filepath); break;
            case 'webp': $img = imagecreatefromwebp($filepath); break;
            default: return [];
        }
    } catch (Exception $e) { return []; }
    if (!$img) return [];

    // Para PNG con transparencia: componer sobre fondo blanco para evitar que
    // los píxeles transparentes se conviertan en negro y contaminen los colores
    $w = imagesx($img);
    $h = imagesy($img);
    $white = imagecreatetruecolor($w, $h);
    imagefill($white, 0, 0, imagecolorallocate($white, 255, 255, 255));
    imagealphablending($white, true);
    imagecopy($white, $img, 0, 0, 0, 0, $w, $h);
    imagedestroy($img);

    $small = imagecreatetruecolor(50, 50);
    imagecopyresampled($small, $white, 0, 0, 0, 0, 50, 50, $w, $h);
    imagedestroy($white);

    $colors = [];
    for ($x = 0; $x < 50; $x++) {
        for ($y = 0; $y < 50; $y++) {
            $rgb = imagecolorat($small, $x, $y);
            $r = ($rgb >> 16) & 0xFF;
            $g = ($rgb >> 8)  & 0xFF;
            $b = $rgb & 0xFF;
            // Ignorar blancos, negros y grises neutros
            if ($r > 235 && $g > 235 && $b > 235) continue;
            if ($r < 20  && $g < 20  && $b < 20)  continue;
            $isNeutral = (abs($r-$g) < 30 && abs($g-$b) < 30 && abs($r-$b) < 30);
            if ($isNeutral) continue;
            $key = (intval($r/32)*32).','. (intval($g/32)*32).','. (intval($b/32)*32);
            $colors[$key] = ($colors[$key] ?? 0) + 1;
        }
    }
    imagedestroy($small);
    arsort($colors);
    $hex = [];
    foreach (array_slice(array_keys($colors), 0, 4) as $key) {
        [$r, $g, $b] = explode(',', $key);
        $hex[] = sprintf('#%02x%02x%02x', (int)$r, (int)$g, (int)$b);
    }
    return $hex;
}

// Wrapper seguro para ejecutar comandos shell — exec/shell_exec pueden estar deshabilitados
function safe_shell_exec(string $cmd): void {
    if (function_exists('exec'))       { @exec($cmd); return; }
    if (function_exists('shell_exec')) { @shell_exec($cmd); return; }
    if (function_exists('system'))     { ob_start(); @system($cmd); ob_end_clean(); return; }
    if (function_exists('passthru'))   { ob_start(); @passthru($cmd); ob_end_clean(); return; }
}

// Extractor de texto PDF puro PHP — fallback cuando pdftotext no está disponible
function extract_pdf_text_php(string $filepath): string {
    if (!file_exists($filepath)) return '';
    $raw = file_get_contents($filepath);
    if (!$raw) return '';

    $text = '';

    // Descomprimir streams FlateDecode si zlib está disponible
    if (function_exists('gzuncompress')) {
        preg_match_all('/stream\r?\n(.*?)\r?\nendstream/s', $raw, $streams);
        foreach (($streams[1] ?? []) as $stream) {
            $dec = @gzuncompress($stream);
            if ($dec !== false) $raw .= ' ' . $dec;
        }
    }

    // Patrón 1: (texto) Tj — texto simple
    preg_match_all('/\(([^)\\\\]{1,300})\)\s*Tj/s', $raw, $m1);
    // Patrón 2: (texto) dentro de array TJ
    preg_match_all('/\(([^)\\\\]{1,300})\)/s', $raw, $m2);

    $parts = array_merge($m1[1] ?? [], $m2[1] ?? []);
    foreach ($parts as $p) {
        // Decodificar escapes PDF básicos
        $p = str_replace(['\\n','\\r','\\t','\\(','\\)','\\\\'], ["\n","\r","\t",'(',')',"\\"], $p);
        // Filtrar solo texto legible (ASCII imprimible)
        $clean = preg_replace('/[^\x20-\x7E\xA0-\xFF\n]/', ' ', $p);
        $clean = trim($clean);
        if (strlen($clean) > 2) $text .= $clean . ' ';
    }

    // Limpiar espacios múltiples
    $text = preg_replace('/\s{3,}/', ' ', $text);
    return trim($text);
}

function read_docx(string $filepath): string {
    if (!file_exists($filepath)) return '';
    $zip = new ZipArchive();
    if ($zip->open($filepath) !== true) return '';
    $xml = $zip->getFromName('word/document.xml');
    $zip->close();
    if (!$xml) return '';
    $xml = str_replace('</w:r>', ' ', $xml);
    $xml = str_replace('</w:p>', "\n", $xml);
    $xml = strip_tags($xml);
    return trim(preg_replace('/[ \t]+/', ' ', $xml));
}

function upload_file(string $field, string $dir_id): string {
    if (empty($_FILES[$field]['tmp_name'])) return '';
    $ext = strtolower(pathinfo($_FILES[$field]['name'], PATHINFO_EXTENSION));
    $ext_mime_map = [
        'jpg'  => ['image/jpeg', 'image/pjpeg'],
        'jpeg' => ['image/jpeg', 'image/pjpeg'],
        'png'  => ['image/png', 'image/x-png'],
        'gif'  => ['image/gif'],
        'webp' => ['image/webp'],
        'pdf'  => ['application/pdf', 'application/x-pdf', 'application/octet-stream'],
        'svg'  => ['image/svg+xml', 'text/html', 'text/plain', 'text/xml', 'application/xml', 'application/octet-stream'],
        'docx' => ['application/zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/octet-stream'],
        'txt'  => ['text/plain', 'text/x-plain'],
    ];
    if (!isset($ext_mime_map[$ext])) return '';
    $mime = mime_content_type($_FILES[$field]['tmp_name']);
    if (!in_array($mime, $ext_mime_map[$ext])) return '';
    $dir = __DIR__ . '/../uploads/proposals/' . $dir_id . '/';
    if (!is_dir($dir)) mkdir($dir, 0755, true);
    $name = uniqid('file_') . '.' . $ext;
    if (move_uploaded_file($_FILES[$field]['tmp_name'], $dir . $name)) {
        return 'uploads/proposals/' . $dir_id . '/' . $name;
    }
    return '';
}

function get_images_for_industry(string $text): array {
    $t = strtolower($text);
    $map = [
        'aviacion|helicoptero|vuelo|aereo|charter|avion|air|aviation|piloto' => [
            'https://images.unsplash.com/photo-1436491865332-7a61a109cc05?w=1600&q=85',
            'https://images.unsplash.com/photo-1569767013863-aa6ef73a6ef5?w=1400&q=85',
            'https://images.unsplash.com/photo-1474302770737-173ee21bab63?w=1400&q=85',
            'https://images.unsplash.com/photo-1464037866556-6812c9d1c72e?w=1400&q=85',
            'https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=1400&q=85',
            'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=1400&q=85',
            'https://images.unsplash.com/photo-1531366936337-7c912a4589a7?w=1400&q=85',
            'https://images.unsplash.com/photo-1501854140801-50d01698950b?w=1400&q=85',
        ],
        'dental|odontolog|dent|clinica dental|ortodon' => [
            'https://images.unsplash.com/photo-1606811841689-23dfddce3e0f?w=1400&q=85',
            'https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=1400&q=85',
            'https://images.unsplash.com/photo-1612349317150-e413f6a5b16d?w=1400&q=85',
            'https://images.unsplash.com/photo-1588776814546-ec7e6b7d94af?w=1400&q=85',
            'https://images.unsplash.com/photo-1579684385127-1ef15d508118?w=1400&q=85',
            'https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?w=1400&q=85',
            'https://images.unsplash.com/photo-1609840114035-3c981b782dfe?w=1400&q=85',
            'https://images.unsplash.com/photo-1571772996211-2f02c9727629?w=1400&q=85',
        ],
        'medic|salud|hospital|clinica|doctor|cirugi|estetica medic|cirugia plastica|dermatolog' => [
            'https://images.unsplash.com/photo-1584820927498-cfe5211fd8bf?w=1400&q=85',
            'https://images.unsplash.com/photo-1631815588090-d4bfec5b1ccb?w=1400&q=85',
            'https://images.unsplash.com/photo-1666214280391-8ff5bd3d0bf2?w=1400&q=85',
            'https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=1400&q=85',
            'https://images.unsplash.com/photo-1576671081837-49000212a370?w=1400&q=85',
            'https://images.unsplash.com/photo-1579684453423-f84349ef60b0?w=1400&q=85',
            'https://images.unsplash.com/photo-1590012314607-cda9d9b699ae?w=1400&q=85',
            'https://images.unsplash.com/photo-1551076805-e1869033e561?w=1400&q=85',
        ],
        'restaurante|food|comida|chef|gastronomia|cafe|bar|cocina|bebida|pizza|sushi|panaderia|pasteleria' => [
            'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1600&q=85',
            'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=1400&q=85',
            'https://images.unsplash.com/photo-1504674900247-0877df9cc836?w=1400&q=85',
            'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=1400&q=85',
            'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?w=1400&q=85',
            'https://images.unsplash.com/photo-1482049016688-2d3e1b311543?w=1400&q=85',
            'https://images.unsplash.com/photo-1567521464027-f127ff144326?w=1400&q=85',
            'https://images.unsplash.com/photo-1551183053-bf91798d1e6d?w=1400&q=85',
        ],
        'hotel|resort|hosped|alojam|suite|turismo|viaje|travel|vacation|glamping' => [
            'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1600&q=85',
            'https://images.unsplash.com/photo-1582719508461-905c673771fd?w=1400&q=85',
            'https://images.unsplash.com/photo-1542314831-068cd1dbfeeb?w=1400&q=85',
            'https://images.unsplash.com/photo-1571003123894-1f0594d2b5d9?w=1400&q=85',
            'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1400&q=85',
            'https://images.unsplash.com/photo-1445019980597-93fa8acb246c?w=1400&q=85',
            'https://images.unsplash.com/photo-1615460549969-36fa19521a4f?w=1400&q=85',
            'https://images.unsplash.com/photo-1602002418816-5c0aeef426aa?w=1400&q=85',
        ],
        'inmob|real.estate|propiedad|apartamento|casa|vivienda|construcc|arquitect|proyecto inmob' => [
            'https://images.unsplash.com/photo-1560518883-ce09059eeffa?w=1600&q=85',
            'https://images.unsplash.com/photo-1613977257363-707ba9348227?w=1400&q=85',
            'https://images.unsplash.com/photo-1580587771525-78b9dba3b914?w=1400&q=85',
            'https://images.unsplash.com/photo-1570129477492-45c003edd2be?w=1400&q=85',
            'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1400&q=85',
            'https://images.unsplash.com/photo-1523217582562-09d0def993a6?w=1400&q=85',
            'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=1400&q=85',
            'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=1400&q=85',
        ],
        'fitness|gym|deport|sport|ejerc|crossfit|pilates|yoga|entren' => [
            'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=1600&q=85',
            'https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=1400&q=85',
            'https://images.unsplash.com/photo-1583454110551-21f2fa2afe61?w=1400&q=85',
            'https://images.unsplash.com/photo-1549060279-7e168fcee0c2?w=1400&q=85',
            'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=1400&q=85',
            'https://images.unsplash.com/photo-1526506118085-60ce8714f8c5?w=1400&q=85',
            'https://images.unsplash.com/photo-1518611012118-696072aa579a?w=1400&q=85',
            'https://images.unsplash.com/photo-1574680096145-d05b474e2155?w=1400&q=85',
        ],
        'moda|ropa|fashion|boutique|clothing|vestido|marca ropa|coleccion|tienda ropa' => [
            'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1600&q=85',
            'https://images.unsplash.com/photo-1441984904996-e0b6ba687e04?w=1400&q=85',
            'https://images.unsplash.com/photo-1469334031218-e382a71b716b?w=1400&q=85',
            'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1400&q=85',
            'https://images.unsplash.com/photo-1445205170230-053b83016050?w=1400&q=85',
            'https://images.unsplash.com/photo-1483985988355-763728e1935b?w=1400&q=85',
            'https://images.unsplash.com/photo-1509631179647-0177331693ae?w=1400&q=85',
            'https://images.unsplash.com/photo-1558769132-cb1aea458c5e?w=1400&q=85',
        ],
        'belleza|spa|estetica|peluqueria|salon|cosmet|manicure|micropigment|cabello' => [
            'https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?w=1600&q=85',
            'https://images.unsplash.com/photo-1560066984-138dadb4c035?w=1400&q=85',
            'https://images.unsplash.com/photo-1487412947147-5cebf100ffc2?w=1400&q=85',
            'https://images.unsplash.com/photo-1516975080664-ed2fc6a32937?w=1400&q=85',
            'https://images.unsplash.com/photo-1570172619644-dfd03ed5d881?w=1400&q=85',
            'https://images.unsplash.com/photo-1604654894610-df63bc536371?w=1400&q=85',
            'https://images.unsplash.com/photo-1595476108010-b4d1f102b1b1?w=1400&q=85',
            'https://images.unsplash.com/photo-1562322140-8baeececf3df?w=1400&q=85',
        ],
        'abogad|legal|derecho|juridic|notari|firma legal|consultoria legal' => [
            'https://images.unsplash.com/photo-1589829545856-d10d557cf95f?w=1600&q=85',
            'https://images.unsplash.com/photo-1521791136064-7986c2920216?w=1400&q=85',
            'https://images.unsplash.com/photo-1450101499163-c8848c66ca85?w=1400&q=85',
            'https://images.unsplash.com/photo-1575505586569-646b2ca898fc?w=1400&q=85',
            'https://images.unsplash.com/photo-1507679799987-c73779587ccf?w=1400&q=85',
            'https://images.unsplash.com/photo-1568992687947-868a62a9f521?w=1400&q=85',
            'https://images.unsplash.com/photo-1541726260-e6b6a6a08b27?w=1400&q=85',
            'https://images.unsplash.com/photo-1488998527040-85054a85150e?w=1400&q=85',
        ],
        'finanz|contab|inversion|banco|seguro|credito|financier|ahorro|crypto|wealth' => [
            'https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?w=1600&q=85',
            'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=1400&q=85',
            'https://images.unsplash.com/photo-1554224155-6726b3ff858f?w=1400&q=85',
            'https://images.unsplash.com/photo-1535320903710-d993d3d77d29?w=1400&q=85',
            'https://images.unsplash.com/photo-1560472355-536de3962603?w=1400&q=85',
            'https://images.unsplash.com/photo-1559526324-4b87b5e36e44?w=1400&q=85',
            'https://images.unsplash.com/photo-1543286386-2e659306cd6c?w=1400&q=85',
            'https://images.unsplash.com/photo-1553729459-efe14ef6055d?w=1400&q=85',
        ],
        'tecnolog|software|app|startup|digital|it |desarrollo web|programac|saas|inteligencia artificial' => [
            'https://images.unsplash.com/photo-1518770660439-4636190af475?w=1600&q=85',
            'https://images.unsplash.com/photo-1504639725590-34d0984388bd?w=1400&q=85',
            'https://images.unsplash.com/photo-1531297484001-80022131f5a1?w=1400&q=85',
            'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=1400&q=85',
            'https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=1400&q=85',
            'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=1400&q=85',
            'https://images.unsplash.com/photo-1487058792275-0ad4aaf24ca7?w=1400&q=85',
            'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1400&q=85',
        ],
        'educac|colegio|universidad|academia|escuela|instituto|curso|capacitac|entrenamiento|coaching' => [
            'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?w=1600&q=85',
            'https://images.unsplash.com/photo-1509062522246-3755977927d7?w=1400&q=85',
            'https://images.unsplash.com/photo-1580582932707-520aed937b7b?w=1400&q=85',
            'https://images.unsplash.com/photo-1546410531-bb4caa6b424d?w=1400&q=85',
            'https://images.unsplash.com/photo-1488190211105-8b0e65b80b4e?w=1400&q=85',
            'https://images.unsplash.com/photo-1427504494785-3a9ca7044f45?w=1400&q=85',
            'https://images.unsplash.com/photo-1503676260728-1c00da094a0b?w=1400&q=85',
            'https://images.unsplash.com/photo-1513258496099-48168024aec0?w=1400&q=85',
        ],
        'logistic|transport|cargo|mudanza|envio|mensajeria|flota|camion|shipping' => [
            'https://images.unsplash.com/photo-1601584115197-04ecc0da31d7?w=1600&q=85',
            'https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=1400&q=85',
            'https://images.unsplash.com/photo-1494412574643-ff11b0a5c1c3?w=1400&q=85',
            'https://images.unsplash.com/photo-1553413077-190dd305871c?w=1400&q=85',
            'https://images.unsplash.com/photo-1565793298595-6a879b1d9492?w=1400&q=85',
            'https://images.unsplash.com/photo-1568605117036-5fe5e7bab0b7?w=1400&q=85',
            'https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?w=1400&q=85',
            'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=1400&q=85',
        ],
        'event|boda|matrimoni|fiesta|catering|salon de event|gala|organizac event|wedding' => [
            'https://images.unsplash.com/photo-1519741497674-611481863552?w=1600&q=85',
            'https://images.unsplash.com/photo-1464366400600-7168b8af9bc3?w=1400&q=85',
            'https://images.unsplash.com/photo-1467810563316-b5476525c0f9?w=1400&q=85',
            'https://images.unsplash.com/photo-1511795409834-ef04bbd61622?w=1400&q=85',
            'https://images.unsplash.com/photo-1529543544282-ea669407fca3?w=1400&q=85',
            'https://images.unsplash.com/photo-1478146059778-26028b07395a?w=1400&q=85',
            'https://images.unsplash.com/photo-1537633552985-df8429e8048b?w=1400&q=85',
            'https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?w=1400&q=85',
        ],
        'joyeria|joya|bijou|lujo|luxury|reloj|diamante|plateria|oro' => [
            'https://images.unsplash.com/photo-1515562141207-7a88fb7ce338?w=1600&q=85',
            'https://images.unsplash.com/photo-1573408301185-9519f94a83c6?w=1400&q=85',
            'https://images.unsplash.com/photo-1611591437281-460bfbe1220a?w=1400&q=85',
            'https://images.unsplash.com/photo-1602173574767-37ac01994b2a?w=1400&q=85',
            'https://images.unsplash.com/photo-1605100804763-247f67b3557e?w=1400&q=85',
            'https://images.unsplash.com/photo-1535632066927-ab7c9ab60908?w=1400&q=85',
            'https://images.unsplash.com/photo-1588444650733-d0f3f4c4fe36?w=1400&q=85',
            'https://images.unsplash.com/photo-1618160702438-9b02c6f49073?w=1400&q=85',
        ],
        'fotograf|foto|studio|camara|imagen|retrato|sesion foto|video|produccion audiovisual' => [
            'https://images.unsplash.com/photo-1452780212940-6f5c0d14d848?w=1600&q=85',
            'https://images.unsplash.com/photo-1542038784456-1ea8e935640e?w=1400&q=85',
            'https://images.unsplash.com/photo-1554048612-b6a482bc67e5?w=1400&q=85',
            'https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=1400&q=85',
            'https://images.unsplash.com/photo-1471341971476-ae15ff5dd4ea?w=1400&q=85',
            'https://images.unsplash.com/photo-1495745966610-2a67f2297e5e?w=1400&q=85',
            'https://images.unsplash.com/photo-1518155317743-a8ff43ea6a5f?w=1400&q=85',
            'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1400&q=85',
        ],
        'agro|agricult|finca|campo|cultivo|ganaderia|organico|alimento|farm' => [
            'https://images.unsplash.com/photo-1500937386664-56d1dfef3854?w=1600&q=85',
            'https://images.unsplash.com/photo-1464226184884-fa280b87c399?w=1400&q=85',
            'https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=1400&q=85',
            'https://images.unsplash.com/photo-1560493676-04071c5f467b?w=1400&q=85',
            'https://images.unsplash.com/photo-1574323347407-f5e1ad6d020b?w=1400&q=85',
            'https://images.unsplash.com/photo-1529413951213-fdf2a7dae99a?w=1400&q=85',
            'https://images.unsplash.com/photo-1501004318641-b39e6451bec6?w=1400&q=85',
            'https://images.unsplash.com/photo-1444858291040-58f756a3bdd6?w=1400&q=85',
        ],
        'mascota|veterinar|animal|pet|perro|gato|dog|cat|tienda mascotas' => [
            'https://images.unsplash.com/photo-1587300003388-59208cc962cb?w=1600&q=85',
            'https://images.unsplash.com/photo-1548767797-d8c844163c4c?w=1400&q=85',
            'https://images.unsplash.com/photo-1583512603805-3cc6b41f3edb?w=1400&q=85',
            'https://images.unsplash.com/photo-1477884213360-7e9d7dcc1e48?w=1400&q=85',
            'https://images.unsplash.com/photo-1601758124510-52d02ddb7cbd?w=1400&q=85',
            'https://images.unsplash.com/photo-1510771463146-e89e6e86560e?w=1400&q=85',
            'https://images.unsplash.com/photo-1612160609504-334bef6bbf5b?w=1400&q=85',
            'https://images.unsplash.com/photo-1548199973-03cce0bbc87b?w=1400&q=85',
        ],
        'marketing|publicidad|agencia|branding|diseno|creativ|comunicac|social media' => [
            'https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1600&q=85',
            'https://images.unsplash.com/photo-1533750349088-cd871a92f312?w=1400&q=85',
            'https://images.unsplash.com/photo-1551434678-e076c223a692?w=1400&q=85',
            'https://images.unsplash.com/photo-1542744094-3a31f272c490?w=1400&q=85',
            'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1400&q=85',
            'https://images.unsplash.com/photo-1504868584819-f8e8b4b6d7e3?w=1400&q=85',
            'https://images.unsplash.com/photo-1562577309-4932fdd64cd1?w=1400&q=85',
            'https://images.unsplash.com/photo-1432888622747-4eb9a8efeb07?w=1400&q=85',
        ],
        'construccion|contratista|obra|renovac|reforma|acabado|materiales|ingenier' => [
            'https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=1600&q=85',
            'https://images.unsplash.com/photo-1589939705384-5185137a7f0f?w=1400&q=85',
            'https://images.unsplash.com/photo-1621905252507-b35492cc74b4?w=1400&q=85',
            'https://images.unsplash.com/photo-1560179707-f14e90ef3623?w=1400&q=85',
            'https://images.unsplash.com/photo-1581578731548-c64695cc6952?w=1400&q=85',
            'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=1400&q=85',
            'https://images.unsplash.com/photo-1429497419816-9ca5cfb4571a?w=1400&q=85',
            'https://images.unsplash.com/photo-1558618047-3c8c76ca7d13?w=1400&q=85',
        ],
    ];

    foreach ($map as $pattern => $urls) {
        if (preg_match('/(' . $pattern . ')/i', $t)) {
            return $urls;
        }
    }

    // Generic business/professional fallback
    return [
        'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1600&q=85',
        'https://images.unsplash.com/photo-1497366754035-f200581399c4?w=1400&q=85',
        'https://images.unsplash.com/photo-1552664730-d307ca884978?w=1400&q=85',
        'https://images.unsplash.com/photo-1559136555-9303baea8ebd?w=1400&q=85',
        'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=1400&q=85',
        'https://images.unsplash.com/photo-1600880292203-757bb62b4baf?w=1400&q=85',
        'https://images.unsplash.com/photo-1542744173-8e7e53415bb0?w=1400&q=85',
        'https://images.unsplash.com/photo-1521737711867-e3b97375f902?w=1400&q=85',
    ];
}

// ── Switch ────────────────────────────────────────────────────────────────────
switch ($action) {

    case 'list':
        $client_id = (int)($_GET['client_id'] ?? 0);
        if (!$client_id) { echo json_encode(['ok' => false, 'error' => 'client_id requerido']); exit; }
        dm_require_client_access($client_id);
        $stmt = $pdo->prepare("
            SELECT p.id, p.project_name, p.status, p.client_vote, p.token, p.created_at,
                (SELECT MAX(a.created_at) FROM dm_activity a
                 WHERE a.ref_id = p.id AND a.action = 'proposal_viewed_by_client') AS last_viewed_at,
                (SELECT MAX(a.created_at) FROM dm_activity a
                 WHERE a.ref_id = p.id AND a.action = 'proposal_final_generated') AS last_final_at
            FROM dm_proposals p
            WHERE p.client_id = ?
            ORDER BY p.created_at DESC
        ");
        $stmt->execute([$client_id]);
        echo json_encode(['ok' => true, 'proposals' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
        break;

    case 'get':
        $id = (int)($_GET['id'] ?? 0);
        $stmt = $pdo->prepare("SELECT * FROM dm_proposals WHERE id=?");
        $stmt->execute([$id]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }
        dm_require_client_access((int)$p['client_id']);
        echo json_encode(['ok' => true, 'proposal' => $p]);
        break;

    case 'save':
        $client_id    = (int)($input['client_id'] ?? 0);
        $project_name = trim($input['project_name'] ?? '');
        if (!$client_id || !$project_name) { echo json_encode(['ok' => false, 'error' => 'Faltan campos']); exit; }
        dm_require_client_access($client_id);
        $id = (int)($input['id'] ?? 0);
        $data = [
            'client_id'       => $client_id,
            'project_name'    => $project_name,
            'brief'           => $input['brief'] ?? '',
            'structure'       => $input['structure'] ?? '',
            'references_urls' => $input['references_urls'] ?? '',
            'brand_colors'    => $input['brand_colors'] ?? '',
            'brand_fonts'     => $input['brand_fonts'] ?? '',
            'extra_notes'     => $input['extra_notes'] ?? '',
            'replicate_url'   => $input['replicate_url'] ?? '',
            'created_by'      => dm_user()['id'],
        ];
        // Solo actualizar logo/manual si vienen explícitamente en el request
        if (isset($input['logo_path'])         && $input['logo_path'] !== '')         $data['logo_path']         = $input['logo_path'];
        if (isset($input['brand_manual_path']) && $input['brand_manual_path'] !== '') $data['brand_manual_path'] = $input['brand_manual_path'];
        if ($id) {
            $sets = implode(',', array_map(fn($k) => "$k=:$k", array_keys($data)));
            $data['id'] = $id;
            $pdo->prepare("UPDATE dm_proposals SET $sets WHERE id=:id")->execute($data);
        } else {
            // Atomic check+increment — un solo UPDATE que solo afecta si hay cupo
            $uid_p = (int)dm_user()['id'];
            $upd = $pdo->prepare("UPDATE dm_subscriptions SET proposals_used = proposals_used + 1 WHERE user_id = ? AND proposals_used < proposals_limit");
            $upd->execute([$uid_p]);
            if ($upd->rowCount() === 0) {
                $sub_p = $pdo->prepare("SELECT proposals_used, proposals_limit FROM dm_subscriptions WHERE user_id=?");
                $sub_p->execute([$uid_p]);
                $sp = $sub_p->fetch();
                http_response_code(402);
                echo json_encode(['ok'=>false,'error'=>"Alcanzaste tu límite de ".($sp['proposals_limit']??0)." propuestas este mes. Puedes comprar propuestas adicionales por \$3 c/u.",'limit_reached'=>true,'used'=>(int)($sp['proposals_used']??0),'limit'=>(int)($sp['proposals_limit']??0)]);
                exit;
            }
            $data['token'] = generate_token();
            $cols = implode(',', array_keys($data));
            $vals = ':' . implode(',:', array_keys($data));
            $pdo->prepare("INSERT INTO dm_proposals ($cols) VALUES ($vals)")->execute($data);
            $id = (int)$pdo->lastInsertId();
        }
        echo json_encode(['ok' => true, 'id' => $id]);
        break;

    case 'upload':
        $client_id = (int)($_POST['client_id'] ?? 0);
        $prop_id   = (int)($_POST['proposal_id'] ?? 0);
        $type      = $_POST['type'] ?? 'logo';
        $dir_id    = $client_id ?: 'tmp';
        $field     = ($type === 'brand_manual') ? 'brand_manual' : 'logo';
        $path      = upload_file($field, $dir_id);
        if (!$path) { echo json_encode(['ok' => false, 'error' => 'Error al subir']); exit; }
        $col = ($type === 'brand_manual') ? 'brand_manual_path' : 'logo_path';
        if ($prop_id) {
            $pdo->prepare("UPDATE dm_proposals SET $col=? WHERE id=?")->execute([$path, $prop_id]);
        }
        $colors = [];
        if ($type === 'logo') {
            $colors = extract_colors_from_image(__DIR__ . '/../' . $path);
            if (!empty($colors) && $prop_id) {
                $pdo->prepare("UPDATE dm_proposals SET brand_colors=? WHERE id=?")->execute([implode(', ', $colors), $prop_id]);
            }
        }
        echo json_encode(['ok' => true, 'path' => $path, 'url' => '/' . $path, 'colors' => $colors]);
        break;

    case 'upload_brief':
        $client_id = (int)($_POST['client_id'] ?? 0);
        if (!$client_id) { echo json_encode(['ok' => false, 'error' => 'client_id requerido']); exit; }
        if (empty($_FILES['brief_file']['tmp_name'])) { echo json_encode(['ok' => false, 'error' => 'No se recibio archivo']); exit; }
        $ext = strtolower(pathinfo($_FILES['brief_file']['name'], PATHINFO_EXTENSION));
        if (!in_array($ext, ['docx', 'txt', 'pdf'])) { echo json_encode(['ok' => false, 'error' => 'Solo docx, txt o pdf']); exit; }
        $dir = __DIR__ . '/../uploads/briefs/' . $client_id . '/';
        if (!is_dir($dir)) mkdir($dir, 0755, true);
        $filepath = $dir . uniqid() . '.' . $ext;
        if (!move_uploaded_file($_FILES['brief_file']['tmp_name'], $filepath)) {
            echo json_encode(['ok' => false, 'error' => 'Error al guardar']); exit;
        }
        $text = '';
        if ($ext === 'docx') {
            $text = read_docx($filepath);
        } elseif ($ext === 'txt') {
            $text = file_get_contents($filepath);
        } elseif ($ext === 'pdf') {
            // Intentar pdftotext si está disponible
            $tmp = sys_get_temp_dir() . '/' . uniqid() . '.txt';
            safe_shell_exec('pdftotext ' . escapeshellarg($filepath) . ' ' . escapeshellarg($tmp) . ' 2>/dev/null');
            if (file_exists($tmp) && filesize($tmp) > 10) {
                $text = trim(file_get_contents($tmp));
                @unlink($tmp);
            }
            // Fallback PHP puro si pdftotext no funcionó
            if (strlen($text) < 50) {
                $text = extract_pdf_text_php($filepath);
            }
        }
        $urls = [];
        preg_match_all('/https?:\/\/[^\s\)\]"\'<>]+/', $text, $um);
        $urls = array_unique($um[0] ?? []);
        echo json_encode(['ok' => true, 'text' => $text, 'filename' => basename($filepath), 'urls' => array_values($urls)]);
        break;

    case 'select_styles':
        // Recibe brief del cliente, devuelve 3 style_keys elegidos por Claude (1 dark, 1 light, 1 colorful)
        $proj   = trim($input['project_name'] ?? '');
        $brief  = trim($input['brief']        ?? '');
        $extra  = trim($input['extra_notes']  ?? '');

        // Definición compacta de estilos para el selector (reutiliza la misma lista de $all_styles)
        // NOTA: se necesita definir el catálogo estático aquí ya que $all_styles se define dentro del case 'generate'
        $style_catalog = [
            // DARK
            'moderno_bold'        => ['cat'=>'dark','name'=>'Moderno Bold','vibe'=>'Cinematográfico, oscuro, premium. Apple meets Awwwards. Tipografía enorme.','best_for'=>'tecnología, startups, agencias, marcas premium, lujo moderno'],
            'neon_oscuro'         => ['cat'=>'dark','name'=>'Neon Oscuro','vibe'=>'Cyberpunk suave, neón en negro. Impacto visual máximo en la noche.','best_for'=>'gaming, entretenimiento nocturno, música, clubs, tech edgy, apps oscuras'],
            'brutalismo_editorial'=> ['cat'=>'dark','name'=>'Brutalismo Editorial','vibe'=>'Anti-diseño que llama la atención. Bordes crudos, contraste puro, valentía.','best_for'=>'arte, cultura, moda alternativa, agencias creativas, portfolios edgy'],
            'tech_dark'           => ['cat'=>'dark','name'=>'Tech Dark / Dashboard','vibe'=>'Matrix meets Linear.app. Datos, precisión, futurismo técnico.','best_for'=>'SaaS B2B, fintech, ciberseguridad, análisis de datos, software empresarial'],
            'luxury_dark'         => ['cat'=>'dark','name'=>'Luxury Dark','vibe'=>'Louis Vuitton digital. Dorado sobre negro, materiales nobles, ultra-premium.','best_for'=>'joyería de lujo, relojes, fashion high-end, perfumes, autos de lujo'],
            'cinematografico'     => ['cat'=>'dark','name'=>'Cinematográfico','vibe'=>'Como trailer de película de Hollywood. Imágenes épicas, escala total.','best_for'=>'productoras, fotógrafos, directores, agencias creativas de video, festivales'],
            'glassmorphism_dark'  => ['cat'=>'dark','name'=>'Glassmorphism Dark','vibe'=>'Vidrio translúcido sobre oscuridad. iOS Dark Mode meets Awwwards.','best_for'=>'apps móviles, SaaS moderno, fintech, servicios de suscripción premium'],
            'retro_80s'           => ['cat'=>'dark','name'=>'Retro 80s / Synthwave','vibe'=>'Synthwave, outrun, neón sobre negro. Nostalgia retrofuturista.','best_for'=>'música retro/electrónica, gaming retro, marcas nostálgicas, eventos vintage'],
            'cosmos_astro'        => ['cat'=>'dark','name'=>'Cosmos / Astronomía','vibe'=>'NASA meets Dribbble. Espacio profundo, nebulosas, escala del universo.','best_for'=>'ciencia, educación STEM, exploración, turismo espacial, observatorios'],
            'dark_editorial'      => ['cat'=>'dark','name'=>'Dark Editorial','vibe'=>'Vogue en noir. Fotografía de moda en oscuridad absoluta. Mucho espacio.','best_for'=>'fotografía de moda, modelos, diseñadores, marcas de ropa premium'],
            // LIGHT
            'ultra_minimalista'   => ['cat'=>'light','name'=>'Ultra Minimalista','vibe'=>'Less is everything. Espacio blanco infinito, tipografía sola, pureza.','best_for'=>'arquitectura, diseño de producto, portfolios, marcas de autor, consultoría'],
            'corporativo_premium' => ['cat'=>'light','name'=>'Corporativo Premium','vibe'=>'McKinsey meets IBM. Autoridad, confianza, escala global. B2B serio pero no aburrido.','best_for'=>'consultoría, legal, finanzas, banca, seguros, B2B enterprise'],
            'editorial_magazine'  => ['cat'=>'light','name'=>'Editorial Magazine','vibe'=>'NYT meets Monocle. Periodismo visual de alto nivel. Grid perfecto.','best_for'=>'medios digitales, revistas, blogs premium, periodismo, contenido editorial'],
            'lujo_blanco'         => ['cat'=>'light','name'=>'Lujo Blanco','vibe'=>'Chanel, Hermès, Bottega Veneta. Blanco total, materiales nobles, lujo sin gritar.','best_for'=>'joyería, moda lujo, spas premium, bodas, cosméticos high-end, hoteles 5 estrellas'],
            'medico_clinico'      => ['cat'=>'light','name'=>'Médico / Clínico','vibe'=>'Confianza científica. Limpio, estructurado, profesional. Genera credibilidad instantánea.','best_for'=>'hospitales, clínicas, médicos, farmacias, laboratorios, salud digital'],
            'arquitectura'        => ['cat'=>'light','name'=>'Arquitectura','vibe'=>'Planos, proporción, espacio negativo. Como un portafolio de Zaha Hadid.','best_for'=>'arquitectos, ingenieros, constructoras, diseño de interiores, urbanismo'],
            'soft_pastel'         => ['cat'=>'light','name'=>'Soft Pastel','vibe'=>'Suave, femenino, lifestyle premium. Colores dulces con producción profesional.','best_for'=>'belleza, bienestar, yoga, maternidad, moda femenina, lifestyle brands'],
            'papel_artesanal'     => ['cat'=>'light','name'=>'Papel Artesanal','vibe'=>'Kraft, naturaleza, handmade premium. Calidez artesanal con diseño moderno.','best_for'=>'alimentos artesanales, café, cervecería artesanal, productos eco, bodas rústicas'],
            'elegante_europeo'    => ['cat'=>'light','name'=>'Elegante Europeo','vibe'=>'Lujo discreto, minimalismo sofisticado. Serif clásica, proporción áurea. Galería de arte de Milán.','best_for'=>'moda premium, arquitectura de interiores, consultoría high-end, hoteles boutique'],
            'swiss_design'        => ['cat'=>'light','name'=>'Swiss / Tipográfico','vibe'=>'International Typographic Style. Grid rígido, Helvetica energy, belleza matemática.','best_for'=>'institucional, museos, agencias de diseño, B2B tech europeo'],
            // COLORFUL
            'gradiente_vibrante'  => ['cat'=>'colorful','name'=>'Gradiente Vibrante','vibe'=>'Stripe meets Figma. Gradientes ricos, profundidad visual, SaaS moderno que convierte.','best_for'=>'SaaS, fintech, plataformas digitales, apps de productividad, marketplaces'],
            'retro_pop_70s'       => ['cat'=>'colorful','name'=>'Retro Pop 70s','vibe'=>'Groovy, warm, fun. Harvest gold, avocado green, burnt orange. Vintage con producción moderna.','best_for'=>'alimentos artesanales, cerveza craft, moda vintage, música, eventos retro'],
            'y2k_revival'         => ['cat'=>'colorful','name'=>'Y2K Revival','vibe'=>'Chrome, pixel art, translucent plastic. Nostalgia 2000 con producción actual. Trendy 2025.','best_for'=>'moda gen-z, música pop, beauty joven, eventos, clubs, tecnología cool'],
            'organico_natural'    => ['cat'=>'colorful','name'=>'Orgánico Natural','vibe'=>'Tierra, plantas, sostenibilidad. Colores naturales vibrantes pero suaves, formas orgánicas.','best_for'=>'productos orgánicos, agricultura, sostenibilidad, eco-brands, alimentación natural'],
            'tropical_caribe'     => ['cat'=>'colorful','name'=>'Tropical Caribe','vibe'=>'Playa, palmeras, turismo vibrante. Colores saturados del trópico, alegría y aventura.','best_for'=>'turismo, hoteles playa, tours, deportes acuáticos, restaurantes tropicales'],
            'flat_bold'           => ['cat'=>'colorful','name'=>'Flat Bold','vibe'=>'Material Design meets Mondrian. Colores primarios puros, geometría perfecta.','best_for'=>'educación infantil, apps de consumo, alimentación, servicios masivos, retail'],
            'startup_colorido'    => ['cat'=>'colorful','name'=>'Startup Colorido','vibe'=>'Energía Y Combinator. Bold, rápido, memorable. Colores de marca con actitud.','best_for'=>'startups early-stage, apps consumer, plataformas de comunidad, ed-tech'],
            'futurista'           => ['cat'=>'colorful','name'=>'Futurista','vibe'=>'Tesla meets SpaceX website. Tecnología del futuro, precisión y asombro.','best_for'=>'tecnología deep-tech, hardware, robótica, energía limpia, space tech'],
            'isometrico'          => ['cat'=>'colorful','name'=>'Isométrico','vibe'=>'Ilustraciones 3D isométricas + UI plana. Profundidad sin complejidad.','best_for'=>'fintech, logistics, supply chain, B2B SaaS, plataformas de datos'],
            'playful_creativo'    => ['cat'=>'colorful','name'=>'Playful Creativo','vibe'=>'Agencias creativas, energía joven y fresca. Fun pero profesional.','best_for'=>'agencias de diseño, estudios creativos, edtech infantil, apps jóvenes'],
        ];

        $catalog_lines = '';
        foreach ($style_catalog as $key => $s) {
            $catalog_lines .= "[{$s['cat']}] KEY:{$key} | {$s['name']} | {$s['vibe']} | IDEAL PARA: {$s['best_for']}\n";
        }

        $sel_prompt = <<<PROMPT
Eres un director creativo senior con 20 años de experiencia. Tu tarea es elegir los 3 mejores estilos de diseño web para este cliente.

REGLA OBLIGATORIA: debes elegir EXACTAMENTE 1 de cada categoría:
- 1 estilo [dark]
- 1 estilo [light]
- 1 estilo [colorful]

BRIEF DEL CLIENTE:
Nombre / Proyecto: {$proj}
Descripción: {$brief}
Notas adicionales: {$extra}

ESTILOS DISPONIBLES (30 opciones):
{$catalog_lines}

INSTRUCCIONES:
- Analiza el industria, tono, público objetivo y valores de marca del cliente
- Elige el estilo que mejor encaje en cada categoría basándote en el campo "IDEAL PARA"
- El razonamiento debe ser específico al cliente (no genérico)
- Responde ÚNICAMENTE con JSON válido, sin texto adicional, sin markdown

FORMATO DE RESPUESTA:
{"styles":[{"key":"...","name":"...","category":"dark","reasoning":"..."},{"key":"...","name":"...","category":"light","reasoning":"..."},{"key":"...","name":"...","category":"colorful","reasoning":"..."}],"overall":"Frase de 1-2 oraciones explicando por qué estos 3 estilos encajan perfectamente con el cliente"}
PROMPT;

        $sel_raw = call_claude($sel_prompt, 1000);
        if (!$sel_raw) { echo json_encode(['ok'=>false,'error'=>'Error seleccionando estilos']); exit; }

        // Extraer JSON aunque Claude incluya texto extra
        preg_match('/\{.*\}/s', $sel_raw, $json_m);
        $sel = json_decode($json_m[0] ?? $sel_raw, true);
        if (empty($sel['styles']) || count($sel['styles']) < 3) {
            // Fallback a estilos fijos si Claude falló el formato
            $sel = ['styles'=>[
                ['key'=>'moderno_bold',       'name'=>'Moderno Bold',       'category'=>'dark',     'reasoning'=>'Estilo premium por defecto'],
                ['key'=>'corporativo_premium', 'name'=>'Corporativo Premium','category'=>'light',    'reasoning'=>'Estilo profesional por defecto'],
                ['key'=>'startup_colorido',    'name'=>'Startup Colorido',   'category'=>'colorful', 'reasoning'=>'Estilo dinámico por defecto'],
            ],'overall'=>'Selección estándar de estilos'];
        }
        // Asegurar orden: dark=1, light=2, colorful=3
        $ordered = [null, null, null];
        foreach ($sel['styles'] as $st) {
            if ($st['category'] === 'dark')     $ordered[0] = $st;
            elseif ($st['category'] === 'light') $ordered[1] = $st;
            else                                  $ordered[2] = $st;
        }
        // Si alguno quedó null por error en categoría, usar fallback
        $ordered[0] = $ordered[0] ?? ['key'=>'moderno_bold',       'name'=>'Moderno Bold',       'category'=>'dark',     'reasoning'=>'Fallback'];
        $ordered[1] = $ordered[1] ?? ['key'=>'corporativo_premium', 'name'=>'Corporativo Premium','category'=>'light',    'reasoning'=>'Fallback'];
        $ordered[2] = $ordered[2] ?? ['key'=>'startup_colorido',    'name'=>'Startup Colorido',   'category'=>'colorful', 'reasoning'=>'Fallback'];

        echo json_encode(['ok'=>true, 'styles'=>$ordered, 'overall'=>$sel['overall'] ?? '']);
        break;

    case 'generate':
        // Soporte token de cliente para regen etapa 1 (sin sesión admin)
        if (!dm_logged_in()) {
            $ct = $input['client_token'] ?? '';
            if (!$ct) { echo json_encode(['ok'=>false,'error'=>'No autenticado']); exit; }
            $ct_row = $pdo->prepare("SELECT id FROM dm_proposals WHERE token=?");
            $ct_row->execute([$ct]);
            $ct_data = $ct_row->fetch(PDO::FETCH_ASSOC);
            if (!$ct_data) { echo json_encode(['ok'=>false,'error'=>'Token inválido']); exit; }
            $input['id'] = $ct_data['id']; // ID viene del token, no del input (seguridad)
        }
        $id = (int)($input['id'] ?? 0);
        if (!$id) { echo json_encode(['ok' => false, 'error' => 'id requerido']); exit; }
        $stmt = $pdo->prepare("SELECT * FROM dm_proposals WHERE id=?");
        $stmt->execute([$id]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }

        // ── Usar brand assets de dm_clients como fallback ─────────────────────
        $client_brand = [];
        if (!empty($p['client_id'])) {
            $cb = $pdo->prepare("SELECT brand_logo, brand_manual, brand_colors, brand_fonts, brand_description FROM dm_clients WHERE id=? LIMIT 1");
            $cb->execute([$p['client_id']]);
            $client_brand = $cb->fetch(PDO::FETCH_ASSOC) ?: [];
        }
        if (empty($p['logo_path'])         && !empty($client_brand['brand_logo']))   $p['logo_path']         = $client_brand['brand_logo'];
        if (empty($p['brand_manual_path']) && !empty($client_brand['brand_manual'])) $p['brand_manual_path'] = $client_brand['brand_manual'];
        if (empty($p['brand_colors'])      && !empty($client_brand['brand_colors'])) $p['brand_colors']      = $client_brand['brand_colors'];
        if (empty($p['brand_fonts'])       && !empty($client_brand['brand_fonts']))  $p['brand_fonts']       = $client_brand['brand_fonts'];
        $brand_description_ctx = trim($client_brand['brand_description'] ?? '');

        $style = (int)($input['style'] ?? 1);
        if (!in_array($style, [1, 2, 3])) { echo json_encode(['ok' => false, 'error' => 'Estilo inválido']); exit; }

        // ── Colores de marca ──────────────────────────────────────────────────
        $colors_raw = trim($p['brand_colors'] ?? '');
        $color_list = array_values(array_filter(array_map('trim', explode(',', $colors_raw))));
        $c1 = $color_list[0] ?? '#1a1a2e';
        $c2 = $color_list[1] ?? '#E8272A';
        $c3 = $color_list[2] ?? '#ffffff';
        $c4 = $color_list[3] ?? $c2;

        // ── Logo ──────────────────────────────────────────────────────────────
        $logo_url = '';
        if (!empty($p['logo_path'])) {
            $proto    = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
            $logo_url = $proto . '://' . ($_SERVER['HTTP_HOST'] ?? 'dimark.co') . '/supercerebro/' . $p['logo_path'];
        }
        $logo_html = $logo_url
            ? '<img src="' . $logo_url . '" alt="' . htmlspecialchars($p['project_name']) . '" style="height:48px;object-fit:contain;max-width:180px;display:block">'
            : '<span style="font-size:22px;font-weight:900;letter-spacing:-1px">' . htmlspecialchars($p['project_name']) . '</span>';

        // ── Imágenes de industria ─────────────────────────────────────────────
        $industry_text = $p['project_name'] . ' ' . $p['brief'] . ' ' . ($p['extra_notes'] ?? '');
        $img_list      = get_images_for_industry($industry_text);

        // ── Manual de marca: extraer texto Y parsear colores/tipografías ─────────
        $brand_manual_text   = '';
        $manual_colors_found = [];
        $manual_fonts_found  = [];

        if (!empty($p['brand_manual_path'])) {
            $manual_abs = __DIR__ . '/../' . $p['brand_manual_path'];
            if (file_exists($manual_abs)) {
                $manual_raw = '';
                // Intentar pdftotext si está disponible
                $tmp_manual = sys_get_temp_dir() . '/manual_' . $id . '_' . time() . '.txt';
                safe_shell_exec('pdftotext -layout ' . escapeshellarg($manual_abs) . ' ' . escapeshellarg($tmp_manual) . ' 2>/dev/null');
                if (file_exists($tmp_manual) && filesize($tmp_manual) > 20) {
                    $manual_raw = trim((string)file_get_contents($tmp_manual));
                    @unlink($tmp_manual);
                }
                // Fallback PHP puro si pdftotext no funcionó
                if (strlen($manual_raw) < 50) {
                    $manual_raw = extract_pdf_text_php($manual_abs);
                }
                if (strlen($manual_raw) > 20) {
                    $brand_manual_text = substr($manual_raw, 0, 3000);

                    // Extraer códigos HEX del PDF (colores de marca oficiales)
                    preg_match_all('/#([0-9A-Fa-f]{6})\b/', $manual_raw, $hex_m);
                    foreach (array_unique($hex_m[0] ?? []) as $hex) {
                        $h = ltrim($hex, '#');
                        $r = hexdec(substr($h, 0, 2));
                        $g = hexdec(substr($h, 2, 2));
                        $b = hexdec(substr($h, 4, 2));
                        // Ignorar blancos, negros y grises puros
                        $isNeutral = (abs($r - $g) < 25 && abs($g - $b) < 25 && abs($r - $b) < 25);
                        $isWhite   = ($r > 235 && $g > 235 && $b > 235);
                        $isBlack   = ($r < 25  && $g < 25  && $b < 25);
                        if (!$isNeutral && !$isWhite && !$isBlack) {
                            $manual_colors_found[] = $hex;
                        }
                    }
                    $manual_colors_found = array_values(array_unique($manual_colors_found));

                    // Extraer fuentes mencionadas en el manual
                    $known_google_fonts = [
                        'Montserrat','Roboto','Open Sans','Lato','Raleway','Oswald','Poppins','Nunito',
                        'Inter','Playfair Display','Merriweather','Source Sans','Mulish','Quicksand',
                        'DM Sans','Syne','Plus Jakarta Sans','Outfit','Josefin Sans','Barlow',
                        'Bebas Neue','Abril Fatface','Cormorant','EB Garamond','Libre Baskerville',
                    ];
                    foreach ($known_google_fonts as $gf) {
                        if (stripos($manual_raw, $gf) !== false) {
                            $manual_fonts_found[] = $gf;
                        }
                    }
                    $manual_fonts_found = array_values(array_unique($manual_fonts_found));
                }
            }
        }

        // Si el manual tiene colores, los toma como colores oficiales (mayor prioridad)
        if (!empty($manual_colors_found)) {
            $c1 = $manual_colors_found[0];
            if (isset($manual_colors_found[1])) $c2 = $manual_colors_found[1];
            if (isset($manual_colors_found[2])) $c3 = $manual_colors_found[2];
            if (isset($manual_colors_found[3])) $c4 = $manual_colors_found[3];
        }

        // ── Análisis de competencia — propuesta específica → cliente reciente ──
        $comp_analysis_text = '';
        try {
            $ca_found = null;
            // Prioridad 1: análisis generado específicamente para esta propuesta
            $ca = $pdo->prepare("SELECT analysis, competitors FROM dm_competitive_analysis WHERE proposal_id=? ORDER BY created_at DESC LIMIT 1");
            $ca->execute([$id]);
            $ca_row = $ca->fetch(PDO::FETCH_ASSOC);
            if ($ca_row && !empty($ca_row['analysis'])) {
                $ca_found = $ca_row;
            } elseif (!empty($p['client_id'])) {
                // Prioridad 2: análisis más reciente del mismo cliente (cualquier propuesta)
                $ca2 = $pdo->prepare("SELECT analysis, competitors FROM dm_competitive_analysis WHERE client_id=? ORDER BY created_at DESC LIMIT 1");
                $ca2->execute([$p['client_id']]);
                $ca2_row = $ca2->fetch(PDO::FETCH_ASSOC);
                if ($ca2_row && !empty($ca2_row['analysis'])) {
                    $ca_found = $ca2_row;
                }
            }
            if ($ca_found) {
                // Prefijar con lista de competidores analizados para dar contexto a Claude
                $comps_decoded = json_decode($ca_found['competitors'] ?? '[]', true);
                if (!empty($comps_decoded)) {
                    $comp_names = implode(', ', array_map(fn($c) => ($c['name'] ?? '') . ' (' . ($c['url'] ?? '') . ')', $comps_decoded));
                    $comp_analysis_text = "COMPETITORS ANALYZED: {$comp_names}\n\n" . substr($ca_found['analysis'], 0, 2400);
                } else {
                    $comp_analysis_text = substr($ca_found['analysis'], 0, 2500);
                }
            }
        } catch (Exception $e) {}

        // ── Extraer datos clave del extra_notes para el prompt ────────────────
        $extra = $p['extra_notes'] ?? '';
        $refs  = trim($p['references_urls'] ?? '');
        $fonts = trim($p['brand_fonts'] ?? '');
        $replicate_url = trim($p['replicate_url'] ?? '');
        $replicate_content = '';
        if ($replicate_url) {
            $fetched = fetch_url_content($replicate_url);
            if ($fetched) {
                $replicate_content = "Sitio a replicar: {$replicate_url}\n\nCONTENIDO EXTRAÍDO:\n{$fetched}";
            }
        }

        // ── 30 estilos de diseño ─────────────────────────────────────────────
        $all_styles = [
// ═══ DARK ════════════════════════════════════════════════════════════════════
'moderno_bold' => ['name'=>'Moderno Bold','category'=>'dark','vibe'=>'Cinematográfico, oscuro, premium. Apple meets Awwwards. Tipografía enorme que impacta.','best_for'=>'tecnología, agencias creativas, productos premium, startups','heading'=>'Syne','body'=>'DM Sans',
'css_base'=>"body{background:#0a0a0a;color:#f0f0f0} :root{--bg:#0a0a0a;--bg2:#111;--bg3:#1a1a1a;--border:rgba(255,255,255,.08)}",
'sections'=>'HEADER: fixed; padding:20px 40px; transparent→rgba(10,10,10,.95)+blur(20px) on scroll; logo left; nav right uppercase letter-spacing:2px; CTA button border var(--accent).
HERO: 100vh; IMG1 center/cover; overlay linear-gradient(135deg,rgba(0,0,0,.85),rgba(0,0,0,.4)); H1 clamp(56px,10vw,130px) weight:900 line-height:.9; badge pill credential above H1; 2 buttons primary+outline; value prop paragraph.
SERVICIOS: padding:120px 80px; big number "01" accent opacity:.15 behind title; grid 3 cols; card: bg:#111 img-top 200px padding:24px; hover translateY(-10px) shadow.
STATS: gradient primary→accent; 4 big numbers font-size:64px weight:900 with labels.
NOSOTROS: 2 cols; img clip-path:polygon(0 0,92% 0,100% 100%,0 100%) 600px; right: badge+title+4 differentiators with ✦ accent.
TESTIMONIOS: bg:#111; carousel 3 cards round-photo 60px stars ★★★★★ quote auto-rotate 5s.
GALERÍA: CSS columns:3; 6 images hover scale(1.03) accent overlay.
CONTACTO: bg:#0a0a0a; 2 cols left:contact-data right:form border-bottom inputs; submit bg:accent.
FOOTER: bg:#050505; 4 cols logo+desc+social services contact newsletter; gradient separator.'],

'neon_oscuro' => ['name'=>'Neón Oscuro','category'=>'dark','vibe'=>'Cyberpunk vibrante. Neón eléctrico sobre negro profundo. Gaming, tech edgy, nightlife.','best_for'=>'gaming, clubes, bares, entretenimiento nocturno, tech edgy, música','heading'=>'Syne','body'=>'Inter',
'css_base'=>"body{background:#050510;color:#e0e0ff} :root{--bg:#050510;--bg2:#0a0a20;--bg3:#0f0f2a;--border:rgba(180,100,255,.2)}",
'sections'=>'HEADER: bg:#050510 border-bottom:1px solid accent box-shadow:0 0 20px accent-30%; logo con text-shadow glow; nav links hover:glow.
HERO: 100vh bg:#050510; múltiples radial-gradient spots en primary y accent opacity:.15; H1 clamp(60px,12vw,140px) weight:900 text-shadow:0 0 80px accent gradient-text primary→accent; 2 botones: accent bg+shadow-glow y outline-accent; imagen con border neón.
SERVICIOS: bg:#0a0a20; cards bg:rgba(accent,.05) border:1px solid rgba(accent,.2); icono con glow; hover box-shadow:0 0 30px accent-30%.
STATS: fondo con diag gradient primary+accent opacity:.1; números con text-shadow glow; líneas neón.
NOSOTROS: 2 cols; imagen con box-shadow:0 0 0 2px accent, 0 0 40px accent-30%; accent lines.
TESTIMONIOS: glassmorphism oscuro bg:rgba(255,255,255,.03) border:1px rgba(accent,.15); estrellas accent.
GALERÍA: grid hover activa glow neón en cada imagen.
CONTACTO: inputs border-bottom:accent; botón con animation pulse-neon.
FOOTER: top line neón; social icons SVG con glow.'],

'brutalismo_editorial' => ['name'=>'Brutalismo Editorial','category'=>'dark','vibe'=>'Raw, bold, sin concesiones. Grid roto, tipografía enorme que sangra. Awwwards SOTD energy.','best_for'=>'agencias creativas, artistas, música, moda alternativa, diseño','heading'=>'Bebas Neue','body'=>'Space Grotesk',
'css_base'=>"body{background:#0d0d0d;color:#f5f5f5} :root{--bg:#0d0d0d;--bg2:#151515;--bg3:#1a1a1a;--border:#333}",
'sections'=>'HEADER: bg:#0d0d0d border-bottom:3px solid accent; logo CAPS huge left; nav links uppercase weight:900 letter-spacing:3px; CTA bg:accent color:#000 no-border-radius padding:12px 24px.
HERO: min-height:100vh; display:grid grid-template-rows; H1 font-size:clamp(80px,18vw,220px) weight:900 line-height:.85 uppercase color:accent SANGRA fuera del grid; imagen en posición absoluta rotada -5deg en esquina; texto descriptivo small caps debajo del H1; contador de año/número en esquina superior derecha; botón CTA rectangular no-radius bg:accent color:#000.
SERVICIOS: padding:80px; lista numerada vertical 01 02 03; cada ítem ocupa toda la anchura; número enorme opacity:.2 detrás; título uppercase H2; hover: background:accent color:#000 en todo el row.
STATS: grid 2x2 con líneas gruesas separadoras; cada celda: número enorme + descripción en caps.
NOSOTROS: 1 columna texto; párrafo grande serif; imagen fullwidth con caption.
TESTIMONIOS: citas tipográficas grandes con " " enormes en accent; nombre en caps pequeñas.
GALERÍA: grid irregular (CSS grid-template-areas); algunas imágenes grandes otras pequeñas.
CONTACTO: form minimalista; solo labels uppercase + input border-bottom:3px accent; botón bg:accent.
FOOTER: texto en caps; grid simple 2 cols; número de teléfono enorme.'],

'tech_dark' => ['name'=>'Tech Dark','category'=>'dark','vibe'=>'Vercel, Linear, Supabase. Dark UI profesional con detalles sutiles que gritan calidad técnica.','best_for'=>'SaaS, software, APIs, herramientas dev, plataformas B2B tech','heading'=>'Inter','body'=>'Inter',
'css_base'=>"body{background:#09090b;color:#fafafa} :root{--bg:#09090b;--bg2:#111113;--bg3:#18181b;--border:rgba(255,255,255,.08)}",
'sections'=>'HEADER: bg:rgba(9,9,11,.8) backdrop-blur:12px border-bottom:1px solid border; logo izq; nav center links text-sm text-zinc-400 hover:text-white; right: badge "Beta" + botón outline + botón primary.
HERO: min-height:100vh pt:120px; centered max-w:800px mx:auto; badge pill arriba "✦ Anuncio producto"; H1 clamp(48px,7vw,88px) weight:800 tracking-tight; text-gradient subtle white→zinc-400; descripción text-lg text-zinc-400 max-w:500px; 2 botones: primary bg:white text:black y ghost; debajo: bento grid 3 cols mostrando features con iconos y descripciones; cada bento-card: bg:#18181b border:1px border rounded-xl p:24px.
SERVICIOS: bg:#09090b; grid 3 cols feature-cards con border rounded-lg; icono SVG simple; feature name weight:600; descripción text-sm text-zinc-400.
STATS: bg:#18181b border-y border; flex row justify-around; cada stat: número weight:700 + label text-zinc-400; separadores verticales.
NOSOTROS: bg:#09090b; 2 cols 55/45; izq: headline + párrafo text-zinc-400 + lista de milestones con dot accent; der: terminal window simulado con código de la empresa o proceso.
TESTIMONIOS: bg:#18181b; grid 3 cols cards border rounded-xl; company logo top; quote text-sm; avatar + name + role text-zinc-400.
GALERÍA: bg:#09090b; grid 2x3 con screenshots o mockups del producto border rounded-lg overflow-hidden.
CONTACTO: bg:#09090b; centered max-w:480px; form card bg:#18181b border rounded-xl p:32px; inputs bg:#09090b border rounded-md.
FOOTER: bg:#09090b border-t border; grid 4 cols links; bottom row: logo + copyright + social icons.'],

'luxury_dark' => ['name'=>'Luxury Dark','category'=>'dark','vibe'=>'Louis Vuitton meets Rolls Royce en web. Oscuro con dorado/champagne. Ultra premium y exclusivo.','best_for'=>'joyería, relojes, inmobiliaria lujo, moda premium, servicios VIP, yates','heading'=>'Cormorant Garamond','body'=>'Lato',
'css_base'=>"body{background:#0a0806;color:#f5ede0} :root{--bg:#0a0806;--bg2:#120f0a;--bg3:#1a1510;--border:rgba(200,160,80,.15)}",
'sections'=>'HEADER: bg:transparent→rgba(10,8,6,.95) scroll; logo centrado texto CAPS letter-spacing:6px; nav links letter-spacing:3px text-xs uppercase color:rgba(245,237,224,.6) hover:accent; línea horizontal 1px gold divide header de body.
HERO: 100vh bg:#0a0806; IMG1 right-half 100% cover no-overlay; left half: bg muy oscuro con grain texture (CSS noise); badge text-xs letter-spacing:4px uppercase accent; H1 clamp(36px,5vw,72px) weight:300 (thin serif) line-height:1.15; tagline serif italic color:accent; CTA botón outline-gold letter-spacing:3px uppercase padding:16px 48px.
SERVICIOS: padding:120px 80px alt-rows; cada fila: número roman (I II III) accent size:120px opacity:.1; título weight:300 serif; línea gold 1px; descripción elegante.
STATS: bg:#120f0a; 4 métricas en fila con líneas doradas separadoras; número serif grande color:accent; label uppercase letter-spacing:2px text-xs.
NOSOTROS: bg:#0a0806; 2 cols; img con gold border-frame (outline offset); right: headline serif + párrafo elegante + lista con guiones dorados.
TESTIMONIOS: cita única grande centrada serif italic; auto-rotate; nombre en small-caps accent; logo del cliente arriba.
GALERÍA: grid irregular 3 imágenes grandes con gap:2px; b&w hover→color transition.
CONTACTO: bg:#120f0a; 2 cols; left: dirección y datos con gold separadores; right: form minimalista inputs solo border-bottom gold; botón text uppercase letter-spacing:4px.
FOOTER: bg:#050403; gold line top; logo centrado; cols centradas; "© 2025 · Todos los derechos reservados" centrado bottom.'],

'cinematografico' => ['name'=>'Cinematográfico','category'=>'dark','vibe'=>'Póster de película + editorial de revista. Fullbleed photography, drama, contraste extremo.','best_for'=>'producción audiovisual, fotografía, eventos, turismo premium, restaurantes finos','heading'=>'Playfair Display','body'=>'Source Sans 3',
'css_base'=>"body{background:#000;color:#fff} :root{--bg:#000;--bg2:#0a0a0a;--bg3:#111;--border:rgba(255,255,255,.1)}",
'sections'=>'HEADER: fixed transparent; logo blanco izq; nav links blancos weight:300 letter-spacing:2px; sin botón CTA—solo el logo y links.
HERO: 100vh fullbleed IMG1; overlay gradient:linear(to-bottom, rgba(0,0,0,.2) 0%, rgba(0,0,0,.7) 70%, #000 100%); contenido centrado bottom 20%; H1 clamp(64px,12vw,160px) weight:900 uppercase letter-spacing:-2px line-height:.9; sub-headline serif italic weight:300; botón CTA ghost-white border-white.
SERVICIOS: bg:#000; alternating rows: imagen fullwidth 60vh cover LEFT + texto derecha padding:80px; siguiente: texto izq + imagen RIGHT; cada par es un servicio; H2 weight:900 uppercase; texto weight:300.
STATS: bg:#111; row de 4 stats con líneas blancas separadoras; números enormes weight:900; labels weight:300 uppercase.
NOSOTROS: bg:#000; imagen fullwidth height:70vh cover con overlay; texto centrado encima blanco serif.
TESTIMONIOS: fondo negro; cita enorme centrada weight:300 serif italic; nombre en caps pequeñas.
GALERÍA: fullwidth masonry 3 cols sin gap o gap:1px; imágenes b&w hover→color saturate.
CONTACTO: bg:#0a0a0a; minimal—solo H2 grande y datos de contacto + form simple.
FOOTER: bg:#000; border-top:1px white; logo centrado; copyright.'],

'glassmorphism_dark' => ['name'=>'Glassmorphism Dark','category'=>'dark','vibe'=>'SaaS moderno con profundidad. Capas de cristal sobre fondos gradient oscuros. Depth y sofisticación.','best_for'=>'fintech, SaaS, apps móviles, seguros, crypto, plataformas digitales','heading'=>'Plus Jakarta Sans','body'=>'Inter',
'css_base'=>"body{background:#060818;color:#e8eaf6} :root{--bg:#060818;--bg2:#0d1128;--bg3:#131630;--border:rgba(255,255,255,.08)}",
'sections'=>'HEADER: bg:rgba(6,8,24,.7) backdrop-blur:20px border-bottom:1px solid rgba(255,255,255,.08); logo izq gradient text; nav links text-sm hover:text-white; CTA botón gradient primary→accent border-radius:100px.
HERO: 100vh bg:#060818; 3 radial gradients coloreados primary accent y morado en corners; contenido centrado; H1 clamp(52px,8vw,100px) weight:800; gradient-text animation slow; descripción text-lg opacity:.7; 2 botones: glassmorphism pill (bg:rgba(255,255,255,.08) border:1px rgba(white,.15) backdrop-blur:10px) y filled gradient; debajo: preview del producto/servicio en card glassmorphism flotante.
SERVICIOS: 3 cols cards glassmorphism bg:rgba(255,255,255,.04) border:1px rgba(255,255,255,.08) border-radius:20px backdrop-blur:10px; icono gradient; hover:bg:rgba(255,255,255,.08) scale(1.02).
STATS: 4 glassmorphism cards en row; cada una: ícono circle gradient + número + label.
NOSOTROS: bg:#0d1128; 2 cols; imagen con múltiples capas glassmorphism encima; right: timeline de milestones con accent dots.
TESTIMONIOS: slider de cards glassmorphism; cada card: avatar border gradient; estrellas; quote; nombre.
GALERÍA: grid con glassmorphism overlay en hover mostrando info.
CONTACTO: form en card glassmorphism centrada max-w:520px; inputs glassmorphism; botón gradient full-width.
FOOTER: bg:#060818; glassmorphism card footer con 4 cols; gradient top line.'],

'retro_80s' => ['name'=>'Retro 80s','category'=>'dark','vibe'=>'Synthwave, Miami Vice, Tron. Grid de neón sobre negro. Nostalgia con producción moderna.','best_for'=>'música, entretenimiento retro, bares temáticos, moda vintage, estudio creativo','heading'=>'Oswald','body'=>'Roboto',
'css_base'=>"body{background:#0a001a;color:#f0e6ff} :root{--bg:#0a001a;--bg2:#110022;--bg3:#180033;--border:rgba(var_accent,.3)}",
'sections'=>'HEADER: bg:#0a001a; logo con retro glow text-shadow magenta+cyan; nav links uppercase letter-spacing:3px; border-bottom:1px solid accent.
HERO: 100vh; perspectiva grid de líneas (CSS con gradients repeating-linear-gradient) hacia horizonte; sol/luna retro con gradient; H1 enorme italic uppercase con text-shadow doble magenta+cyan; subtitle monospace; botón CTA outline-accent con shadow glow.
SERVICIOS: bg:#110022; grid 3 cols; cada card: borde neon simple top; número retro grande; título uppercase; descripción; todo con palette magenta+cyan+amarillo.
STATS: fondo con grid retro; 4 stats con números en digital/LCD font (Orbitron o similar); labels.
NOSOTROS: bg:#0a001a; 2 cols; imagen con duotone filter magenta+cyan; texto retro.
TESTIMONIOS: cassette/VHS aesthetic cards; quote en monospace; stars pixel-style ★.
GALERÍA: grid con vignette y scanlines CSS effect en cada imagen.
CONTACTO: bg:#110022; form con inputs monospace border-bottom accent; botón con neon pulse.
FOOTER: grid de líneas retro; social como labels retro; copyright en monospace.'],

'cosmos_astro' => ['name'=>'Cosmos / Astro','category'=>'dark','vibe'=>'Espacio profundo, nebulosas, tecnología de punta. Inspirado en SpaceX, NASA, Blue Origin.','best_for'=>'tecnología avanzada, ingeniería, ciencia, innovación, drones, satélites','heading'=>'Rajdhani','body'=>'Exo 2',
'css_base'=>"body{background:#03060f;color:#c8d8f0} :root{--bg:#03060f;--bg2:#060c1a;--bg3:#0a1228;--border:rgba(100,160,255,.12)}",
'sections'=>'HEADER: bg:rgba(3,6,15,.8) backdrop-blur; logo con icono astronómico; nav links color:rgba(200,216,240,.7) hover:white; badge "Mission Active" parpadeante.
HERO: 100vh; bg:#03060f con CSS stars (radial gradient dots tiny whites); nebula color blobs primary+accent opacity:.15; H1 clamp(48px,8vw,96px) weight:700 tracking-wide; subtítulo monospace type-writer effect; contador animado "missions/projects"; 2 botones: primary filled + outline; globe/orbit animation SVG derecha.
SERVICIOS: bg:#060c1a; cards con subtle border y hover:border-primary glow; icono técnico SVG; título; spec list con ✓ marks.
STATS: bg:#03060f; 4 métricas con líneas de datos (like terminal); números con accent; unidades en text-small.
NOSOTROS: timeline horizontal con hitos de la empresa; cada punto: círculo accent dot + año + descripción.
TESTIMONIOS: bg:#060c1a; cards con company logos; quote; rating técnico.
GALERÍA: grid con imágenes en dark frame; hover glow border-primary.
CONTACTO: bg:#03060f; terminal-style form—labels en verde estilo código; inputs monospace.
FOOTER: bg:#030609; mission control aesthetic; grid 4 cols; coordenadas decorativas.'],

'dark_editorial' => ['name'=>'Dark Editorial','category'=>'dark','vibe'=>'Magazine oscuro de lujo. Spreads tipográficos impactantes, fotografía raw, editorial de moda.','best_for'=>'moda, fotografía artística, revistas, lifestyle premium, perfumes, cosmética','heading'=>'EB Garamond','body'=>'Mulish',
'css_base'=>"body{background:#111010;color:#f0ede8} :root{--bg:#111010;--bg2:#191717;--bg3:#211e1e;--border:rgba(240,237,232,.08)}",
'sections'=>'HEADER: bg:transparent; logo centrado letra-espaciado extremo; nav distribuida simétricamente; border-bottom:1px rgba(white,.1).
HERO: 100vh; split 50/50: izq bg:#111010 padding:80px texto editorial H1 enorme clamp(64px,10vw,120px) weight:400 italic serif line-height:.9; der imagen fullheight cover; accent stripe 4px vertical entre los dos.
SERVICIOS: bg:#111010; lista editorial: cada servicio ocupa 100% width con padding:60px 80px border-bottom:1px border; número en esquina; H2 weight:300; texto en 2 cols.
STATS: 4 stats en row con border dividers; serif weight:300 números; uppercase labels tiny letter-spacing.
NOSOTROS: fullwidth image con caption estilo magazine; debajo texto en columnas.
TESTIMONIOS: gran cita serif centrada weight:300 italic tamaño enorme; auto-rotate.
GALERÍA: editorial grid asimétrico; mezcla landscape y portrait; sin gap o gap:2px.
CONTACTO: minimalista; heading grande + email link underline + form básico.
FOOTER: minimal 1 fila; logo centrado; copyright.'],

// ═══ LIGHT ═══════════════════════════════════════════════════════════════════
'ultra_minimalista' => ['name'=>'Ultra Minimalista','category'=>'light','vibe'=>'Apple.com meets Dieter Rams. Cada elemento tiene un propósito. El espacio es el protagonista.','best_for'=>'productos premium, apps, tecnología consumer, arquitectura, diseño industrial','heading'=>'Inter','body'=>'Inter',
'css_base'=>"body{background:#ffffff;color:#1d1d1f} :root{--bg:#fff;--bg2:#f5f5f7;--bg3:#fbfbfd;--border:rgba(0,0,0,.08)}",
'sections'=>'HEADER: bg:rgba(255,255,255,.85) backdrop-blur:20px border-bottom:1px solid rgba(0,0,0,.06); logo izq text weight:600; nav links text-sm color:#1d1d1f; CTA botón bg:#1d1d1f color:white border-radius:100px text-sm.
HERO: min-height:100vh pt:120px; centered max-w:900px; tag line text-sm uppercase letter-spacing:2px color:accent mb:24px; H1 clamp(52px,8vw,96px) weight:700 tracking-tight line-height:1.05 color:#1d1d1f; descripción text-xl color:#6e6e73 max-w:600px mx:auto mt:24px; 1 CTA botón bg:accent color:white rounded-full; imagen del producto/servicio centered grande con subtle drop-shadow mt:60px.
SERVICIOS: bg:#f5f5f7; padding:100px 80px; título centrado weight:700; grid 3 cols; cada feature: ícono SF-symbols style, H3 weight:600, descripción text-sm color:#6e6e73.
STATS: bg:#fff; 4 stats en fila con líneas divisoras muy sutiles; número grande weight:700 color:accent; label text-sm color:#6e6e73.
NOSOTROS: bg:#f5f5f7; imagen grande centered con rounded-2xl; debajo texto centrado max-w:700px.
TESTIMONIOS: bg:#fff; grid 2 cols cards bg:#f5f5f7 rounded-2xl padding:32px; quote text-lg; autor + avatar pequeño.
GALERÍA: bg:#f5f5f7; grid 3 cols rounded-xl overflow-hidden; cada imagen aspect-ratio:4/3.
CONTACTO: bg:#fff; centrado max-w:560px; form card bg:#f5f5f7 rounded-2xl padding:40px; inputs bg:#fff rounded-lg border-transparent focus:border-accent.
FOOTER: bg:#f5f5f7 border-top:1px rgba(0,0,0,.06); grid 4 cols link-groups text-sm; bottom: copyright color:#6e6e73.'],

'corporativo_premium' => ['name'=>'Corporativo Premium','category'=>'light','vibe'=>'McKinsey meets IBM. Autoridad, confianza, escala global. B2B serio pero no aburrido.','best_for'=>'consultoría, legal, finanzas, banca, seguros, recursos humanos, B2B enterprise','heading'=>'Source Serif 4','body'=>'Source Sans 3',
'css_base'=>"body{background:#ffffff;color:#1a2332} :root{--bg:#fff;--bg2:#f4f6f9;--bg3:#e8ecf1;--border:rgba(26,35,50,.1)}",
'sections'=>'HEADER: bg:#fff border-bottom:2px solid primary; logo izq con tagline small debajo; nav links color:#1a2332 weight:500; CTA botón bg:primary color:white rounded:0 (sin border-radius).
HERO: bg:primary; padding:100px 80px; 2 cols 55/45; izq: etiqueta servicio text-xs uppercase letter-spacing:3px color:rgba(white,.6); H1 clamp(40px,5.5vw,72px) weight:700 color:white serif line-height:1.15; párrafo color:rgba(white,.8); 2 botones white+outline-white; credenciales logos clientes o certificaciones debajo; der: imagen profesional team/office rounded:0.
SERVICIOS: bg:#f4f6f9; padding:100px 80px; heading con accent underline; grid 3 cols; card: bg:#fff border-top:3px solid accent padding:32px; ícono outline; H3 serif; descripción; link → color:accent.
STATS: bg:primary; 4 cols; cada stat: número white weight:700 + descripción color:rgba(white,.7); border-right rgba(white,.2).
NOSOTROS: bg:#fff; 2 cols; left: timeline de hitos company; right: imagen equipo + certifications badges.
TESTIMONIOS: bg:#f4f6f9; 3 cards bg:#fff; logo empresa cliente top; quote serif italic; nombre + cargo; border-left:4px solid accent.
GALERÍA: bg:#fff; grid 3 cols; imágenes oficinas/equipo/proyectos; hover subtle overlay primary.
CONTACTO: bg:#1a2332; 2 cols; left: datos de contacto + mapa embed; right: form bg:rgba(white,.05) border inputs; botón bg:accent.
FOOTER: bg:#1a2332; 4 cols; color:rgba(white,.7); logo blanco; bottom bar bg:rgba(0,0,0,.2).'],

'editorial_magazine' => ['name'=>'Editorial Magazine','category'=>'light','vibe'=>'Vogue meets Monocle online. Columnas tipográficas, pull quotes enormes, fotografía editorial.','best_for'=>'medios, publicaciones, blogs premium, moda, cultura, gastronomía sofisticada','heading'=>'Playfair Display','body'=>'Lato',
'css_base'=>"body{background:#fafaf8;color:#1a1a1a} :root{--bg:#fafaf8;--bg2:#f3f3f0;--bg3:#ececea;--border:rgba(0,0,0,.1)}",
'sections'=>'HEADER: bg:#fafaf8 border-bottom:2px solid #1a1a1a; logo centrado tipográfico; nav distribuida en 2 bloques simétricamente; fecha/edición text-xs derecha.
HERO: 100vh; magazine spread: imagen fullwidth height:70vh cover object-position:center; caption text-xs color:#666 border-top:1px #1a1a1a; H1 serif clamp(52px,8vw,110px) weight:700 italic mt:40px max-w:900px; byline text-sm uppercase letter-spacing:2px color:accent.
SERVICIOS: padding:80px; 2 cols magazine layout; numerados con punto; título H2 serif; texto en 2 columnas CSS; pull-quote centrado grande italic color:accent en medio.
STATS: fila de stats con border separadores; número enorme serif; label caps small.
NOSOTROS: layout tipo perfil de revista: imagen grande izq crop cuadrado; der texto biográfico serif con drop-cap primera letra en accent.
TESTIMONIOS: citas con tipografía enorme " " decorativas accent; nombre en small caps; en loop.
GALERÍA: editorial grid mixto landscape+portrait sin gap o 1px; algunas imágenes span 2 cols.
CONTACTO: minimalista; solo heading serif + correo link grande + form simple.
FOOTER: 2 filas: links categorías + copyright + logo. Muy tipográfico.'],

'lujo_blanco' => ['name'=>'Lujo Blanco','category'=>'light','vibe'=>'Chanel, Hermès, Bottega Veneta. Blanco total, materiales nobles, lujo sin necesitar gritar.','best_for'=>'joyería, moda lujo, spas premium, bodas, cosméticos high-end, hoteles 5 estrellas','heading'=>'Cormorant Garamond','body'=>'Lato',
'css_base'=>"body{background:#fdfcfa;color:#1a1713} :root{--bg:#fdfcfa;--bg2:#f7f5f1;--bg3:#f0ece6;--border:rgba(26,23,19,.08)}",
'sections'=>'HEADER: bg:rgba(253,252,250,.97) border-bottom:1px solid rgba(0,0,0,.06); logo CAPS letter-spacing:8px text-sm centrado; nav simétricamente distribuida letter-spacing:3px text-xs uppercase color:#666.
HERO: 100vh; imagen fullwidth cover derecha 55%; izquierda: bg:#fdfcfa padding:80px; tagline text-xs letter-spacing:5px uppercase color:accent; H1 serif weight:300 clamp(40px,5vw,72px) line-height:1.2 italic; párrafo elegante color:#666; CTA botón bg:#1a1713 color:white letter-spacing:3px uppercase text-xs padding:16px 48px no-border-radius.
SERVICIOS: bg:#fdfcfa; padding:120px 80px; alternating: texto izq + imagen der, luego imagen izq + texto der; cada par: nombre del servicio en caps letter-spacing:3px; descripción serif weight:300; línea gold/accent thin.
STATS: 4 métricas en fila; líneas verticales separadoras muy sutiles; número serif weight:300 grande; label caps tiny.
NOSOTROS: bg:#f7f5f1; fullwidth imagen horizontal height:500px cover + caption; debajo texto centrado max-w:700px serif weight:300.
TESTIMONIOS: bg:#fdfcfa; cita grande centrada serif italic weight:300; autor small caps; logo de la empresa encima si aplica.
GALERÍA: grid 3 cols gap:2px; hover: subtle brightness reduce; todas las imágenes misma altura.
CONTACTO: bg:#f7f5f1; 2 cols; izq datos en tipografía elegante con líneas separadoras; der form minimalista inputs border-bottom-only accent.
FOOTER: bg:#1a1713; color:rgba(253,252,250,.7); logo centrado blanco caps; cols centradas links; bottom copyright.'],

'medico_clinico' => ['name'=>'Médico / Clínico','category'=>'light','vibe'=>'Confianza, higiene, profesionalismo médico. Inspira tranquilidad y certeza en el paciente.','best_for'=>'clínicas, hospitales, dentistas, psicólogos, centros médicos, laboratorios','heading'=>'Nunito','body'=>'Open Sans',
'css_base'=>"body{background:#f8fafe;color:#1e293b} :root{--bg:#f8fafe;--bg2:#eef4ff;--bg3:#fff;--border:rgba(59,130,246,.1)}",
'sections'=>'HEADER: bg:#fff shadow-sm border-bottom:1px solid rgba(59,130,246,.1); logo+nombre clínica izq; nav links color:#64748b hover:primary; teléfono y botón "Agendar cita" bg:primary color:white rounded-lg right.
HERO: bg:linear-gradient(135deg,#eef4ff,#f8fafe); padding:100px 80px; 2 cols; izq: badge "✓ Certificados" pill verde; H1 clamp(40px,5.5vw,64px) weight:700 color:#1e293b; descripción color:#64748b; 2 botones: "Agendar cita" bg:primary y "Ver servicios" outline; logos de seguros aceptados debajo; der: imagen médicos/instalaciones rounded-2xl bg:#fff shadow-lg.
SERVICIOS: bg:#fff; padding:80px; grid 3 cols; card: bg:#f8fafe border:1px rgba(primary,.1) rounded-2xl padding:28px; ícono médico color:primary; H3; descripción; "Más info →" color:primary.
STATS: bg:primary; 4 stats color:white; número + label; íconos checkmark.
NOSOTROS: bg:#f8fafe; 2 cols; left: fotos equipo médico con nombres y especialidades; right: credentials, certificaciones, años de experiencia.
TESTIMONIOS: bg:#fff; 3 cards; estrellas amarillas; testimonial de paciente; nombre + tratamiento realizado.
GALERÍA: bg:#f8fafe; instalaciones/equipos; grid 3 cols rounded-xl.
CONTACTO: bg:#eef4ff; 2 cols; left: dirección+teléfono+horarios tabla; right: form "Agendar cita" card bg:#fff shadow rounded-2xl.
FOOTER: bg:#1e293b; 4 cols; logo blanco; información legal médica texto-xs; copyright.'],

'arquitectura' => ['name'=>'Arquitectura / Inmobiliaria','category'=>'light','vibe'=>'Zaha Hadid Architects website. Espacios enormes, fotografía arquitectónica, planos y líneas.','best_for'=>'arquitectura, inmobiliaria premium, construcción de lujo, interiores, urbanismo','heading'=>'Josefin Sans','body'=>'Raleway',
'css_base'=>"body{background:#f8f7f5;color:#1c1c1c} :root{--bg:#f8f7f5;--bg2:#f0ede8;--bg3:#e8e4de;--border:rgba(28,28,28,.1)}",
'sections'=>'HEADER: bg:rgba(248,247,245,.95) border-bottom:1px solid rgba(0,0,0,.08); logo tipográfico caps letter-spacing:4px; nav links uppercase letter-spacing:2px text-xs; botón "Proyectos" outline.
HERO: 100vh; fullbleed IMG1 cover con overlay linear-gradient(to-right,rgba(248,247,245,.9) 40%,transparent); left content padding:80px; H1 serif clamp(52px,8vw,100px) weight:300 line-height:1; subtítulo caps letter-spacing:4px text-xs color:accent; CTA botón bg:#1c1c1c color:white padding:16px 40px text-sm letter-spacing:2px uppercase.
SERVICIOS: bg:#f8f7f5; padding:100px 80px; heading caps letter-spacing:3px border-bottom:2px solid #1c1c1c pb:20px; lista de proyectos/servicios estilo portfolio: número + nombre + imagen thumbnail hover:show; hover row: bg:#f0ede8.
STATS: bg:#1c1c1c; 4 métricas color:white; número grande weight:300; label letter-spacing:2px uppercase text-xs.
NOSOTROS: bg:#f0ede8; 2 cols; izq: "firma fundada en año" + filosofía texto weight:300 + equipo texto; der: imagen estudio/equipo fullheight.
TESTIMONIOS: bg:#f8f7f5; citas de clientes con proyecto referenciado; nombre + empresa; sin fotos—solo tipografía.
GALERÍA: portfolio masonry 3 cols sin gap; cada imagen con hover: overlay oscuro con nombre del proyecto en caps.
CONTACTO: bg:#1c1c1c; color:white; 2 cols; izq: dirección + email; der: form minimalista inputs border-bottom white; botón outline-white.
FOOTER: bg:#f0ede8; simple 2 filas; copyright caps.'],

'soft_pastel' => ['name'=>'Soft Pastel / Wellness','category'=>'light','vibe'=>'Calidez, bienestar, cuidado personal. Paleta suave, formas orgánicas, friendly y cercano.','best_for'=>'spa, yoga, nutrición, psicología, maternidad, productos naturales, beauty','heading'=>'Quicksand','body'=>'Nunito',
'css_base'=>"body{background:#fff9f5;color:#3d2b2b} :root{--bg:#fff9f5;--bg2:#fef3ec;--bg3:#fde8d8;--border:rgba(var_accent,.15)}",
'sections'=>'HEADER: bg:rgba(255,249,245,.95) border-bottom:1px solid rgba(accent,.1); logo con leaf/flor icon; nav links color:#8b6b61 hover:accent; botón "Reservar" bg:accent color:white rounded-full.
HERO: bg:linear-gradient(135deg,#fff9f5,#fef3ec); padding:100px 80px; 2 cols; izq: badge pill "✿ Bienvenida"; H1 clamp(40px,6vw,72px) weight:700 color:#3d2b2b; tagline cursiva color:accent; descripción color:#8b6b61; CTA botón rounded-full bg:accent shadow-lg; der: imagen hero con border-radius:60% 40% 30% 70% / 60% 30% 70% 40% (forma orgánica); floating badges wellness.
SERVICIOS: bg:#fff; padding:80px; grid 3 cols; card: bg:#fff9f5 border:1px rgba(accent,.15) rounded-3xl padding:32px; ícono floral/hoja color:accent; H3 weight:700; descripción text-sm color:#8b6b61; precio o CTA.
STATS: bg:accent; color:white; 4 stats rounded-2xl; número grande; label.
NOSOTROS: bg:#fef3ec; 2 cols; imagen con forma orgánica border-radius animada CSS; der: "Mi historia" heading; párrafo personal; lista de valores con ✿.
TESTIMONIOS: bg:#fff9f5; masonry 3 cols; cards rounded-3xl bg:#fff; estrellas; foto circular; quote.
GALERÍA: bg:#fff; grid rounded-3xl gap:16px imágenes de tratamientos/ambiente.
CONTACTO: bg:#fde8d8; centrado; heading "¿Lista para empezar?"; form card rounded-3xl bg:#fff; botón accent full-width rounded-full.
FOOTER: bg:#3d2b2b; color:rgba(255,249,245,.8); logo claro; 3 cols; inspirational quote.'],

'papel_artesanal' => ['name'=>'Papel Artesanal','category'=>'light','vibe'=>'Hecho a mano, cálido, auténtico. Texturas de papel, serif humanista, artesanía y alma.','best_for'=>'artesanos, cafeterías, restaurantes auténticos, productos handmade, marcas locales','heading'=>'Libre Baskerville','body'=>'Lora',
'css_base'=>"body{background:#f5f0e8;color:#2c1810} :root{--bg:#f5f0e8;--bg2:#ede5d5;--bg3:#e5d8c4;--border:rgba(44,24,16,.1)}",
'sections'=>'HEADER: bg:#f5f0e8 border-bottom:2px solid rgba(primary,.15); logo handcraft style (serif grande); nav links serif italic color:#5a3e2b; sin botón—solo links.
HERO: bg:#f5f0e8; 2 cols 50/50; izq: imagen artesanal con marco tipo polaroid border:8px white box-shadow; der: grande espacio blanco; H1 serif clamp(44px,7vw,88px) weight:700 italic color:primary; tagline pequeña caps letter-spacing:3px color:accent; CTA botón bg:primary color:white rounded-sm.
SERVICIOS: bg:#ede5d5; padding:80px; lista tipo menú de restaurante: categorías con ornamento decorativo; cada ítem: nombre + descripción italic + precio si aplica; separadores con ornamento tipográfico.
STATS: bg:primary; color:beige; 4 stats con números romanos o escritos; label serif italic.
NOSOTROS: bg:#f5f0e8; texto largo tipo carta personal en serif; imagen sepia o polaroid; firma manuscrita (google fonts cursiva).
TESTIMONIOS: bg:#ede5d5; tarjetas estilo nota manuscrita; quote en cursiva google font; nombre.
GALERÍA: bg:#f5f0e8; grid tipo tablero de fotos con pins; random slight rotations en cada imagen; gaps irregulares.
CONTACTO: bg:#2c1810; color:#f5f0e8; carta de contacto estilo vintage; form con inputs sepia; botón bg:accent.
FOOTER: bg:#2c1810; color:#ede5d5; logo negativo; texto mínimo; ornamento central.'],

'elegante_europeo' => ['name'=>'Elegante Europeo','category'=>'light','vibe'=>'Lujo discreto, minimalismo sofisticado. Espacios blancos amplios, serif clásica, proporción áurea. Como una galería de arte de Milán.','best_for'=>'moda premium, arquitectura de interiores, consultoría de alta gama, hoteles boutique, marcas de lujo accesible, joyería, spas premium','heading'=>'Playfair Display','body'=>'Source Sans 3',
'css_base'=>"body{background:#fafaf8;color:#1a1a1a} :root{--bg:#fafaf8;--bg2:#f4f2ee;--bg3:#eceae4;--border:rgba(0,0,0,.07)}",
'sections'=>'HEADER: bg:rgba(250,250,248,.97) backdrop-blur:10px border-bottom:1px solid rgba(0,0,0,.06); logo centrado; nav links distribuidos simétricamente a ambos lados del logo; font-size:11px letter-spacing:3px text-transform:uppercase color:#999; sin CTA visible solo links.
HERO: min-height:100vh; 2 cols 45/55; izq: bg:primary; padding:100px 60px; display:flex align-items:flex-start flex-direction:column justify-content:center; H1 Playfair Display clamp(40px,5vw,72px) font-weight:400 italic color:white line-height:1.18; tagline text-xs uppercase letter-spacing:4px color:rgba(white,.5) margin-bottom:20px ANTES del H1; descripción text-base color:rgba(white,.72) margin-top:20px; CTA botón border:1px solid rgba(white,.4) color:white bg:transparent padding:12px 28px letter-spacing:2px text-xs uppercase rounded-none hover:bg:white hover:color:primary; der: imagen 100% height object-fit:cover sin overlay; transición suave.
SERVICIOS: bg:#fafaf8; padding:120px 80px; H2 Playfair Display text-center font-size:clamp(28px,4vw,44px) font-weight:400; línea decorativa 60px 1px solid accent center margin:20px auto 60px; grid 3 cols gap:48px; cada card: border-top:1px solid rgba(0,0,0,.08) padding-top:28px; número "01" Playfair color:accent opacity:.25 font-size:36px; H3 Playfair font-size:20px font-weight:600 margin:8px 0; descripción Source Sans color:#666 font-size:15px; flecha "→" link color:primary margin-top:auto; hover: border-top-color:primary.
STATS: bg:primary; padding:80px; 4 stats en row separados por línea vertical 1px solid rgba(white,.15); número Playfair clamp(44px,5vw,72px) color:white font-weight:400; label text-xs letter-spacing:4px uppercase color:rgba(white,.55) margin-top:8px.
NOSOTROS: bg:#fafaf8; padding:100px 80px; 2 cols 55/45 gap:80px; izq: badge text-xs uppercase letter-spacing:3px color:accent margin-bottom:16px; H2 Playfair clamp(28px,3.5vw,44px) font-weight:400; párrafo color:#555 margin:24px 0; lista 3 ítems con dash "—" color:accent; CTA link color:primary text-decoration:underline underline-offset:4px; der: imagen con frame: padding:16px bg:white box-shadow:0 8px 48px rgba(0,0,0,.08) border-radius:2px.
TESTIMONIOS: bg:#f4f2ee; padding:80px; 3 cols; cada card: comilla " font-size:72px Playfair color:accent opacity:.15 line-height:.5 margin-bottom:16px; cita font-style:italic color:#444 font-size:15px; línea 40px 1px solid rgba(0,0,0,.15) margin:20px 0; nombre text-xs uppercase letter-spacing:3px color:#999.
GALERÍA: bg:#fafaf8; padding:80px; CSS columns:3 gap:16px; 6 imágenes; cada img: width:100% mb:16px border-radius:2px; hover: filter:saturate(1.1) scale(1.02) transition:.5s.
CONTACTO: bg:#1a1a1a; color:#fafaf8; padding:100px 80px; 2 cols gap:80px; izq: H2 Playfair color:white clamp(28px,3.5vw,44px) font-weight:400; datos contacto color:rgba(white,.6) con ícono SVG inline antes; der: form — inputs solo border-bottom:1px solid rgba(white,.15) bg:transparent color:white padding:12px 0 font-size:15px; label text-xs uppercase letter-spacing:2px color:rgba(white,.4); botón bg:accent color:white padding:14px 32px letter-spacing:2px text-xs uppercase rounded-none mt:24px.
FOOTER: bg:#111; color:rgba(250,250,248,.45); padding:48px 80px; logo centrado Playfair color:white font-size:22px; links text-xs uppercase letter-spacing:3px margin-top:24px text-center; línea 1px solid rgba(white,.08) mt:32px mb:16px; copyright text-center text-xs.'],

'swiss_design' => ['name'=>'Swiss / Tipográfico','category'=>'light','vibe'=>'International Typographic Style. Grid rígido, Helvetica energy, funcionalidad pura y belleza matemática.','best_for'=>'institucional, museos, instituciones culturales, agencias de diseño, B2B tech europeo','heading'=>'Barlow','body'=>'Barlow',
'css_base'=>"body{background:#ffffff;color:#000000} :root{--bg:#fff;--bg2:#f4f4f4;--bg3:#e8e8e8;--border:rgba(0,0,0,.15)}",
'sections'=>'HEADER: bg:#fff border-bottom:2px solid #000; logo tipográfico peso bold; nav links text-sm weight:600 uppercase tracking-widest; CTA bg:#000 color:#fff no-border-radius padding:12px 24px.
HERO: bg:#fff; grid 12 cols; H1 enorme clamp(80px,15vw,180px) weight:900 uppercase tracking-tighter line-height:.85 span full width; accent color en palabra clave; descripción text-sm weight:400 col 7-12; imagen b&w col 7-12 fullheight; CTA botón row siguiente.
SERVICIOS: bg:#f4f4f4; grid estricta tabla tipo revista suiza; cada servicio: número grid / nombre bold uppercase / descripción / flecha →; hover row bg:#000 color:#fff.
STATS: bg:#000; color:#fff; 4 cols iguales; número enorme weight:900; label text-xs uppercase letter-spacing:4px.
NOSOTROS: bg:#fff; column grid; H2 enorme; texto en 2 cols estrictas; sin decoración.
TESTIMONIOS: bg:#f4f4f4; citas tipográficas puras; peso variable thin↔bold; sin fotos.
GALERÍA: grid de 6 imágenes con mathematical proportions (fibonacci o golden ratio en sizes).
CONTACTO: bg:#fff; formulario en grid 12 cols; campos alineados perfectamente; botón bg:accent.
FOOTER: bg:#000; color:#fff; grid exacta; copyright caps letter-spacing:4px; minimal.'],

// ═══ COLORFUL ════════════════════════════════════════════════════════════════
'gradiente_vibrante' => ['name'=>'Gradiente Vibrante','category'=>'colorful','vibe'=>'Stripe meets Figma. Gradientes ricos, profundidad visual, SaaS moderno que convierte.','best_for'=>'SaaS, fintech, plataformas digitales, apps de productividad, marketplaces','heading'=>'Plus Jakarta Sans','body'=>'Inter',
'css_base'=>"body{background:#fafbff;color:#0f172a} :root{--bg:#fafbff;--bg2:#f1f5ff;--bg3:#fff;--border:rgba(var_primary,.12)}",
'sections'=>'HEADER: bg:rgba(250,251,255,.9) backdrop-blur:12px border-bottom:1px border; logo gradient text; nav links text-sm color:#64748b hover:#0f172a; CTA botón gradient primary→accent rounded-full.
HERO: bg:#fafbff; padding:120px 80px; radial gradient glow primary opacity:.12 top-center; centered max-w:900px; badge pill gradient outline; H1 clamp(52px,8vw,96px) weight:800 tracking-tight; gradient text en key words; descripción text-xl color:#475569; 2 botones: gradient filled + outline; debajo: floating feature cards con shadow-xl glass showcasing benefits.
SERVICIOS: bg:#fff; grid 3 cols; cards gradient border (border:2px solid transparent background-clip:padding-box + gradient border trick); ícono gradient background circle; hover: lift con gradient shadow.
STATS: gradient mesh background; 4 glassmorphism stat cards; número bold white; label white opacity:.8.
NOSOTROS: bg:#fafbff; 2 cols; izq: lista de features con gradient bullet icons; der: product/service mockup con gradient glow shadow.
TESTIMONIOS: bg:#fff; masonry 3 cols; cada card: gradient top border 2px; quote; autor con gradient avatar bg.
GALERÍA: bg:#f1f5ff; grid con gradient overlay en hover primary→accent opacity:.6.
CONTACTO: bg:gradient mesh; form centrado card bg:white shadow-2xl rounded-2xl padding:48px.
FOOTER: bg:#0f172a; 4 cols; gradient text en logo; links color:rgba(white,.6); gradient divider top.'],

'retro_pop_70s' => ['name'=>'Retro Pop 70s','category'=>'colorful','vibe'=>'Groovy, warm, fun. Harvest gold, avocado green, burnt orange. Vintage con producción moderna.','best_for'=>'alimentos artesanales, cerveza craft, moda vintage, música, eventos retro, food truck','heading'=>'Abril Fatface','body'=>'Josefin Sans',
'css_base'=>"body{background:#fdf6e3;color:#2b1d0e} :root{--bg:#fdf6e3;--bg2:#f5e9c8;--bg3:#ede0b0;--border:rgba(43,29,14,.15)}",
'sections'=>'HEADER: bg:#fdf6e3 border-bottom:3px solid #2b1d0e; logo retro (Abril Fatface huge); nav links uppercase letter-spacing:2px color:#5c3d1e; CTA botón bg:primary color:#fdf6e3 rounded-sm.
HERO: bg:primary; padding:100px 80px; 2 cols; izq: big retro badge "EST. 20XX" circular; H1 Abril Fatface clamp(56px,10vw,120px) color:#fdf6e3 line-height:.9; tagline caps letter-spacing:3px color:accent; CTA botón bg:#fdf6e3 color:primary; der: imagen con retro frame border:8px solid accent + shadow.
SERVICIOS: bg:#fdf6e3; padding:80px; retro cards con zigzag borders o scallop edges CSS; cada card: big number retro; nombre en Abril Fatface; descripción Josefin Sans.
STATS: bg:#2b1d0e; color:#fdf6e3; 4 stats en retro boxes border:3px solid accent; números Abril Fatface.
NOSOTROS: bg:#f5e9c8; collage aesthetic: img con polaroid frame; texto con retro typeface; ornamentos de época (☆ ◆ ●).
TESTIMONIOS: bg:#fdf6e3; "What people say" heading retro; cards con wavy border CSS; quote cursiva.
GALERÍA: bg:#ede0b0; grid tipo pósteres vintage; cada imagen con frame y caption retro.
CONTACTO: bg:primary; color:#fdf6e3; retro postcard design; form con retro inputs; botón bg:accent.
FOOTER: bg:#2b1d0e; color:#fdf6e3; groovy wavy top border CSS; logo; copyright.'],

'y2k_revival' => ['name'=>'Y2K Revival','category'=>'colorful','vibe'=>'Chrome, pixel art, translucent plastic. Nostalgia del año 2000 con producción actual. Trendy 2025.','best_for'=>'moda gen-z, música pop, beauty/cosmética joven, eventos, clubs, tecnología cool','heading'=>'Orbitron','body'=>'Space Grotesk',
'css_base'=>"body{background:#e8e0ff;color:#0a0020} :root{--bg:#e8e0ff;--bg2:#d4c8ff;--bg3:#fff;--border:rgba(100,50,255,.2)}",
'sections'=>'HEADER: bg:rgba(232,224,255,.8) backdrop-blur:10px border:1px solid rgba(255,255,255,.5); logo chrome effect CSS (gradient plata); nav links Orbitron uppercase text-xs; CTA botón chrome bg:linear-gradient(silver) color:#0a0020.
HERO: bg:#e8e0ff; múltiples elementos: cursor de PC antiguo CSS; windows XP style floating UI elements semi-transparent; H1 Orbitron clamp(48px,8vw,96px) weight:900 con chrome/holographic gradient text animation; badge "Y2K ✦ 2025"; CTA botón translucent plastic bg:rgba(255,255,255,.3) border:1px solid rgba(white,.6) backdrop-blur; imagen con filter: hue-rotate + saturate.
SERVICIOS: bg:#d4c8ff; plastic cards bg:rgba(255,255,255,.4) backdrop-blur border:1px rgba(white,.5) border-radius:20px; ícono pixel art estilo; hover: rotate slight + scale.
STATS: bg:linear-gradient(135deg,primary,accent); chrome stats boxes; números Orbitron color:silver.
NOSOTROS: bg:#e8e0ff; holographic card; timeline con pixel dots; texto Space Grotesk.
TESTIMONIOS: bg:#fff; cards tipo sticker/badge; rating en pixel stars ★.
GALERÍA: bg:#d4c8ff; photos con filter saturate + slight color-aberration; grid con gaps.
CONTACTO: bg:primary; color:white; plastic form card; Orbitron labels; botón chrome.
FOOTER: bg:#0a0020; color:#e8e0ff; pixel art divider top; Orbitron logo; grid.'],

'organico_natural' => ['name'=>'Orgánico / Natural','category'=>'colorful','vibe'=>'Tierra, plantas, sostenibilidad. Colores naturales vibrantes pero suaves, formas orgánicas y texturas.','best_for'=>'productos orgánicos, agricultura, sostenibilidad, eco-brands, alimentación natural','heading'=>'Nunito','body'=>'Lato',
'css_base'=>"body{background:#f4f8f0;color:#1d3018} :root{--bg:#f4f8f0;--bg2:#e8f0e0;--bg3:#dcebd0;--border:rgba(29,48,24,.1)}",
'sections'=>'HEADER: bg:rgba(244,248,240,.95) border-bottom:2px solid rgba(primary,.2); logo con leaf icon; nav links color:#3d5e35 hover:accent; botón "Contactar" bg:primary color:white rounded-full.
HERO: bg:linear-gradient(135deg,#e8f0e0,#f4f8f0); 2 cols; izq: badge "🌿 100% Natural"; H1 clamp(44px,6.5vw,80px) weight:700 color:#1d3018; descripción color:#4a6b42; CTA botón bg:accent color:white rounded-full; certifications eco badges; der: imagen producto/campo con border-radius orgánico (50% 50% 30% 70% / 60% 40% 70% 30%) CSS animation slow float.
SERVICIOS: bg:#fff; grid 3 cols; cards bg:#f4f8f0 rounded-3xl border:2px solid rgba(primary,.15); leaf icon accent; H3; descripción; "Ver más" color:primary.
STATS: bg:primary; color:white; 4 stats con leaf separators; número grande; label.
NOSOTROS: bg:#e8f0e0; 2 cols; texto sobre valores y proceso natural; imagen campo/proceso con rounded orgánico; bullet list con 🌿.
TESTIMONIOS: bg:#f4f8f0; 3 cards con flower/leaf decorations; rating 5 estrellas verdes; quote.
GALERÍA: bg:#fff; grid imágenes nature/product; hover green overlay.
CONTACTO: bg:#1d3018; color:#f4f8f0; earthy tone form; botón bg:accent rounded-full.
FOOTER: bg:#1d3018; logo blanco con leaf; 3 cols; eco certification logos; copyright.'],

'tropical_caribe' => ['name'=>'Tropical / Caribe','category'=>'colorful','vibe'=>'Playa, palmeras, turismo vibrante. Colores saturados del trópico, alegría y aventura.','best_for'=>'turismo, hoteles playa, tours, deportes acuáticos, restaurantes tropicales, resorts','heading'=>'Nunito','body'=>'Open Sans',
'css_base'=>"body{background:#fff8f0;color:#1a1a2e} :root{--bg:#fff8f0;--bg2:#fff0e0;--bg3:#ffe8d0;--border:rgba(var_accent,.2)}",
'sections'=>'HEADER: bg:rgba(255,248,240,.95) border-bottom:2px solid rgba(accent,.2); logo tropical (palm icon); nav links color:#c2440a hover:accent; botón "Reservar" bg:accent color:white rounded-full shadow-lg.
HERO: bg:primary; fullbleed IMG1 cover overlay:linear-gradient(to-right,rgba(primary,.85) 40%,rgba(primary,.3)); 2 cols; izq: badge pill "☀️ Temporada 2025"; H1 clamp(48px,7vw,88px) weight:800 color:white line-height:1.1; descripción color:rgba(white,.85); 2 botones blanco+outline-white; floating weather/rating badges; der imagen tropical overlaid.
SERVICIOS: bg:#fff8f0; grid 3 cols; cards bg:#fff rounded-2xl border-top:4px solid accent shadow-md; ícono emoji o SVG tropical; H3; descripción; precio si aplica; CTA.
STATS: bg:accent; color:white; 4 stats; ícono + número grande + label; rounded-2xl cards.
NOSOTROS: bg:#fff0e0; 2 cols; imagen playa/resort rounded-2xl; der: historia, ubicación, highlights con ☀️ bullets.
TESTIMONIOS: bg:#fff8f0; 3 cards trip-advisor style; rating estrellas; quote; nombre + país.
GALERÍA: bg:#fff; grid masonry 3 cols; imágenes destinos/actividades; hover overlay con nombre.
CONTACTO: bg:primary; color:white; form card bg:rgba(white,.1) backdrop-blur border:rgba(white,.2) rounded-2xl; botón bg:accent.
FOOTER: bg:#1a1a2e; color:rgba(255,248,240,.8); palma SVG decorativa; 3 cols; copyright.'],

'flat_bold' => ['name'=>'Flat Bold','category'=>'colorful','vibe'=>'Geométrico, colores sólidos, impacto visual inmediato. Diseño gráfico elevated a web.','best_for'=>'servicios simples, pymes dinámicas, agencias locales, food delivery, apps consumer','heading'=>'Barlow Condensed','body'=>'Barlow',
'css_base'=>"body{background:#ffffff;color:#000000} :root{--bg:#fff;--bg2:#f5f5f5;--bg3:#ebebeb;--border:#000}",
'sections'=>'HEADER: bg:primary; logo CAPS blanco weight:900 letter-spacing:2px; nav links blancos uppercase; botón bg:#fff color:primary no-border-radius weight:900.
HERO: bg:primary; 2 cols; izq: H1 Barlow Condensed clamp(72px,13vw,160px) weight:900 uppercase color:white line-height:.9; acento en palabra clave con bg:accent color:primary inline-block px:8px; botón bg:#fff color:primary padding:16px 40px no-radius weight:900; der: forma geométrica bold (rectángulo, círculo) en accent conteniendo imagen.
SERVICIOS: bg:#fff; 3 cols; cada card: top border:8px solid accent; número bold; título uppercase; descripción; hover bg:primary color:white.
STATS: bg:#000; 4 cols; número enorme Barlow Condensed weight:900 color:accent; label caps blanco.
NOSOTROS: bg:accent; color:#000; 2 cols sin imagen—solo texto bold; gran número de años en bg:#000 color:accent.
TESTIMONIOS: bg:primary; color:white; citas grandes; nombre en accent.
GALERÍA: bg:#fff; grid 3x2; cada imagen con borde grueso 4px primary; hover border:accent.
CONTACTO: bg:#000; color:#fff; form minimalista; inputs border-bottom:4px white; botón bg:primary.
FOOTER: bg:primary; color:white; border-top:8px solid accent; 3 cols uppercase; minimal.'],

'startup_colorido' => ['name'=>'Startup Colorido','category'=>'colorful','vibe'=>'Energía startup 2025. Gradientes vibrantes, glassmorphism, blobs animados, micro-interacciones.','best_for'=>'startups tech, apps móviles, plataformas jóvenes, edtech, healthtech','heading'=>'Plus Jakarta Sans','body'=>'Inter',
'css_base'=>"body{background:#f0f4ff;color:#1a1a2e} :root{--bg:#f0f4ff;--bg2:#e8eeff;--bg3:#fff;--border:rgba(99,102,241,.12)}",
'sections'=>'HEADER: bg:primary; logo blanco izq; nav links blancos; botón CTA outline-blanco hover:bg-white color:primary; badge "Nuevo" pill animado.
HERO: gradient:135deg,primary→accent; 100vh; 3 blobs blur:80px opacity:.4 animation:float; grid 2 cols; izq: badge "✦ Bienvenidos"; H1 clamp(48px,7vw,90px) weight:800 color:white; párrafo opacity:.85; 2 botones blanco+outline; der: imagen rounded-2xl rotate(-2deg) con floating stat-cards.
SERVICIOS: bg:#f8f9ff; grid 3 cols; cards bg:#fff rounded-2xl shadow-sm; ícono gradient circle 64px; H3; descripción; hover translateY(-8px) shadow-lg.
STATS: gradient primary→accent; 4 glassmorphism cards bg:rgba(white,.1) backdrop-blur border:rgba(white,.2) rounded-xl.
NOSOTROS: bg:#fff; 2 cols; imagen rounded-3xl con 2 floating stat-cards; der: badge+title+checkmarks+botón.
TESTIMONIOS: bg:#f8f9ff; grid 3 cols; bg:#fff rounded-xl; estrellas ★★★★★; avatar border:3px accent; quote.
GALERÍA: bg:#fff; grid 3x2; hover gradient overlay primary→accent opacity:.7 zoom icon.
CONTACTO: gradient primary→accent; form card bg:#fff rounded-2xl padding:48px max-w:600px; botón primary full-width.
FOOTER: bg:primary; 4 cols glassmorphism pills; blobs deco; copyright.'],

'futurista' => ['name'=>'Futurista','category'=>'colorful','vibe'=>'Holográfico, iridiscente, tecnología del futuro. Como si Apple diseñara en 2040.','best_for'=>'tecnología de punta, IA, biotech, robótica, innovación disruptiva, NFT/crypto','heading'=>'Rajdhani','body'=>'Exo 2',
'css_base'=>"body{background:#08001a;color:#e0d0ff} :root{--bg:#08001a;--bg2:#100028;--bg3:#180038;--border:rgba(180,100,255,.15)}",
'sections'=>'HEADER: bg:rgba(8,0,26,.7) backdrop-blur:20px; logo con holographic shimmer CSS animation; nav links color:rgba(224,208,255,.7) hover:white; botón gradient iridiscente border:1px rgba(primary,.3).
HERO: bg:#08001a; 100vh; radial gradients holográficos (purple+cyan+pink) blur:120px opacity:.3 en corners animados; H1 clamp(52px,9vw,110px) weight:700 tracking-tight; gradient text animado iridiscente (hue-rotate animation); subtítulo monospace text-sm color:rgba(224,208,255,.6); 2 botones: holographic border animated + filled gradient; derecha: 3D rotating element (CSS) o imagen futurista.
SERVICIOS: bg:#100028; grid 3 cols; cards holographic border (animated gradient border CSS); ícono tech SVG color:accent; hover: holographic glow scale(1.03).
STATS: 4 stats holographic cards; número gradient text animado; label color:rgba(white,.6).
NOSOTROS: bg:#08001a; 2 cols; timeline de innovaciones; imagen futurista con glow effects.
TESTIMONIOS: bg:#100028; cards glassmorphism purple; quote; avatar con glow border.
GALERÍA: bg:#08001a; grid con imágenes futuristas; hover: hue-rotate filter.
CONTACTO: bg:#100028; form holographic card; inputs glassmorphism; botón holographic animated.
FOOTER: bg:#04000d; gradient top line iridiscente; logo holographic; 4 cols; copyright.'],

'isometrico' => ['name'=>'Isométrico','category'=>'colorful','vibe'=>'Ilustraciones isométricas, tech producto visual, profundidad 3D sin WebGL. Moderno y diferenciador.','best_for'=>'SaaS con producto visual, logística, fintech con datos, infraestructura cloud, gaming B2B','heading'=>'Outfit','body'=>'DM Sans',
'css_base'=>"body{background:#f8faff;color:#0f1729} :root{--bg:#f8faff;--bg2:#eef2ff;--bg3:#fff;--border:rgba(99,102,241,.1)}",
'sections'=>'HEADER: bg:#fff border-bottom:1px border shadow-sm; logo + wordmark; nav links color:#64748b; CTA botón bg:primary color:white rounded-lg.
HERO: bg:linear-gradient(135deg,#f8faff,#eef2ff); padding:100px 80px; 2 cols 55/45; izq: badge "🚀 v2.0 Disponible"; H1 clamp(44px,6vw,72px) weight:800 tracking-tight color:#0f1729; descripción color:#475569; 2 botones primary+outline; debajo logos de empresas clients "Usado por"; der: isometric mockup del producto (CSS transform rotateX(30deg) rotateY(-30deg)) mostrando interface.
SERVICIOS: bg:#fff; grid 3 cols; cada card: isometric icon top (PNG o SVG rotado CSS); H3 weight:700; descripción; hover: card lifts con isometric shadow.
STATS: bg:primary; 4 stats color:white; número grande; label opacity:.8; isometric separator lines.
NOSOTROS: bg:#eef2ff; timeline de producto milestones con isometric dots; imagen isometric office/team.
TESTIMONIOS: bg:#fff; grid 3 cols trust-cards con company logo; quote text-sm; rating; author.
GALERÍA: bg:#f8faff; isometric screenshots grid en CSS perspective; hover: flat transition.
CONTACTO: bg:#0f1729; color:white; form card bg:#1a2540 rounded-xl; inputs bg:#0f1729 border-primary; botón gradient.
FOOTER: bg:#0f1729; 4 cols; logo blanco + wordmark; links color:rgba(white,.6); isometric divider top.'],

'playful_creativo' => ['name'=>'Playful Creativo','category'=>'colorful','vibe'=>'Agencias creativas, estudio de diseño, energía joven y fresca. Fun pero profesional.','best_for'=>'agencias de diseño, estudios creativos, edtech infantil, apps jóvenes, entretenimiento familiar','heading'=>'Nunito','body'=>'Nunito',
'css_base'=>"body{background:#fffdf5;color:#1a1a2e} :root{--bg:#fffdf5;--bg2:#fff8e8;--bg3:#fff3d0;--border:rgba(var_accent,.2)}",
'sections'=>'HEADER: bg:#fffdf5 border-bottom:3px dashed accent; logo con carácter ilustrado; nav links weight:800 color:#1a1a2e; CTA botón bg:accent color:#fff rounded-full weight:800 con emoji.
HERO: bg:#fffdf5; 2 cols; izq: floating sticker badges absolutos con emojis; H1 clamp(48px,8vw,96px) weight:900 color:#1a1a2e; keyword en highlight amarillo bg:accent inline-block rounded-lg px:8px; descripción weight:600 color:#64748b; CTA botón bg:primary color:#fff rounded-full big shadow; social proof "👍 +1000 clientes felices"; der: ilustración o imagen playful con random sticker elements flotando.
SERVICIOS: bg:#fff; grid 3 cols; cards bg:#fffdf5 rounded-2xl border:3px solid #1a1a2e shadow-hard (box-shadow 4px 4px 0px #1a1a2e); ícono emoji grande; H3 weight:800; descripción; hover: shadow-harder translate(-2px,-2px).
STATS: bg:primary; color:white; 4 stats rounded-2xl; número huge weight:900; emoji + label.
NOSOTROS: bg:#fff8e8; 2 cols; imagen con sticker frame (border:4px solid #1a1a2e border-radius:12px + rotation) ; der: "quiénes somos" con bullet points emoji; fun tone.
TESTIMONIOS: bg:#fffdf5; masonry 3 cols; cards comic-style border:3px solid #1a1a2e shadow-hard; ★★★★★; quote entre " "; nombre + emoji país.
GALERÍA: bg:#fff; bento grid mixed sizes; cada celda border:3px solid #1a1a2e hover:bg:accent.
CONTACTO: bg:accent; padding:80px; card bg:#fff border:4px solid #1a1a2e shadow-hard rounded-2xl; form fun; botón primary.
FOOTER: bg:#1a1a2e; color:white; 3 cols; logo blanco; sticker emojis decorativos; copyright fun.'],
        ]; // fin $all_styles

        // ── Selección del estilo para esta generación ─────────────────────────
        $style_key = trim($input['style_key'] ?? '');
        // Validar que el key exista; si no, usar fallbacks por posición
        if (!isset($all_styles[$style_key])) {
            $fallbacks = [1=>'moderno_bold', 2=>'elegante_europeo', 3=>'startup_colorido'];
            $style_key = $fallbacks[$style] ?? 'moderno_bold';
        }
        $s = $all_styles[$style_key];

        // ── Tipografía: manual > campo manual > defecto por estilo ───────────
        $font = ['heading' => $s['heading'], 'body' => $s['body']];

        // Prioridad 1: fuentes encontradas en el manual
        if (!empty($manual_fonts_found)) {
            $font['heading'] = $manual_fonts_found[0];
            if (isset($manual_fonts_found[1])) $font['body'] = $manual_fonts_found[1];
        }
        // Prioridad 2: fuentes ingresadas manualmente
        if ($fonts) {
            $font_parts = array_map('trim', explode(',', $fonts));
            if (!empty($font_parts[0])) $font['heading'] = $font_parts[0];
            if (!empty($font_parts[1])) $font['body']    = $font_parts[1];
        }

        // Bloque resumen del manual para el prompt
        $manual_summary = '';
        if ($brand_manual_text) {
            $manual_summary .= "\nCONTENIDO EXTRAÍDO DEL MANUAL:\n" . $brand_manual_text;
            if (!empty($manual_colors_found)) {
                $manual_summary .= "\n\nCOLORES OFICIALES DETECTADOS EN EL MANUAL (USAR EXACTAMENTE ESTOS):\n" . implode('  |  ', $manual_colors_found);
            }
            if (!empty($manual_fonts_found)) {
                $manual_summary .= "\n\nTIPOGRAFÍAS OFICIALES DETECTADAS EN EL MANUAL:\n" . implode(', ', $manual_fonts_found);
            }
        }

        // ── Construir el bloque de imágenes para el prompt ────────────────────
        $img_block = '';
        for ($i = 0; $i < min(8, count($img_list)); $i++) {
            $img_block .= 'IMG' . ($i + 1) . ': ' . $img_list[$i] . "\n";
        }

        // ── Secciones estructurales del cliente ───────────────────────────────
        $structure_block = trim($p['structure'] ?? '');

        // ── Prompt master ─────────────────────────────────────────────────────
        $prompt = <<<PROMPT
You are the world's best web designer — Awwwards, FWA, CSS Design Awards winner. You create production-ready, spectacular websites that win awards. Your code is clean, complete, and works perfectly on first run.

━━━ MISSION ━━━
Generate a COMPLETE, AWARD-WINNING homepage HTML for a real paying client. This is a real proposal that will be shown to the client tomorrow. Excellence is not optional.

━━━ NON-NEGOTIABLE RULES ━━━
1. Generate COMPLETE HTML from <!DOCTYPE html> all the way to </html> — NEVER stop before finishing
2. ZERO Lorem Ipsum — every word of content must come from the business brief below
3. Navigation menu items must be REAL sections of this specific business (not generic "Home, About, Services, Contact")
4. Use ONLY these exact brand colors — never use generic blues, greens, or purple from defaults
5. Every service/product mentioned in the brief must appear by name
6. All 3 testimonials must sound like real clients of THIS business (reference the actual service they used)
7. Stats/numbers must be believable for this industry (use brief data or industry-realistic figures)
8. The hero H1 must communicate the UNIQUE value proposition — not a generic tagline
9. Every section (services, about, portfolio, pricing) must end with a CTA button — never leave a section without a next step
10. Include at minimum 3 trust signals: years in business badge, client count, certifications, awards, or media mentions
11. Every img tag must have a descriptive alt attribute (never alt="" for content images)
12. All interactive elements (buttons, links, inputs) must have :focus-visible styles for accessibility
13. Mobile must be fully usable: tap targets minimum 44px, no horizontal scroll, readable font sizes (min 16px body)

━━━ CLIENT BRIEF ━━━
Business name: {$p['project_name']}
Industry/sector: (infer from brief below)

BRIEF:
{$p['brief']}

REQUESTED STRUCTURE:
{$structure_block}

EXTRA CONTEXT (goals, target audience, differentials, competitors):
{$extra}

━━━ MANUAL DE MARCA (MÁXIMA PRIORIDAD — LEER Y APLICAR TODO) ━━━
El manual de marca es la fuente de verdad del cliente. DEBES implementar en el diseño:
- COLORES: usa EXACTAMENTE los HEX detectados — nunca los sustituyas
- TIPOGRAFÍAS: las fuentes indicadas en el manual son obligatorias
- LOGO: respeta las reglas de espaciado, versiones y fondos permitidos que indica el manual
- ELEMENTOS GRÁFICOS: si el manual menciona formas geométricas, líneas, patterns, texturas, iconografía o elementos decorativos de la marca — incorpóralos en el diseño web (como separadores, fondos, overlays, bordes decorativos)
- TAGLINE / SLOGAN: si aparece en el manual, úsalo en el hero
- TONO VISUAL: aplica el carácter visual de la marca (minimalista, bold, colorido, elegante, etc.) descrito en el manual
- CONSTRUCCIONES PROHIBIDAS: si el manual indica colores, combinaciones o estilos que no deben usarse, evítalos
{$manual_summary}
{$brand_description_ctx}

━━━ COMPETITIVE INTELLIGENCE ━━━
(Use this intel to make the design BEAT the competition — differentiate, don't copy their patterns)
{$comp_analysis_text}

━━━ SITE TO REPLICATE ━━━
(Analyze the structure, sections, flow, and copy patterns of this site. Recreate a similar layout and user journey adapted to our client brand.)
{$replicate_content}

━━━ REFERENCE SITES ━━━
(Match or exceed the quality level of these references)
{$refs}

━━━ BRAND COLORS — USE EXACTLY THESE ━━━
Primary:  {$c1}
Accent:   {$c2}
Light:    {$c3}
Extra:    {$c4}

In CSS :root define:
--primary: {$c1};
--accent: {$c2};
--light: {$c3};
--extra: {$c4};

IMPORTANT: Use var(--primary) and var(--accent) for ALL colored elements.
NEVER use #007bff, #28a745, #6c757d or any Bootstrap/generic defaults.

━━━ TYPOGRAPHY ━━━
Heading font: {$font['heading']} (Google Fonts)
Body font: {$font['body']} (Google Fonts)
Include in <head>: @import both fonts from Google Fonts API

━━━ THIS PROPOSAL: {$s['name']} ━━━
Vibe: {$s['vibe']}

BASE CSS VARIABLES:
{$s['css_base']}

SECTION-BY-SECTION SPECIFICATIONS (follow exactly):
{$s['sections']}

━━━ IMAGES — USE THESE EXACT URLs ━━━
(These are industry-relevant high-quality photos. Assign them as described)
{$img_block}
Assignment:
- Hero background: IMG1 (the most impactful, full-screen)
- Services section: IMG2, IMG3, IMG4 (one per service card)
- About/Nosotros: IMG5
- Gallery: IMG3, IMG4, IMG5, IMG6, IMG7, IMG8 (in grid)
- Parallax/separator: IMG2

━━━ LOGO — MANDATORY IN HEADER AND FOOTER ━━━
CRITICAL: You MUST place this exact logo HTML in the header nav AND in the footer. DO NOT skip it, DO NOT replace it with text, DO NOT modify the src URL.

LOGO HTML (copy verbatim, preserve the img src exactly):
{$logo_html}

Visibility rules:
- The logo MUST always be visible. Never hide it with opacity:0, visibility:hidden or display:none.
- On DARK or COLORFUL backgrounds: wrap the img in a <div style="background:rgba(255,255,255,0.12);padding:6px 12px;border-radius:6px;display:inline-flex"> so it is always readable — do NOT use brightness(0) filter which makes it invisible.
- On LIGHT backgrounds: use logo as-is, no filter, no wrapper.
- The header starts transparent on scroll position 0. Apply a CSS rule so the logo is always visible even when header background is transparent (e.g. drop-shadow or the white pill wrapper above).
- Text fallback (no img): use font-weight:900 and the correct color for the background.

━━━ MANDATORY TECHNICAL REQUIREMENTS ━━━
- Scroll progress bar: thin 3px bar at top of page, background var(--accent), width updates with JS
- IntersectionObserver for reveal animations: elements fade+slide up when entering viewport
- Header scroll behavior: changes background/shadow when page scrolls past 80px
- Stagger animations on cards: animation-delay: calc(0.1s * var(--i)) using CSS custom properties
- Hover effects on all cards and buttons with smooth transitions (.25s ease)
- Responsive breakpoints: @media(max-width:1024px), @media(max-width:768px), @media(max-width:480px)
- WhatsApp floating button (bottom-right, green #25D366) linking to wa.me/ if WhatsApp mentioned in brief
- Form validation with inline error messages (JS, no alert())
- Smooth scroll between sections: scroll-behavior:smooth on html
- All images with loading="lazy" attribute
- CSS custom property --i for stagger: set via inline style on each card

━━━ FOOTER MUST INCLUDE ━━━
- Logo: paste the EXACT logo HTML above (with filter:brightness(0) invert(1) if footer background is dark)
- 4 columns: business description + social links | services list | contact info | (newsletter or extra CTA)
- Real contact info from brief (phone, email, address, WhatsApp)
- Social media icons (SVG inline, not Font Awesome)
- Copyright line with business name and current year
- This exact credit at the very bottom:
<div style="text-align:center;padding:10px;background:rgba(0,0,0,0.25);font-size:11px;opacity:0.6">Propuesta creada con <a href="https://dimark.co" target="_blank" style="color:inherit;font-weight:700;text-decoration:none">Super Cerebro by DIMARK</a></div>

━━━ SEO — MANDATORY IN <head> ━━━
Include ALL of these in the <head> (use real data from the brief):
- <title> tag: business name + main service + city if local (max 60 chars)
- <meta name="description"> (150-160 chars, include main keyword + CTA)
- Open Graph: og:title, og:description, og:image (use IMG1 URL), og:type="website"
- Twitter Card: twitter:card="summary_large_image", twitter:title, twitter:description
- <link rel="canonical"> with placeholder URL structure
- Structured Data JSON-LD: use schema.org/LocalBusiness or schema.org/Organization
  Include: name, description, url, telephone, address (from brief), sameAs (social links from brief)
- <meta name="robots" content="index, follow">
- <html lang="es"> (or detected language from brief)

━━━ PERFORMANCE — MANDATORY ━━━
- Google Fonts: use <link rel="preconnect" href="https://fonts.googleapis.com"> BEFORE the @import
- Add <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> as well
- font-display: swap on ALL @font-face declarations
- Hero image (IMG1): add fetchpriority="high" loading="eager" — it is the LCP element, NOT lazy
- All other images: loading="lazy" (already required)
- All <script> tags: use defer attribute
- CSS: avoid @import inside <style> — use <link> tags in <head> for external CSS
- Add will-change: transform only on animated elements (cards on hover, not all elements)

━━━ CONVERSION ARCHITECTURE ━━━
Structure the page as a conversion funnel:
1. Hero: attention + single primary CTA (WhatsApp or contact form) — above the fold
2. After services section: CTA "¿Listo para empezar?" with button + optional phone number
3. After testimonials: CTA with urgency ("Lugares disponibles este mes" or similar believable scarcity)
4. Sticky header: after scroll, show a compact CTA button in the navigation bar (right side)
5. Floating WhatsApp button: always visible bottom-right (#25D366)
6. Exit-intent or last section: "¿Tienes preguntas?" with a short contact form (name, phone, message)
The primary action should always be ONE click away regardless of scroll position.

━━━ OUTPUT FORMAT ━━━
- Return ONLY the complete HTML — no explanations, no markdown, no code fences
- Start with: <!DOCTYPE html>
- End with: </html>
- The file must be self-contained (all CSS in <style>, all JS in <script> before </body>)
- DO NOT truncate or summarize any section — write every section fully
PROMPT;

        $pdo->prepare("UPDATE dm_proposals SET status='generating' WHERE id=?")->execute([$id]);
        $log_dir = __DIR__ . '/../logs/';
        if (!is_dir($log_dir)) mkdir($log_dir, 0755, true);
        file_put_contents($log_dir . 'token_usage.log', date('Y-m-d H:i:s') . " | INICIO estilo:{$style} id:{$id}\n", FILE_APPEND);

        // Lock de archivo por propuesta: serializa las 3 requests paralelas del frontend
        // (Promise.all lanza style 1+2+3 simultáneamente — el lock garantiza que solo
        // una llame a Claude a la vez, evitando el rate limit de 8k tokens/min)
        $lock_file = sys_get_temp_dir() . '/dm_prop_' . $id . '.lock';
        $lock_fp   = fopen($lock_file, 'w');
        $lock_ok   = $lock_fp && flock($lock_fp, LOCK_EX); // bloquea hasta turno libre

        $html = call_claude_html($prompt);

        if ($lock_ok) { flock($lock_fp, LOCK_UN); }
        if ($lock_fp) { fclose($lock_fp); }

        if (empty($html) || $html === '__CREDIT_ERROR__') {
            $pdo->prepare("UPDATE dm_proposals SET status='ready' WHERE id=?")->execute([$id]);
            $err = ($html === '__CREDIT_ERROR__') ? 'SIN_CREDITOS' : 'Error generando';
            echo json_encode(['ok' => false, 'error' => $err]); exit;
        }

        // Limpiar markdown fences si Claude los agrega
        $html = preg_replace('/^```html\s*/i', '', trim($html));
        $html = preg_replace('/^```\s*/i', '', $html);
        $html = preg_replace('/```\s*$/i', '', $html);
        $html = trim($html);

        // Validar completitud — asegurar que el HTML cierra correctamente
        if (stripos($html, '</html>') === false) {
            if (stripos($html, '</body>') !== false) {
                $html .= '</html>';
            } else {
                $html .= '</div></section></main></body></html>';
            }
        }

        // Garantizar lang="es" en <html>
        if (preg_match('/<html(?![^>]*lang=)/i', $html)) {
            $html = preg_replace('/<html/i', '<html lang="es"', $html, 1);
        }

        // Garantizar meta charset
        if (stripos($html, 'charset') === false) {
            $html = str_ireplace('<head>', '<head><meta charset="UTF-8">', $html);
        }

        // Garantizar meta viewport
        if (stripos($html, 'viewport') === false) {
            $html = str_ireplace('<head>', '<head><meta name="viewport" content="width=device-width, initial-scale=1">', $html);
        }

        // Garantizar preconnect Google Fonts
        if (stripos($html, 'fonts.googleapis.com') !== false && stripos($html, 'preconnect') === false) {
            $gc = '<link rel="preconnect" href="https://fonts.googleapis.com">' . "\n"
                . '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
            $html = str_ireplace('<link', $gc . "\n<link", $html, 1);
        }

        // Garantizar que el footer de DIMARK esté presente
        $dimark_credit = '<div style="text-align:center;padding:10px;background:rgba(0,0,0,0.25);font-size:11px;opacity:0.6">Propuesta creada con <a href="https://dimark.co" target="_blank" style="color:inherit;font-weight:700;text-decoration:none">Super Cerebro by DIMARK</a></div>';
        if (stripos($html, 'dimark.co') === false) {
            $html = str_ireplace('</body>', $dimark_credit . '</body>', $html);
        }

        // Log calidad por propuesta
        $qc = [
            'title'      => (int)(stripos($html, '<title') !== false),
            'meta_desc'  => (int)(stripos($html, 'name="description"') !== false),
            'og_title'   => (int)(stripos($html, 'og:title') !== false),
            'schema'     => (int)(stripos($html, 'application/ld+json') !== false),
            'preconnect' => (int)(stripos($html, 'preconnect') !== false),
            'whatsapp'   => (int)(stripos($html, 'wa.me') !== false),
            'canonical'  => (int)(stripos($html, 'canonical') !== false),
            'html_len'   => strlen($html),
        ];
        file_put_contents($log_dir . 'quality.log',
            date('Y-m-d H:i:s') . " | id:{$id} style:{$style} score:" . array_sum(array_slice($qc, 0, 7)) . "/7 | " . json_encode($qc) . "\n",
            FILE_APPEND);


        $col      = "proposal_{$style}";
        $name_col = "proposal_{$style}_name";
        $pdo->prepare("UPDATE dm_proposals SET $col=?, $name_col=?, status='ready' WHERE id=?")
            ->execute([$html, $s['name'], $id]);

        echo json_encode(['ok' => true, 'style' => $style, 'name' => $s['name'], 'html' => $html]);
        break;

    case 'send':
        $id = (int)($input['id'] ?? 0);
        if (!$id) { echo json_encode(['ok' => false, 'error' => 'id requerido']); exit; }
        $stmt = $pdo->prepare("SELECT token,project_name,client_id FROM dm_proposals WHERE id=?");
        $stmt->execute([$id]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }
        dm_require_client_access((int)$p['client_id']);
        $pdo->prepare("UPDATE dm_proposals SET status='sent' WHERE id=?")->execute([$id]);
        $link = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . '/supercerebro/propuesta.php?t=' . $p['token'];
        echo json_encode(['ok' => true, 'link' => $link]);
        break;

    // ── ENVIAR PROPUESTA POR EMAIL AL CLIENTE ─────────────────────────────────
    case 'send_email':
        $id = (int)($input['id'] ?? 0);
        if (!$id) { echo json_encode(['ok' => false, 'error' => 'id requerido']); exit; }
        $stmt = $pdo->prepare("SELECT p.token, p.project_name, p.client_id, p.status, c.email AS client_email, c.name AS client_name FROM dm_proposals p JOIN dm_clients c ON c.id=p.client_id WHERE p.id=?");
        $stmt->execute([$id]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }
        dm_require_client_access((int)$p['client_id']);
        $client_email = trim($p['client_email'] ?? '');
        if (!$client_email || !filter_var($client_email, FILTER_VALIDATE_EMAIL)) {
            echo json_encode(['ok' => false, 'error' => 'El cliente no tiene email registrado. Agrégalo en la ficha del cliente.']); exit;
        }
        $link = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . '/supercerebro/propuesta.php?t=' . $p['token'];
        $project = htmlspecialchars($p['project_name']);
        $cname   = htmlspecialchars($p['client_name']);
        $html_email = "<!DOCTYPE html><html><body style='font-family:Arial,sans-serif;max-width:600px;margin:40px auto;color:#1a1a1a'>
<h2 style='color:#1a1a1a'>Propuesta de Diseño Web — {$project}</h2>
<p>Hola {$cname},</p>
<p>Te hemos preparado <strong>3 propuestas de diseño web</strong> para revisar. Haz clic en el botón para verlas y seleccionar la que más te gusta:</p>
<div style='text-align:center;margin:32px 0'>
  <a href='{$link}' style='display:inline-block;padding:14px 32px;background:#e8272a;color:#fff;text-decoration:none;border-radius:8px;font-weight:700;font-size:16px'>Ver propuestas →</a>
</div>
<p style='font-size:12px;color:#888'>O copia este enlace: {$link}</p>
<hr style='border:none;border-top:1px solid #eee;margin:24px 0'>
<p style='font-size:12px;color:#888'>SuperCerebro by DIMARK · Medellín, Colombia</p>
</body></html>";
        $to      = $client_email;
        $subject = "Tu propuesta de diseño web está lista — {$project}";
        $headers = implode("\r\n", [
            'MIME-Version: 1.0',
            'Content-Type: text/html; charset=UTF-8',
            'From: SuperCerebro <no-reply@dimark.co>',
            'X-Mailer: PHP/' . PHP_VERSION,
        ]);
        $enc_subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
        $sent = @mail($to, $enc_subject, $html_email, $headers);
        if ($sent) {
            $pdo->prepare("UPDATE dm_proposals SET status='sent' WHERE id=? AND status NOT IN ('voted','final')")->execute([$id]);
            dm_activity((int)$p['client_id'], 'proposal_email', "Propuesta '{$project}' enviada por email a {$client_email}");
            echo json_encode(['ok' => true, 'message' => "Email enviado a {$client_email}", 'link' => $link]);
        } else {
            echo json_encode(['ok' => false, 'error' => 'No se pudo enviar el email. Verifica la configuración del servidor de correo.', 'link' => $link]);
        }
        break;

    // ── PUBLICAR PROPUESTA APROBADA EN WORDPRESS ──────────────────────────────
    case 'publish_wp':
        $id = (int)($input['id'] ?? 0);
        if (!$id) { echo json_encode(['ok' => false, 'error' => 'id requerido']); exit; }
        $stmt = $pdo->prepare("SELECT proposal_final, project_name, client_id, status FROM dm_proposals WHERE id=?");
        $stmt->execute([$id]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }
        dm_require_client_access((int)$p['client_id']);
        if (empty($p['proposal_final'])) {
            echo json_encode(['ok' => false, 'error' => "La propuesta no tiene versión final. Genera la versión final primero en el editor."]); exit;
        }
        // Delegar a wp-publish.php lógica: reutilizamos el endpoint directamente desde JS
        echo json_encode(['ok' => true, 'html' => $p['proposal_final'], 'title' => $p['project_name'], 'client_id' => $p['client_id']]);
        break;

    case 'vote':
        $token = $input['token'] ?? '';
        $vote  = (int)($input['vote'] ?? 0);
        $notes = trim($input['comments'] ?? '');
        if (!$token || !$vote || !in_array($vote, [1,2,3], true)) {
            echo json_encode(['ok' => false, 'error' => 'Faltan datos o voto inválido (debe ser 1, 2 o 3)']); exit;
        }
        // FASE 2 FIX: transacción ACID — el UPDATE condicional `client_vote IS NULL` previene
        // doble voto en clicks rápidos consecutivos. Si ya votó, devolvemos su voto previo.
        try {
            $pdo->beginTransaction();
            $stmt = $pdo->prepare("SELECT id,project_name,client_comments,client_vote FROM dm_proposals WHERE token=? FOR UPDATE");
            $stmt->execute([$token]);
            $prop = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$prop) {
                $pdo->rollBack();
                echo json_encode(['ok' => false, 'error' => 'Token invalido']); exit;
            }
            if (!empty($prop['client_vote']) && (int)$prop['client_vote'] > 0) {
                $pdo->rollBack();
                echo json_encode(['ok' => false, 'error' => 'Ya votaste la Propuesta '.(int)$prop['client_vote'].'. Si quieres cambiar, contacta al equipo.', 'already_voted' => (int)$prop['client_vote']]); exit;
            }
            $comments = json_decode($prop['client_comments'] ?? '[]', true) ?: [];
            if (!empty($notes)) {
                $comments[] = ['section' => 'Voto final', 'comment' => $notes, 'style' => $vote, 'date' => date('Y-m-d H:i:s')];
            }
            $upd = $pdo->prepare("UPDATE dm_proposals SET client_vote=?,client_comments=?,status='voted' WHERE token=? AND (client_vote IS NULL OR client_vote=0)");
            $upd->execute([$vote, json_encode($comments), $token]);
            if ($upd->rowCount() === 0) {
                $pdo->rollBack();
                echo json_encode(['ok' => false, 'error' => 'Voto ya registrado por otra petición simultánea']); exit;
            }
            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            echo json_encode(['ok' => false, 'error' => 'Error guardando voto']); exit;
        }

        // Notificación HTML al equipo
        $base_url  = ((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http').'://'.($_SERVER['HTTP_HOST']??'dimark.co').'/supercerebro/index.php';
        $notes_row = $notes ? '<tr><td style="padding:6px 12px;color:#666;width:140px">Notas cliente</td><td style="padding:6px 12px">'.nl2br(htmlspecialchars($notes)).'</td></tr>' : '';
        dm_notify_email(
            "🗳️ {$prop['project_name']} — cliente eligió Propuesta {$vote}",
            '<div style="font-family:sans-serif;max-width:580px;margin:0 auto;background:#f9f9f9;padding:24px;border-radius:8px">'
            .'<h2 style="color:#E8272A;margin:0 0 20px">🗳️ Voto de cliente recibido</h2>'
            .'<table style="width:100%;border-collapse:collapse;background:#fff;border-radius:6px;overflow:hidden">'
            .'<tr style="background:#E8272A"><td colspan="2" style="padding:10px 12px;color:#fff;font-weight:bold">Detalle de la elección</td></tr>'
            .'<tr><td style="padding:6px 12px;color:#666;width:140px">Proyecto</td><td style="padding:6px 12px;font-weight:bold">'.htmlspecialchars($prop['project_name']).'</td></tr>'
            .'<tr style="background:#fafafa"><td style="padding:6px 12px;color:#666">Propuesta elegida</td><td style="padding:6px 12px;font-weight:bold;color:#E8272A">Propuesta '.$vote.'</td></tr>'
            .$notes_row
            .'<tr><td style="padding:6px 12px;color:#666">Fecha</td><td style="padding:6px 12px">'.date('d/m/Y H:i').'</td></tr>'
            .'</table>'
            .'<p style="margin:20px 0 0"><a href="'.$base_url.'" style="background:#E8272A;color:#fff;padding:10px 22px;text-decoration:none;border-radius:4px;font-weight:bold">Ver en SuperCerebro →</a></p>'
            .'</div>'
        );
        echo json_encode(['ok' => true]);
        break;

    case 'vote_comment':
        $token   = $input['token'] ?? '';
        $section = trim($input['section'] ?? 'General');
        $comment = trim($input['comment'] ?? '');
        if (!$token || !$comment) { echo json_encode(['ok' => false, 'error' => 'Faltan datos']); exit; }
        $stmt = $pdo->prepare("SELECT id,client_comments FROM dm_proposals WHERE token=?");
        $stmt->execute([$token]);
        $prop = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$prop) { echo json_encode(['ok' => false, 'error' => 'Token invalido']); exit; }
        $style_val = (int)($input['style'] ?? 0);
        $replace   = !empty($input['replace']); // true = reemplazar comentarios del mismo style (evita duplicados en 2ª guardada)
        $comments  = json_decode($prop['client_comments'] ?? '[]', true) ?: [];
        if ($replace) {
            // Eliminar entradas anteriores con el mismo style y section para que el nuevo feedback reemplace al viejo
            $comments = array_values(array_filter($comments, fn($c) => !((int)($c['style']??-1) === $style_val && ($c['section']??'') === $section)));
        }
        $comments[] = ['section' => $section, 'comment' => $comment, 'style' => $style_val, 'date' => date('Y-m-d H:i:s')];
        $pdo->prepare("UPDATE dm_proposals SET client_comments=? WHERE id=?")->execute([json_encode($comments), $prop['id']]);

        // Notificar al equipo cuando el cliente envía el feedback estructurado (replace=true = guardada definitiva)
        if ($replace) {
            $proj_row = $pdo->prepare("SELECT project_name FROM dm_proposals WHERE id=?");
            $proj_row->execute([$prop['id']]);
            $pname = $proj_row->fetchColumn() ?: 'Propuesta #' . $prop['id'];
            $base_url = ((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http').'://'.($_SERVER['HTTP_HOST']??'dimark.co').'/supercerebro/index.php';
            dm_notify_email(
                "💬 {$pname} — cliente envió feedback (Propuesta {$style_val})",
                '<div style="font-family:sans-serif;max-width:580px;margin:0 auto;background:#f9f9f9;padding:24px;border-radius:8px">'
                .'<h2 style="color:#E8272A;margin:0 0 20px">💬 Feedback de cliente recibido</h2>'
                .'<table style="width:100%;border-collapse:collapse;background:#fff;border-radius:6px;overflow:hidden">'
                .'<tr style="background:#E8272A"><td colspan="2" style="padding:10px 12px;color:#fff;font-weight:bold">Detalle del feedback</td></tr>'
                .'<tr><td style="padding:6px 12px;color:#666;width:140px">Proyecto</td><td style="padding:6px 12px;font-weight:bold">'.htmlspecialchars($pname).'</td></tr>'
                .'<tr style="background:#fafafa"><td style="padding:6px 12px;color:#666">Propuesta</td><td style="padding:6px 12px;font-weight:bold;color:#E8272A">Propuesta '.$style_val.'</td></tr>'
                .'<tr><td style="padding:6px 12px;color:#666">Sección</td><td style="padding:6px 12px">'.htmlspecialchars($section).'</td></tr>'
                .'<tr style="background:#fafafa"><td style="padding:6px 12px;color:#666">Feedback</td><td style="padding:6px 12px">'.nl2br(htmlspecialchars($comment)).'</td></tr>'
                .'<tr><td style="padding:6px 12px;color:#666">Fecha</td><td style="padding:6px 12px">'.date('d/m/Y H:i').'</td></tr>'
                .'</table>'
                .'<p style="margin:20px 0 0"><a href="'.$base_url.'" style="background:#E8272A;color:#fff;padding:10px 22px;text-decoration:none;border-radius:4px;font-weight:bold">Revisar en SuperCerebro →</a></p>'
                .'</div>'
            );
        }
        echo json_encode(['ok' => true]);
        break;

    case 'get_comments_public':
        $token = $_GET['token'] ?? '';
        if (!$token) { echo json_encode(['ok' => false, 'error' => 'Token invalido']); exit; }
        $stmt = $pdo->prepare("SELECT client_comments,client_vote FROM dm_proposals WHERE token=?");
        $stmt->execute([$token]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }
        $comments = json_decode($row['client_comments'] ?? '[]', true) ?: [];
        echo json_encode(['ok' => true, 'comments' => $comments, 'vote' => $row['client_vote']]);
        break;

    case 'public':
        $token = $_GET['token'] ?? '';
        if (!$token) { echo json_encode(['ok' => false, 'error' => 'Token invalido']); exit; }
        $stmt = $pdo->prepare("SELECT id,project_name,proposal_1,proposal_2,proposal_3,proposal_1_name,proposal_2_name,proposal_3_name,client_vote,client_comments,status,regen_count_1,regen_count_final,proposal_final_name FROM dm_proposals WHERE token=?");
        $stmt->execute([$token]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok' => false, 'error' => 'No encontrada']); exit; }
        // Verificar si existe versión final sin traer el HTML completo (puede ser 100KB+)
        $has_final_row = $pdo->prepare("SELECT 1 FROM dm_proposals WHERE token=? AND proposal_final IS NOT NULL AND proposal_final != '' LIMIT 1");
        $has_final_row->execute([$token]);
        echo json_encode([
            'ok'               => true,
            'proposal'         => $p,
            'regen_1_left'     => max(0, 3 - (int)$p['regen_count_1']),
            'regen_final_left' => max(0, 3 - (int)$p['regen_count_final']),
            'has_final'        => (bool)$has_final_row->fetch(),
        ]);
        break;

    case 'save_html':
        $id    = (int)($input['id'] ?? 0);
        $style = (int)($input['style'] ?? 0);
        $html  = $input['html'] ?? '';
        if (!$id || !$style || empty($html)) { echo json_encode(['ok' => false, 'error' => 'Faltan datos']); exit; }
        if (!in_array($style, [1, 2, 3])) { echo json_encode(['ok' => false, 'error' => 'Estilo inválido']); exit; }
        $sh_chk = $pdo->prepare("SELECT client_id FROM dm_proposals WHERE id=?");
        $sh_chk->execute([$id]);
        $sh_row = $sh_chk->fetch(PDO::FETCH_ASSOC);
        if ($sh_row) dm_require_client_access((int)$sh_row['client_id']);
        $col = "proposal_{$style}";
        $pdo->prepare("UPDATE dm_proposals SET $col=?,updated_at=NOW() WHERE id=?")->execute([$html, $id]);
        echo json_encode(['ok' => true]);
        break;

    case 'delete':
        $id = (int)($input['id'] ?? 0);
        if (!$id) { echo json_encode(['ok' => false, 'error' => 'id requerido']); exit; }
        $del_user = dm_user();
        if ($del_user['role'] !== 'admin') {
            $own = $pdo->prepare("SELECT 1 FROM dm_proposals WHERE id=? AND created_by=?");
            $own->execute([$id, $del_user['id']]);
            if (!$own->fetch()) { echo json_encode(['ok' => false, 'error' => 'Sin permisos para eliminar esta propuesta']); exit; }
        }
        $pdo->prepare("DELETE FROM dm_proposals WHERE id=?")->execute([$id]);
        echo json_encode(['ok' => true]);
        break;

    // ── Incrementar contador regen etapa 1 (validación cliente) ────────────────
    case 'client_regen_inc':
        $tok = $input['token'] ?? $_GET['token'] ?? '';
        if (!$tok) { echo json_encode(['ok'=>false,'error'=>'Token requerido']); exit; }
        $stmt = $pdo->prepare("SELECT id,regen_count_1 FROM dm_proposals WHERE token=?");
        $stmt->execute([$tok]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) { echo json_encode(['ok'=>false,'error'=>'Token inválido']); exit; }
        if ((int)$row['regen_count_1'] >= 3) {
            echo json_encode(['ok'=>false,'error'=>'Límite de 3 regeneraciones alcanzado','regen_left'=>0]); exit;
        }
        $pdo->prepare("UPDATE dm_proposals SET regen_count_1=regen_count_1+1 WHERE token=?")->execute([$tok]);
        echo json_encode(['ok'=>true,'id'=>(int)$row['id'],'regen_left'=> 3 - ((int)$row['regen_count_1'] + 1)]);
        break;

    // ── Obtener versión final guardada ──────────────────────────────────────────
    case 'client_get_final':
        $tok = $_GET['token'] ?? $input['token'] ?? '';
        if (!$tok) { echo json_encode(['ok'=>false,'error'=>'Token requerido']); exit; }
        $stmt = $pdo->prepare("SELECT proposal_final,proposal_final_name,regen_count_final FROM dm_proposals WHERE token=?");
        $stmt->execute([$tok]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) { echo json_encode(['ok'=>false,'error'=>'Token inválido']); exit; }
        echo json_encode([
            'ok'        => true,
            'html'      => $row['proposal_final'] ?? '',
            'name'      => $row['proposal_final_name'] ?? 'Versión Final',
            'regen_left'=> max(0, 3 - (int)$row['regen_count_final']),
        ]);
        break;

    // ── Generar versión final (síntesis de todo el feedback del cliente) ────────
    case 'client_gen_final':
        $tok = $input['token'] ?? '';
        if (!$tok) { echo json_encode(['ok'=>false,'error'=>'Token requerido']); exit; }
        $stmt = $pdo->prepare("SELECT * FROM dm_proposals WHERE token=?");
        $stmt->execute([$tok]);
        $p = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$p) { echo json_encode(['ok'=>false,'error'=>'Token inválido']); exit; }
        if ((int)$p['regen_count_final'] >= 3) {
            echo json_encode(['ok'=>false,'error'=>'Límite de 3 versiones finales alcanzado','regen_left'=>0]); exit;
        }

        // Brand assets del cliente como fallback
        $cbf = [];
        if (!empty($p['client_id'])) {
            $cb2 = $pdo->prepare("SELECT brand_logo,brand_colors,brand_fonts,brand_description FROM dm_clients WHERE id=? LIMIT 1");
            $cb2->execute([$p['client_id']]);
            $cbf = $cb2->fetch(PDO::FETCH_ASSOC) ?: [];
        }
        if (empty($p['brand_colors']) && !empty($cbf['brand_colors'])) $p['brand_colors'] = $cbf['brand_colors'];
        if (empty($p['brand_fonts'])  && !empty($cbf['brand_fonts']))  $p['brand_fonts']  = $cbf['brand_fonts'];
        if (empty($p['logo_path'])    && !empty($cbf['brand_logo']))   $p['logo_path']    = $cbf['brand_logo'];

        // Parsear feedback de comentarios
        $all_coms = json_decode($p['client_comments'] ?? '[]', true) ?: [];
        $prop_names = [1=>($p['proposal_1_name']??'Propuesta 1'),2=>($p['proposal_2_name']??'Propuesta 2'),3=>($p['proposal_3_name']??'Propuesta 3')];
        $fb_by_prop = [1=>[],2=>[],3=>[],'general'=>[]];
        foreach ($all_coms as $c) {
            $sty  = (int)($c['style'] ?? 0);
            $text = trim($c['comment'] ?? '');
            if (!$text) continue;
            $sec = $c['section'] ?? 'General';
            if ($sty >= 1 && $sty <= 3) $fb_by_prop[$sty][] = "[{$sec}]: {$text}";
            else                         $fb_by_prop['general'][] = $text;
        }
        $fb_text = '';
        for ($fi = 1; $fi <= 3; $fi++) {
            if (!empty($fb_by_prop[$fi]))
                $fb_text .= "\nDe {$prop_names[$fi]}:\n" . implode("\n", $fb_by_prop[$fi]);
        }
        if (!empty($fb_by_prop['general']))
            $fb_text .= "\nInstrucciones generales:\n" . implode("\n", $fb_by_prop['general']);
        if (empty(trim($fb_text))) $fb_text = 'Sin feedback específico — genera la mejor versión posible del brief.';

        // Colores
        $cols = array_values(array_filter(array_map('trim', explode(',', $p['brand_colors'] ?? ''))));
        $fc1 = $cols[0] ?? '#1a1a2e'; $fc2 = $cols[1] ?? '#E8272A'; $fc3 = $cols[2] ?? '#ffffff';

        // Logo
        $flogo_url = '';
        if (!empty($p['logo_path'])) {
            $fproto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
            $flogo_url = $fproto . '://' . ($_SERVER['HTTP_HOST'] ?? 'dimark.co') . '/supercerebro/' . $p['logo_path'];
        }
        $flogo_html = $flogo_url
            ? '<img src="' . $flogo_url . '" alt="' . htmlspecialchars($p['project_name']) . '" style="height:48px;object-fit:contain;max-width:180px;display:block">'
            : '<span style="font-size:22px;font-weight:900;letter-spacing:-1px">' . htmlspecialchars($p['project_name']) . '</span>';

        $ffont = !empty($p['brand_fonts']) ? "Tipografía de marca: {$p['brand_fonts']}" : 'Usa Google Fonts premium (Inter, Plus Jakarta Sans, Syne, DM Sans).';
        $fvoted = max(1, min(3, (int)($p['client_vote'] ?? 1)));

        $final_prompt = <<<PROMPT
Eres el mejor desarrollador web del mundo. Genera una landing page completa en HTML/CSS/JS puro.

Esta es la VERSIÓN FINAL — el cliente ya revisó 3 propuestas y dejó feedback detallado.
Debes ejecutar exactamente lo que el cliente pidió.

DATOS DEL CLIENTE:
Proyecto: {$p['project_name']}
Brief: {$p['brief']}
Notas del brief: {$p['extra_notes']}
Colores de marca: {$p['brand_colors']}
{$ffont}
Propuesta favorita del cliente: Propuesta {$fvoted} ({$prop_names[$fvoted]})

FEEDBACK EXACTO DEL CLIENTE (implementa cada punto):
{$fb_text}

INSTRUCCIONES TÉCNICAS OBLIGATORIAS:
1. Una sola página HTML completa, self-contained, sin dependencias externas excepto Google Fonts
2. Logo: usar {$flogo_html}
3. Colores principales del sitio: primario={$fc1}, acento={$fc2}, fondo={$fc3}
4. Mobile-first, responsive con breakpoints 768px y 1024px
5. Animaciones suaves (CSS transitions, intersection observer para scroll reveals)
6. CTAs claros y prominentes con hover effects
7. CERO Lorem Ipsum — todo el contenido real del brief
8. Formulario de contacto con validación JS básica
9. WhatsApp flotante (bottom-right, #25D366) si hay número en el brief
10. El feedback del cliente es SAGRADO — implementa cada instrucción al pie de la letra

Devuelve ÚNICAMENTE el código HTML completo empezando con <!DOCTYPE html>. Sin explicaciones, sin markdown.
PROMPT;

        $final_html = call_claude_html($final_prompt);
        if (empty($final_html)) { echo json_encode(['ok'=>false,'error'=>'Error generando versión final. Intenta de nuevo.']); exit; }

        // Limpiar respuesta si Claude agregó texto antes del DOCTYPE
        if (strpos($final_html, '<!DOCTYPE') !== false) {
            $final_html = substr($final_html, strpos($final_html, '<!DOCTYPE'));
        }

        $pdo->prepare("UPDATE dm_proposals SET proposal_final=?,regen_count_final=regen_count_final+1,status='final' WHERE token=?")
            ->execute([$final_html, $tok]);

        // Notificar al equipo: el cliente generó la versión final
        try {
            $regen_num = (int)$p['regen_count_final'] + 1;
            $pdo->prepare(
                "INSERT INTO dm_activity (user_id, client_id, ref_id, action, detail, ip) VALUES (0,?,?,?,?,?)"
            )->execute([
                (int)$p['client_id'],
                (int)$p['id'],
                'proposal_final_generated',
                'Proyecto: ' . $p['project_name'] . ' · Refinamiento #' . $regen_num,
                $_SERVER['REMOTE_ADDR'] ?? '',
            ]);
        } catch (Exception $e) { /* no bloquear si dm_activity no tiene ref_id aún */ }

        echo json_encode([
            'ok'        => true,
            'html'      => $final_html,
            'regen_left'=> max(0, 3 - ((int)$p['regen_count_final'] + 1)),
        ]);
        break;

    // ── Log de visita del cliente ─────────────────────────────────────────────
    case 'log_visit':
        $tok = $input['token'] ?? '';
        if ($tok) {
            $vis = $pdo->prepare("SELECT id,client_id,project_name,status FROM dm_proposals WHERE token=? LIMIT 1");
            $vis->execute([$tok]);
            $vp = $vis->fetch(PDO::FETCH_ASSOC);
            if ($vp) {
                try {
                    $pdo->prepare(
                        "INSERT INTO dm_activity (user_id, client_id, ref_id, action, detail, ip) VALUES (0,?,?,?,?,?)"
                    )->execute([
                        (int)$vp['client_id'],
                        (int)$vp['id'],
                        'proposal_viewed_by_client',
                        'Proyecto: ' . $vp['project_name'],
                        $_SERVER['REMOTE_ADDR'] ?? '',
                    ]);
                } catch (Exception $e) { /* silencioso */ }
            }
        }
        echo json_encode(['ok' => true]);
        break;

    default:
        echo json_encode(['ok' => false, 'error' => 'Accion no reconocida: ' . $action]);
}