App Builder Pro
{{ user.displayName.split(' ')[0] }}

Crea apps con solo texto

Describe tu idea y la convertimos en una app real al instante.

Describe tu app

Plantillas rápidas

Tu app aparecerá aquí

{{ genStepText }}

{{ step }}

{{ genProgress }}%

{{ currentAppName || 'App generada' }}

Mis aplicaciones

Inicia sesión para ver tus apps guardadas

Aún no has creado ninguna app

{{ (a.name||'A')[0] }}
{{ a.published ? 'Publicada' : 'Borrador' }}

{{ a.name || 'Sin nombre' }}

{{ a.description.substring(0,80) }}...

{{ formatDate(a.createdAt) }}

{{ t.msg }}
'; } function detectType(desc) { const d = desc.toLowerCase(); if (d.includes('tarea') || d.includes('task') || d.includes('to-do') || d.includes('todo') || d.includes('lista')) return 'task'; if (d.includes('clima') || d.includes('weather') || d.includes('temperatura') || d.includes('tiempo')) return 'weather'; if (d.includes('dashboard') || d.includes('analítica') || d.includes('analytics') || d.includes('métrica') || d.includes('estadística')) return 'dashboard'; if (d.includes('tienda') || d.includes('shop') || d.includes('producto') || d.includes('e-commerce') || d.includes('carrito') || d.includes('venta')) return 'shop'; if (d.includes('portfolio') || d.includes('portafolio') || d.includes('personal') || d.includes('curriculum')) return 'portfolio'; if (d.includes('música') || d.includes('music') || d.includes('reproductor') || d.includes('player') || d.includes('canción')) return 'music'; if (d.includes('finanza') || d.includes('gasto') || d.includes('presupuesto') || d.includes('budget') || d.includes('dinero') || d.includes('ingreso') || d.includes('balance')) return 'finance'; if (d.includes('nota') || d.includes('note') || d.includes('post-it') || d.includes('memo')) return 'notes'; return 'task'; } // Generation const generateApp = async () => { if (!description.value.trim() || generating.value) return; generating.value = true; genStep.value = 0; genProgress.value = 0; for (let i = 0; i < genSteps.length; i++) { genStep.value = i; genStepText.value = genSteps[i]; genProgress.value = Math.round(((i + 1) / genSteps.length) * 90); await new Promise(r => setTimeout(r, 400)); } const type = detectType(description.value); const name = appName.value.trim() || 'Mi App'; const html = APP_TEMPLATES[type](name); currentHtml.value = html; currentAppName.value = name; currentAppDesc.value = description.value; genProgress.value = 100; genStepText.value = '¡Lista!'; await new Promise(r => setTimeout(r, 300)); generating.value = false; toast('¡App generada!', 'success'); // Auto-save if logged in if (user.value) { const id = await saveApp({ name, description: description.value, html, type, published: false }); if (id) { await loadSavedApps(); toast('App guardada', 'success'); } } }; const regenerate = async () => { description.value = currentAppDesc.value; await generateApp(); }; const downloadApp = () => { if (!currentHtml.value) return; const blob = new Blob([currentHtml.value], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = (currentAppName.value || 'app').toLowerCase().replace(/\s+/g, '-') + '.html'; a.click(); URL.revokeObjectURL(url); toast('App descargada', 'success'); }; const publishApp = async () => { if (!currentHtml.value || publishing.value) return; publishing.value = true; const slug = 'app-' + Date.now(); try { const res = await fetch('https://anyclaw.store/api/deploy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app_id: slug, zip_b64: '', app_type: 'web_app', update_existing: true, site_map: [], skip_auto: true }) }); // Build ZIP from HTML const zipBuf = await buildZip(currentHtml.value); const b64 = btoa(String.fromCharCode(...new Uint8Array(zipBuf))); const res2 = await fetch('https://anyclaw.store/api/deploy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app_id: slug, zip_b64: b64, app_type: 'web_app', update_existing: true, site_map: [], skip_auto: true }) }); const data = await res2.json(); if (data.url) { toast('¡App publicada!', 'success'); if (user.value) { const app = savedApps.value.find(a => a.name === currentAppName.value && a.description === currentAppDesc.value); if (app) await updateApp(app.id, { published: true, publishUrl: data.url, publishInstallUrl: data.install_url }); await loadSavedApps(); } } } catch (e) { toast('Error al publicar: ' + e.message, 'error'); } publishing.value = false; }; async function buildZip(htmlContent) { // Minimal ZIP implementation const files = [ { name: 'index.html', content: htmlContent }, { name: 'app.json', content: JSON.stringify({ title: currentAppName.value || 'App', description: currentAppDesc.value || '', category: 'web_app', icon: 'icon.svg' }) } ]; let centralDir = [], offset = 0; const parts = []; // ZIP local file header + data for (const file of files) { const content = new TextEncoder().encode(file.content); const nameBytes = new TextEncoder().encode(file.name); const crc = crc32(content); const header = new ArrayBuffer(30); const view = new DataView(header); view.setUint32(0, 0x04034b50, true); view.setUint16(4, 20, true); view.setUint16(6, 0, true); view.setUint16(8, 0, true); view.setUint16(10, 0, true); view.setUint32(14, crc, true); view.setUint32(18, content.length, true); view.setUint32(22, content.length, true); view.setUint16(26, nameBytes.length, true); view.setUint16(28, 0, true); parts.push(header, nameBytes, content); centralDir.push({ header, nameBytes, content, crc, offset, len: content.length }); offset += 30 + nameBytes.length + content.length; } // Central directory let cdOffset = 0; const cdParts = []; for (const entry of centralDir) { const header = new ArrayBuffer(46); const view = new DataView(header); view.setUint32(0, 0x02014b50, true); view.setUint16(4, 20, true); view.setUint16(6, 20, true); view.setUint16(8, 0, true); view.setUint16(10, 0, true); view.setUint16(12, 0, true); view.setUint16(14, 0, true); view.setUint32(16, entry.crc, true); view.setUint32(20, entry.len, true); view.setUint32(24, entry.len, true); view.setUint16(28, entry.nameBytes.length, true); view.setUint16(30, 0, true); view.setUint16(32, 0, true); view.setUint16(34, 0, true); view.setUint16(36, 0, true); view.setUint32(38, 0, true); view.setUint32(42, entry.offset, true); cdParts.push(header, entry.nameBytes); cdOffset += 46 + entry.nameBytes.length; } // End of central directory const end = new ArrayBuffer(22); const ev = new DataView(end); ev.setUint32(0, 0x06054b50, true); ev.setUint16(4, 0, true); ev.setUint16(6, 0, true); ev.setUint16(8, files.length, true); ev.setUint16(10, files.length, true); ev.setUint32(12, cdOffset, true); ev.setUint32(16, offset, true); ev.setUint16(20, 0, true); // Combine all parts const totalSize = offset + cdOffset + 22; const result = new Uint8Array(totalSize); let pos = 0; for (const part of parts) { result.set(new Uint8Array(part), pos); pos += part.byteLength; } for (const part of cdParts) { result.set(new Uint8Array(part), pos); pos += part.byteLength; } result.set(new Uint8Array(end), pos); return result.buffer; } function crc32(buf) { let c = 0xffffffff; const table = []; for (let i = 0; i < 256; i++) { let cc = i; for (let j = 0; j < 8; j++) cc = cc & 1 ? (cc >>> 1) ^ 0xEDB88320 : cc >>> 1; table[i] = cc; } for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; } const windowOpen = (url) => window.open(url, '_blank'); return { user, view, appName, description, generating, publishing, currentHtml, currentAppName, currentAppDesc, genStep, genProgress, genStepText, genSteps, savedApps, toasts, templates, windowObj, useTemplate, generateApp, regenerate, downloadApp, publishApp, loginWithGoogle, logout, loadApp, deleteApp, formatDate, windowOpen }; } }).mount('#app');