/* global React */
const { useState, useEffect, useRef, useMemo } = React;
const fmtDate = s => {
if (!s) return "—";
const p = s.slice(0, 10).split("-");
return p.length === 3 ? `${p[2]}/${p[1]}/${p[0]}` : s.slice(0, 10);
};
/* ---------- Iconos minimalistas ---------- */
const Icon = ({ name, size = 18, stroke = 1.7 }) => {
const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" };
switch (name) {
case "arrow-right": return ;
case "arrow-down": return ;
case "code": return ;
case "cpu": return ;
case "refresh": return ;
case "download": return ;
case "upload": return ;
case "x": return ;
case "sun": return ;
case "moon": return ;
case "mail": return ;
case "phone": return ;
case "pin": return ;
case "check": return ;
case "lock": return ;
case "image": return ;
case "search": return ;
case "github": return ;
case "file-text": return ;
case "images": return ;
case "grid": return ;
case "list": return ;
default: return null;
}
};
/* ---------- Reveal hook ---------- */
const useReveal = (trigger) => {
useEffect(() => {
const els = document.querySelectorAll(".reveal:not(.is-in)");
if (!els.length) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add("is-in"); obs.unobserve(e.target); }});
}, { threshold: 0.12 });
els.forEach(el => obs.observe(el));
return () => obs.disconnect();
}, [trigger]);
};
/* ---------- Datos ---------- */
const SERVICES = [
{
n: "01",
icon: "code",
title: "Desarrollo a medida",
desc: "Aplicaciones web, móviles y de escritorio diseñadas exactamente para tu operativa, sin compromisos.",
items: ["Web apps & PWA", "Backends & APIs", "Apps móviles Android/iOS", "Integraciones ERP/CRM"]
},
{
n: "02",
icon: "cpu",
title: "Automatización con IA",
desc: "Agentes y flujos inteligentes que clasifican, redactan, procesan documentos y conversan con tus clientes.",
items: ["Agentes conversacionales", "Procesado de documentos", "Workflows con LLMs", "Análisis predictivo"]
},
{
n: "03",
icon: "refresh",
title: "Modernización de sistemas",
desc: "Migramos software heredado a stacks modernos, mantenibles y conectados, sin parar tu negocio.",
items: ["Migración legacy", "Refactor & cloud", "Auditoría técnica", "Soporte continuo"]
}
];
const PROCESS = [
{ n: "01", title: "Descubrimiento", desc: "Reunión inicial. Entendemos tu proceso, identificamos oportunidades y delimitamos el alcance." },
{ n: "02", title: "Propuesta", desc: "Documento técnico, plazos y presupuesto cerrado. Sin sorpresas." },
{ n: "03", title: "Construcción", desc: "Sprints quincenales con demos. Tú validas, nosotros iteramos." },
{ n: "04", title: "Despliegue", desc: "Puesta en producción, formación a tu equipo y soporte continuo." }
];
// La lista se carga desde /api/list.php — los datos de demo solo se muestran si la API
// no responde (entorno de previsualización). En tu hosting Arsys verás los datos reales.
const DOWNLOADS_FALLBACK = [
{ id: 1, title: "MASoft Backup Tool", description: "Utilidad de copias de seguridad incrementales con cifrado AES-256 y programación automática.",
category: "free", lang: "powershell", platform: "Windows", version: "v2.4.1", size_label: "12.4 MB", created_at: "2026-04-22", tags: ["PowerShell","Windows","CLI"], has_file: true, screenshot_url: null },
{ id: 2, title: "Facturas IA Lite", description: "Extrae datos de facturas PDF/imagen y los exporta a Excel o CSV. Ideal para gestorías.",
category: "trial", lang: "python", platform: "Windows / Mac", version: "v1.2.0 · 30 días", size_label: "84 MB", created_at: "2026-04-10", tags: ["Python","OCR","IA"], has_file: true, screenshot_url: null },
{ id: 3, title: "ServerPulse", description: "Dashboard ligero para monitorizar servidores Windows y servicios críticos en tiempo real.",
category: "demo", lang: "pwa", platform: "Web · PWA", version: "Demo pública", size_label: "—", created_at: "2026-03-28", tags: ["PWA","JavaScript","Web"], has_file: true, screenshot_url: null },
{ id: 4, title: "Control Presencia", description: "App Android para fichaje con geolocalización y sincronización offline. Versión gratuita 1 usuario.",
category: "free", lang: "android", platform: "Android 8+", version: "v3.0.2", size_label: "9.8 MB", created_at: "2026-04-18", tags: ["Android","Kotlin"], has_file: true, screenshot_url: null },
{ id: 5, title: "StockSync Pro", description: "Sincronizador de inventarios entre tienda física y e-commerce (PrestaShop, WooCommerce).",
category: "trial", lang: "python", platform: "Windows / Linux", version: "v0.9.4 · Beta", size_label: "36 MB", created_at: "2026-04-02", tags: ["Python","API","ETL"], has_file: true, screenshot_url: null },
{ id: 6, title: "Helpdesk IA Demo", description: "Demo interactiva de asistente IA conectado a base de conocimiento corporativa.",
category: "demo", lang: "pwa", platform: "Web", version: "Demo · sin instalación", size_label: "—", created_at: "2026-03-15", tags: ["PWA","RAG","IA"], has_file: true, screenshot_url: null }
];
const CATEGORIES = [
{ id: "all", label: "Todos" },
{ id: "free", label: "Gratuito" },
{ id: "trial", label: "Prueba" },
{ id: "demo", label: "Demo" }
];
const LANG_FILTERS = [
{ id: "all", label: "Todos" },
{ id: "python", label: "Python" },
{ id: "powershell", label: "PowerShell" },
{ id: "pwa", label: "PWA" },
{ id: "android", label: "Android" }
];
/* ---------- Hero ---------- */
const HeroCode = () => (
~/masoft/agents/invoice_pipeline.py
1 # Pipeline IA — facturas → ERP
2 from masoft import Agent , OCR
3
4 agent = Agent (model="claude-haiku-4.5" )
5
6 async def procesar (pdf):
7 {"\u00a0\u00a0"}data = await OCR .extract(pdf)
8 {"\u00a0\u00a0"}return await agent.map_to ("sap" , data)
9
10 # → 12.400 facturas/mes · 0 errores
);
const HeroBento = () => (
/01 — Automatización
−68%
tiempo en tareas repetitivas
/02 — Stack
Python · Node · React · Kotlin
/03 — Sectores
Industrial · Retail · Servicios
/04 — Equipo
Ingenieros senior con +10 años desarrollando software a medida en España.
);
const Hero = ({ variant, onCtaScroll }) => {
if (variant === "minimal") {
return (
Disponibles · Q2 2026
Software a medida . Hecho con cabeza.
Desarrollamos aplicaciones e integramos IA en el día a día de tu empresa. Sin plantillas, sin tonterías.
onCtaScroll("contacto")}>Empezar un proyecto
onCtaScroll("descargas")}>Ver descargas
);
}
return (
Disponibles · Q2 2026
Software a medida y automatización con IA.
Construimos las herramientas que tu empresa necesita —y que ningún software estándar resuelve. Desarrollo, modernización e IA aplicada de verdad.
onCtaScroll("contacto")}>Empezar un proyecto
onCtaScroll("descargas")}> Software gratuito
+85 Proyectos entregados
Expertos en el sector IT
24/7 Soporte clientes
{variant === "bento" ? : }
);
};
/* ---------- Servicios ---------- */
const Services = () => (
Servicios
Tres áreas, un único objetivo.
Tres líneas de trabajo y un único objetivo: que tu empresa funcione mejor con software hecho a su medida.
{SERVICES.map(s => (
{s.n} / Servicio
{s.title}
{s.desc}
))}
Stack:
Python ·
TypeScript ·
React / Next.js ·
FastAPI ·
PostgreSQL ·
Docker ·
Kotlin ·
PowerShell ·
Azure / AWS ·
Claude · GPT · Llama
);
/* ---------- Proceso ---------- */
const Process = () => (
Proceso
De la primera reunión a producción en semanas, no meses.
Un método sencillo, transparente y con entregas frecuentes. Sin sorpresas en la factura ni en el calendario.
{PROCESS.map(p => (
{p.n}
Fase {p.n}
{p.title}
{p.desc}
))}
);
/* ---------- Modal galería de capturas ---------- */
const ScreenshotsModal = ({ open, item, onClose }) => {
if (!item) return null;
const allShots = [];
if (item.screenshot_url) allShots.push(item.screenshot_url);
if (item.screenshots_extra_urls) allShots.push(...item.screenshots_extra_urls);
return (
e.stopPropagation()} style={{maxWidth: 720, maxHeight: '85vh', overflowY: 'auto'}}>
Capturas
{item.title}
{allShots.map((url, i) => (
))}
{allShots.length === 0 &&
No hay capturas disponibles.
}
Cerrar
);
};
/* ---------- Descargas ---------- */
const ScreenshotPlaceholder = ({ label }) => (
<>
{label}
>
);
const Downloads = ({ items, onSubscribe, onDownload }) => {
const [cat, setCat] = useState("all");
const [lang, setLang] = useState("all");
const [view, setView] = useState("grid");
const [shotsOpen, setShotsOpen] = useState(false);
const [shotsItem, setShotsItem] = useState(null);
const counts = useMemo(() => ({
all: items.length,
free: items.filter(i => i.category === "free").length,
trial: items.filter(i => i.category === "trial").length,
demo: items.filter(i => i.category === "demo").length,
}), [items]);
const langCounts = useMemo(() => {
const c = { all: items.length };
items.forEach(i => { c[i.lang] = (c[i.lang] || 0) + 1; });
return c;
}, [items]);
const filtered = items.filter(i =>
(cat === "all" || i.category === cat) &&
(lang === "all" || i.lang === lang)
);
const catLabel = c => c === "free" ? "Gratis" : c === "trial" ? "Prueba" : "Demo";
const catClass = c => c === "free" ? "" : c === "trial" ? "is-trial" : "is-demo";
return (
Descargas
Software gratuito, demos y pruebas.
Una colección de utilidades y demos que hemos liberado para nuestros clientes y la comunidad. Descarga gratis tras un breve registro.
{CATEGORIES.map(c => (
setCat(c.id)}>
{c.label} {counts[c.id]}
))}
{LANG_FILTERS.map(l => (
setLang(l.id)} style={{fontFamily: "var(--mono)", fontSize: 12}}>
{l.label} {langCounts[l.id] || 0}
))}
setView("grid")} title="Vista fichas">
setView("list")} title="Vista lista">
{filtered.map(item => (
{catLabel(item.category)}
{item.screenshot_url
?
:
}
{view === "list" ? (
{item.title}
{item.description}
{item.size_label && item.size_label !== "—" ? item.size_label + " · " : ""}
{fmtDate(item.created_at)}
{item.platform ? " · " + item.platform : ""}
{item.version ? " · " + item.version : ""}
{(item.tags || []).map(t => {t} )}
{item.manual_url && (
)}
{item.has_file
?
onDownload(item)}> Descargar
:
Sin archivo
}
) : (
{item.title}
{item.platform || "—"} · {item.version || "—"}
{item.description}
{(item.tags || []).map(t => (
{t}
))}
{item.size_label || "—"} · {fmtDate(item.created_at)}
{item.manual_url && (
)}
{((item.screenshot_url) || (item.screenshots_extra_urls && item.screenshots_extra_urls.length > 0)) && (
{ setShotsItem(item); setShotsOpen(true); }}>
)}
{item.has_file
?
onDownload(item)}> Descargar
:
Sin archivo
}
)}
))}
{filtered.length === 0 && (
No hay software en esta categoría todavía.
)}
¿Quieres estar al día?
Recibe un aviso cuando publiquemos nuevo software o actualizaciones. Sin spam.
Suscribirme
setShotsOpen(false)}/>
);
};
/* ---------- Caja privacidad (reutilizable) ---------- */
const PrivacyBox = ({ email }) => {
const addr = email || "hola@masoft.es";
return (
Información básica sobre protección de datos (RGPD)
Responsable: MASoft Sistemas Informáticos — {addr}
Finalidad: Gestionar tu suscripción y enviarte comunicaciones sobre nuevos productos, actualizaciones y herramientas publicados en MASoft.es.
Base jurídica: Consentimiento expreso del interesado (art. 6.1.a del Reglamento UE 2016/679 — RGPD).
Conservación: Hasta que solicites la baja. Puedes cancelar tu suscripción en cualquier momento mediante el enlace incluido en cada comunicación o escribiendo a la dirección indicada.
Destinatarios: No se ceden datos a terceros salvo obligación legal. Los datos se tratan en servidores dentro de la UE.
Derechos: Puedes ejercer tus derechos de acceso, rectificación, supresión, oposición, limitación y portabilidad escribiendo a {addr} . Si consideras que el tratamiento no es conforme, puedes reclamar ante la Agencia Española de Protección de Datos (aepd.es) .
);
};
/* ---------- Modal Descarga ---------- */
const DownloadModal = ({ open, item, onClose, adminEmail }) => {
const [accept, setAccept] = useState(false);
const [showTerms, setShowTerms] = useState(false);
useEffect(() => {
if (open) {
setAccept(false);
setShowTerms(false);
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => { document.body.style.overflow = ""; };
}, [open, item]);
if (!item) return null;
return (
e.stopPropagation()}>
Descarga gratuita
{item.title}
{item.size_label && item.size_label !== "—" ? `${item.size_label} · ` : ""}{item.platform || ""}
Aviso legal y limitación de responsabilidad
Este software se distribuye tal cual , sin garantías de ningún tipo, expresas o implícitas. MASoft Sistemas Informáticos no se hace responsable de daños directos, indirectos o consecuentes derivados de su uso.
El usuario es el único responsable de verificar la compatibilidad con su sistema y de realizar las copias de seguridad pertinentes antes de la instalación.
setAccept(e.target.checked)}/>
He leído el aviso legal y acepto la{" "}
setShowTerms(v => !v)} style={{background:"none", border:"none", padding:0, color:"var(--accent)", textDecoration:"underline", cursor:"pointer", fontSize:"inherit", fontFamily:"inherit"}}>
política de privacidad
.
{showTerms &&
}
e.preventDefault()}
>
Descargar ahora
);
};
/* ---------- Modal Suscripción ---------- */
const SubscribeModal = ({ open, onClose, adminEmail }) => {
const [form, setForm] = useState({ name: "", email: "", company: "", accept: false });
const [done, setDone] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [showTerms, setShowTerms] = useState(false);
useEffect(() => {
if (open) {
setDone(false); setError(null); setShowTerms(false);
setForm({ name: "", email: "", company: "", accept: false });
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => { document.body.style.overflow = ""; };
}, [open]);
const submit = async (e) => {
e.preventDefault();
if (!form.email || !form.accept) return;
setLoading(true); setError(null);
try {
const res = await fetch("api/subscribe.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form)
});
const data = await res.json();
if (data.ok) setDone(true);
else setError(data.error === "email_invalid" ? "Email no válido" : "Error al suscribirse");
} catch { setError("No se ha podido conectar con el servidor"); }
setLoading(false);
};
return (
e.stopPropagation()}>
{!done ? (
<>
Mantenme informado
Recibe nuestras novedades
Te avisaremos cuando publiquemos nuevo software, actualizaciones y herramientas gratuitas. Sin spam, puedes darte de baja cuando quieras.
>
) : (
¡Ya estás en la lista!
Te avisaremos cuando publiquemos novedades.
Cerrar
)}
);
};
/* ---------- Contacto ---------- */
const Contact = ({ contactInfo }) => {
const [sent, setSent] = useState(false);
const [form, setForm] = useState({ name: "", email: "", company: "", message: "", topic: "Desarrollo a medida" });
const submit = async (e) => {
e.preventDefault();
try {
const res = await fetch("api/contact.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form)
});
const data = await res.json();
if (data.ok) setSent(true);
} catch {}
};
const email = contactInfo.contact_email || "hola@masoft.es";
const phone = contactInfo.contact_phone || "+34 600 123 456";
const location = contactInfo.contact_location || "Madrid · España";
return (
);
};
/* ---------- Footer ---------- */
const Footer = ({ contactInfo }) => {
const email = contactInfo.contact_email || "hola@masoft.es";
const phone = contactInfo.contact_phone || "+34 600 123 456";
const phoneTel = phone.replace(/\s+/g, "");
return (
);
};
/* ---------- Navbar ---------- */
const Navbar = ({ theme, onToggleTheme, onScroll }) => (
);
/* ---------- App ---------- */
const App = () => {
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"theme": "light",
"accent": "#3D7BFF",
"heroVariant": "code",
"fontDisplay": "Manrope"
}/*EDITMODE-END*/;
const [t, setTweak] = window.useTweaks ? window.useTweaks(TWEAK_DEFAULTS) : [TWEAK_DEFAULTS, () => {}];
const [downloads, setDownloads] = useState([]);
const [subOpen, setSubOpen] = useState(false);
const [dlOpen, setDlOpen] = useState(false);
const [dlItem, setDlItem] = useState(null);
const [contactInfo, setContactInfo] = useState({});
useReveal(downloads.length);
// Carga inicial desde la API
useEffect(() => {
fetch("api/list.php")
.then(r => r.json())
.then(d => { if (d && d.items) setDownloads(d.items); else setDownloads(DOWNLOADS_FALLBACK); })
.catch(() => setDownloads(DOWNLOADS_FALLBACK));
fetch("api/settings.php?action=get_public")
.then(r => r.json())
.then(d => { if (d.ok && d.settings) setContactInfo(d.settings); })
.catch(() => {});
}, []);
// Aplica tema + acento + tipografía
useEffect(() => {
document.documentElement.setAttribute("data-theme", t.theme || "light");
document.documentElement.style.setProperty("--accent", t.accent);
document.documentElement.style.setProperty("--accent-soft", `color-mix(in oklab, ${t.accent} 14%, transparent)`);
document.documentElement.style.setProperty("--display", `"${t.fontDisplay}", system-ui, sans-serif`);
}, [t]);
const onScroll = (id) => {
if (id === "top") { window.scrollTo({ top: 0, behavior: "smooth" }); return; }
const el = document.getElementById(id);
if (el) window.scrollTo({ top: el.offsetTop - 70, behavior: "smooth" });
};
const TweaksPanel = window.TweaksPanel;
const TweakSection = window.TweakSection;
const TweakRadio = window.TweakRadio;
const TweakColor = window.TweakColor;
const TweakSelect = window.TweakSelect;
return (
<>
setTweak("theme", t.theme === "dark" ? "light" : "dark")} onScroll={onScroll}/>
setSubOpen(true)} onDownload={item => { setDlItem(item); setDlOpen(true); }}/>
setDlOpen(false)} adminEmail={contactInfo.contact_email || "hola@masoft.es"}/>
setSubOpen(false)} adminEmail={contactInfo.contact_email || "hola@masoft.es"}/>
{TweaksPanel && (
setTweak("theme", v)} options={[
{ value: "light", label: "Claro" },
{ value: "dark", label: "Oscuro" }
]}/>
setTweak("accent", v)} options={["#3D7BFF", "#0066FF", "#00C2FF", "#7C3AED", "#10B981"]}/>
setTweak("fontDisplay", v)} options={[
{ value: "Manrope", label: "Manrope" },
{ value: "Inter", label: "Inter" },
{ value: "Space Grotesk", label: "Space Grotesk" },
{ value: "Geist", label: "Geist" },
{ value: "DM Sans", label: "DM Sans" }
]}/>
setTweak("heroVariant", v)} options={[
{ value: "code", label: "Código" },
{ value: "bento", label: "Bento" },
{ value: "minimal", label: "Mínimo" }
]}/>
)}
>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( );