Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 72 additions & 10 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
</head>
<body>

<div id="previewBar" class="hidden" style="background:#b45309;color:#111;text-align:center;padding:8px 34px 8px 12px;font-size:13px;position:relative">
<span>Esto es una vista previa y puede tener fallos. Si quieres la versión estable entra al modo normal: </span>
<a href="https://inteebuild.onrender.com" style="color:#111;font-weight:700">inteebuild.onrender.com</a>
<button type="button" id="previewBarClose" aria-label="Cerrar aviso" style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:15px;color:#111;line-height:1">&times;</button>
</div>

<header class="topbar">
<div class="brand">
<span class="logo"><img src="./assets/icon.svg" alt="IB" width="22" height="22" /></span>
Expand Down Expand Up @@ -155,15 +161,30 @@ <h2>Preguntas frecuentes</h2>
<div class="ad-container ad-banner" data-ad="banner"></div>

<div id="alertConfig" class="alert hidden"></div>
<div id="draftBar" class="alert hidden">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
<span>Se encontró un borrador de tu configuración sin compilar.</span>
<span style="display:flex;gap:8px;flex-shrink:0">
<button type="button" class="btn primary sm" id="draftRestore">Recuperar borrador</button>
<button type="button" class="btn ghost sm" id="draftDiscard">Descartar</button>
</span>
</div>
</div>
<div id="draftBar" class="alert hidden">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
<span>Se encontró un borrador de tu configuración sin compilar.</span>
<span style="display:flex;gap:8px;flex-shrink:0">
<button type="button" class="btn primary sm" id="draftRestore">Recuperar borrador</button>
<button type="button" class="btn ghost sm" id="draftDiscard">Descartar</button>
</span>
</div>
</div>

<div class="card" style="margin-top:14px">
<div class="card-title">
<svg class="card-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Mis proyectos
</div>
<p class="hint">Guarda la configuración con nombre para retomarla otro día. Queda en este navegador, sin cuenta ni registro. También puedes exportarla como archivo o importar una.</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<input type="text" id="projName" placeholder="Nombre del proyecto (ej. Radio del barrio)" style="flex:1;min-width:200px" maxlength="40" />
<button type="button" class="btn primary sm" id="projSaveBtn">Guardar</button>
<button type="button" class="btn ghost sm" id="projExportBtn">Exportar JSON</button>
<label class="btn ghost sm" style="margin:0;cursor:pointer">Importar JSON<input type="file" id="projImportInput" accept="application/json,.json" style="display:none" /></label>
</div>
<div id="projList" style="margin-top:10px"></div>
</div>

<div class="layout">
<div class="wizard" id="wizard">
Expand Down Expand Up @@ -324,6 +345,13 @@ <h2>Preguntas frecuentes</h2>
</div>
<img id="iconPreview" class="icon-preview hidden" alt="preview" />
</label>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;padding:10px 0 0">
<label style="flex:1;min-width:80px;margin:0">O genera uno rápido
<input type="text" id="monoIconText" maxlength="3" placeholder="AB" style="text-transform:uppercase" />
</label>
<label style="margin:0">Color <input type="color" id="monoIconColor" value="#4f46e5" /></label>
<button type="button" class="btn ghost sm" id="monoIconBtn">Generar ícono</button>
</div>
<label class="switch">
<input type="checkbox" name="adaptiveIconEnabled" id="adaptiveCheck" />
<span class="trk"><span class="knob"></span></span>
Expand Down Expand Up @@ -361,6 +389,7 @@ <h2>Preguntas frecuentes</h2>
<span style="font-size:12px">Subir imagen (PNG/JPG/WebP, máx 500KB)</span>
</div>
</label>
<button type="button" class="btn ghost sm" id="monoSplashBtn" style="margin-bottom:8px">Generar splash con el monograma (usa tu ícono y color)</button>
<div class="row">
<label>Animación
<select name="splashAnimation">
Expand All @@ -378,6 +407,16 @@ <h2>Preguntas frecuentes</h2>
</div>
</div>

<div class="card" style="margin-top:14px">
<div class="card-title">
<svg class="card-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l1-5h16l1 5"/><path d="M3 9v11a1 1 0 001 1h16a1 1 0 001-1V9"/><path d="M9 21v-6h6v6"/></svg>
Ficha de Play Store
</div>
<p class="hint">Así se vería tu app en la tienda con los datos que ya cargaste. No publica nada, solo es una vista previa.</p>
<button type="button" class="btn ghost sm" id="playPreviewBtn">Ver cómo se vería</button>
<div id="playPreviewBox" class="hidden" style="margin-top:10px"></div>
</div>

<div class="card">
<div class="card-title">
<svg class="card-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
Expand Down Expand Up @@ -611,6 +650,18 @@ <h2>Preguntas frecuentes</h2>
<div id="buildReadinessBox"></div>
</div>

<div class="card engine-card" style="margin-top:14px">
<div class="card-title">
<svg class="card-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21s-6-4.35-6-10a6 6 0 0 1 12 0c0 5.65-6 10-6 10z"/><circle cx="12" cy="11" r="2"/></svg>
Pre-Build Doctor <span class="pro-badge">PREVIEW</span>
</div>
<p class="hint">Chequea config, permisos, HTML/URL y seguridad antes de compilar. Semáforo: OK, aviso o error por punto.</p>
<div class="engine-actions">
<button type="button" class="btn primary sm" id="doctorBtn"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21s-6-4.35-6-10a6 6 0 0 1 12 0c0 5.65-6 10-6 10z"/><circle cx="12" cy="11" r="2"/></svg> Diagnosticar pre-build</button>
</div>
<div id="doctorBox" class="audit-list"></div>
</div>

<div class="card" style="margin-top:14px">
<div class="card-title">
<svg class="card-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/></svg>
Expand Down Expand Up @@ -1306,6 +1357,12 @@ <h2>Preguntas frecuentes</h2>
<button type="button" class="btn ghost sm" id="showApkInfoBtn">Detalles del APK</button>
<button type="button" class="btn ghost sm" id="downloadZipBtn">Código Fuente (.zip)</button>
</div>
<div id="reviewBanner" class="hidden" style="margin-top:10px;padding:11px 13px;border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:13px">
<span style="flex:1;min-width:220px">¿Te funcionó? Déjanos una estrella en GitHub y una reseña en AlternativeTo, de verdad ayuda a que más gente lo encuentre.</span>
<a class="btn ghost sm" href="https://github.com/Fluxionics/InteeBuild" target="_blank" rel="noopener" data-review-dismiss><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg> Estrella en GitHub</a>
<a class="btn ghost sm" href="https://alternativeto.net/software/inteebuild/about/" target="_blank" rel="noopener" data-review-dismiss>Reseña en AlternativeTo</a>
<button type="button" class="btn ghost sm" id="reviewClose" style="opacity:.7">No, gracias</button>
</div>
<div id="apkInfoBox" class="apk-info hidden">
<hr class="section-divider" />
<div class="apk-info-grid" id="apkInfoGrid"></div>
Expand Down Expand Up @@ -1334,7 +1391,10 @@ <h2>Historial de Compilaciones</h2>
</div>
</div>
<div class="card">
<h2>Registros Recientes</h2>
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap">
<h2>Registros Recientes <small id="histCount" style="font-weight:400;color:var(--muted);font-size:12px"></small></h2>
<button type="button" class="btn ghost sm" id="histClearAll">Borrar todo</button>
</div>
<div class="hist-tools">
<input type="text" id="histSearch" placeholder="Buscar por nombre o ID…" />
<select id="histStatus">
Expand Down Expand Up @@ -1502,7 +1562,9 @@ <h3 style="margin-bottom:10px">Términos de Uso y Políticas</h3>
<script src="./js/icons.js"></script>
<script src="./js/modules/util.js"></script>
<script src="./js/modules/form.js"></script>
<script src="./js/modules/projects.js"></script>
<script src="./js/modules/permissions.js"></script>
<script src="./js/modules/doctor.js"></script>
<script src="./js/modules/plugins.js"></script>
<script src="./js/modules/preview.js"></script>
<script src="./js/modules/analyze.js"></script>
Expand Down
11 changes: 11 additions & 0 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,14 @@ if (dupId) Build.duplicateBuild(dupId);

refreshHealthBadge();
Build.loadStats();

const previewBar = $('#previewBar');
if (previewBar && location.hostname !== 'inteebuild.onrender.com') {
let previewBarHidden = false;
try { previewBarHidden = !!localStorage.getItem('ib:previewbar'); } catch (_) {}
if (!previewBarHidden) show(previewBar);
on('#previewBarClose', 'click', () => {
hide(previewBar);
try { localStorage.setItem('ib:previewbar', '1'); } catch (_) {}
});
}
41 changes: 39 additions & 2 deletions js/modules/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,19 @@ const Build = (() => {
on('#zipBtn', 'click', () => downloadZip());
on('#downloadZipBtn', 'click', () => downloadZip());

const REVIEW_KEY = 'ib_review_dismissed';
const dismissReview = () => {
try { localStorage.setItem(REVIEW_KEY, '1'); } catch (_) {}
hide($('#reviewBanner'));
};
const maybeShowReviewBanner = () => {
let dismissed = false;
try { dismissed = !!localStorage.getItem(REVIEW_KEY); } catch (_) {}
if (!dismissed) show($('#reviewBanner'));
};
on('#reviewClose', 'click', dismissReview);
onAll('[data-review-dismiss]', 'click', dismissReview);

on('#toggleConsole', 'click', (e) => {
buildConsole.classList.toggle('hidden');
e.currentTarget.textContent = buildConsole.classList.contains('hidden') ? 'Ver registros en vivo' : 'Ocultar registros';
Expand Down Expand Up @@ -431,6 +444,7 @@ const Build = (() => {
label: 'Descargar ' + FORMAT_LABELS[fmt]
}));
});
maybeShowReviewBanner();
}, 600);
} else if (s.status === 'failed' || s.status === 'error') {
closeEventSource();
Expand Down Expand Up @@ -537,6 +551,9 @@ const Build = (() => {
return;
}

const countEl = $('#histCount');
if (countEl) countEl.textContent = items.length + (items.length === 1 ? ' registro' : ' registros');

historyList.innerHTML = items
.map((h) => {
const statusClass = h.status === 'success' ? 'ok' : h.status === 'failed' || h.status === 'error' ? 'fail' : 'pending';
Expand All @@ -551,17 +568,23 @@ const Build = (() => {
if (h.runUrl) {
buttons.push('<a class="btn ghost sm" href="' + h.runUrl + '" target="_blank" rel="noopener">Logs</a>');
}
buttons.push('<button type="button" class="btn ghost sm" data-dup="' + h.id + '">Duplicar</button>');
buttons.push('<button type="button" class="btn ghost sm" data-dup="' + h.id + '">Cargar</button>');
buttons.push('<button type="button" class="btn ghost sm" data-exp="' + h.id + '">JSON</button>');
buttons.push('<button type="button" class="btn ghost sm" data-del="' + h.id + '">Eliminar</button>');
buttons.push('<button type="button" class="btn ghost sm" data-del="' + h.id + '">Borrar</button>');
const dur = typeof h.duration === 'number' && h.duration > 0
? '<span>' + (h.duration >= 60 ? Math.floor(h.duration / 60) + 'm ' + (h.duration % 60) + 's' : h.duration + 's') + '</span>'
: '';
const fmts = (h.outputs && h.outputs.length ? h.outputs : [h.outputType].filter(Boolean)).map((f) => '<span style="opacity:.75">' + String(f).toUpperCase() + '</span>').join(' ');
return (
'<div class="hitem">' +
'<div class="hitem-left">' +
'<div class="hitem-name">' + escHtml(h.appName || 'Aplicación') + '</div>' +
'<div class="hitem-meta">' +
'<span class="hitem-status ' + statusClass + '">' + statusText + '</span>' +
'<span>' + timeAgo(h.createdAt) + '</span>' +
dur +
'<span>ID: ' + escHtml(h.id) + '</span>' +
fmts +
'</div>' +
'</div>' +
'<div class="hitem-right">' + buttons.join('') + '</div>' +
Expand Down Expand Up @@ -605,6 +628,20 @@ const Build = (() => {
}
};

const clearHistory = async () => {
if (!confirm('¿Borrar todo el historial? Esta acción no se puede deshacer.')) return;
try {
const res = await fetch('/api/history', { method: 'DELETE' });
if (!res.ok) throw new Error('No se pudo borrar el historial');
historyCache = [];
renderHistory();
loadStats();
} catch (err) {
alert(err.message);
}
};
on('#histClearAll', 'click', clearHistory);

const loadHistory = async () => {
try {
const res = await fetch('/api/history');
Expand Down
33 changes: 33 additions & 0 deletions js/modules/doctor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

const Doctor = (() => {
const run = async () => {
const box = $('#doctorBox');
if (!box) return;
box.innerHTML = '<small style="color:var(--muted)">Diagnosticando antes de compilar…</small>';
try {
const r = await fetch('/api/doctor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Form.collect())
});
const j = await r.json();
if (!r.ok) throw new Error(j.error || 'El Doctor falló');
const ico = { ok: 'check', warn: 'alert', error: 'x' };
const cls = { ok: 'ok', warn: 'warn', error: 'fail' };
const rows = j.checks.map((c) =>
`<div class="audit-item ${cls[c.status] || 'warn'}"><span class="audit-ico">${IBIcon(ico[c.status] || 'alert')}</span><div class="audit-main"><b>${escHtml(c.label)}</b>${c.detail ? `<div class="audit-meta"><span class="audit-chip">${escHtml(c.detail)}</span></div>` : ''}</div><span class="audit-badge ${cls[c.status] || 'warn'}">${c.status === 'error' ? 'error' : c.status}</span></div>`
).join('');
const barColor = j.canBuild ? (j.warns ? 'var(--warn)' : 'var(--success)') : 'var(--danger)';
box.innerHTML = rows +
`<div class="audit-summary ${j.canBuild ? 'good' : 'bad'}"><b>Score ${j.score}/100</b> · ${j.checks.length} chequeos · ${escHtml(j.summary)}</div>` +
`<div class="readiness-bar" style="margin-top:6px"><div class="readiness-fill" style="width:${j.score}%;background:${barColor}"></div></div>`;
} catch (e) {
box.innerHTML = '<span style="color:var(--danger)">' + escHtml(e.message) + '</span>';
}
};

on('#doctorBtn', 'click', run);

return { run };
})();
Loading
Loading