/* global React, ReactDOM */ const { useState, useEffect } = React; // Componentes y utilidades compartidas (definidos en common.jsx). const { Icon, useReveal, Navbar, Footer, useSiteTweaks, SiteTweaks, SubscribeModal, Galeria } = window; // Software gratis: la lista sale de api/list.php (tabla softwares). Cada ficha // trae en details las características, los requisitos y las capturas con // título. Los programas no tienen enlace directo: al aceptar el aviso legal se // pide un enlace temporal a api/sw_token.php (como MADeCa Mini), que cuenta la // descarga. El manual se abre desde api/sw_manual.php, que también lo cuenta. const SW_ERRORES = { demasiadas: "Se han pedido demasiadas descargas desde su conexión. Inténtelo dentro de una hora.", no_disponible: "La descarga no está disponible en este momento. Inténtelo más tarde.", robot: "Su navegador no permite esta descarga. Pruebe con otro navegador." }; const conV = v => (v ? (/^v/i.test(v) ? v : "v" + v) : ""); // Capturas de la ficha: las de details (con título y descripción) o, en fichas // antiguas, la principal y las extra sin texto. const capturasDe = item => { const det = item.details && Array.isArray(item.details.capturas) ? item.details.capturas : []; if (det.length) return det.filter(c => c && c.src); const urls = [item.screenshot_url, ...(item.screenshots_extra_urls || [])].filter(Boolean); return urls.map((src, i) => ({ src, t: `Captura ${i + 1}`, d: "" })); }; /* ---------- Descarga: aviso legal + enlace temporal ---------- */ const SwDescarga = ({ item, onClose }) => { const [accept, setAccept] = useState(false); const [preparando, setPreparando] = useState(false); const [error, setError] = useState(null); const [iniciada, setIniciada] = useState(false); const open = !!item; useEffect(() => { if (!open) return undefined; setAccept(false); setError(null); setIniciada(false); document.body.style.overflow = "hidden"; const esc = e => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", esc); return () => { window.removeEventListener("keydown", esc); document.body.style.overflow = ""; }; }, [item]); const descargar = async () => { if (!accept || preparando) return; setPreparando(true); setError(null); setIniciada(false); try { const r = await fetch("api/sw_token.php", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ slug: item.slug, acepto: true }) }); const d = await r.json().catch(() => ({})); if (!r.ok || !d.ok || !d.url) throw new Error(d.error || "servidor"); window.location.href = d.url; setIniciada(true); } catch (e) { setError(SW_ERRORES[e.message] || "No se ha podido iniciar la descarga. Inténtelo de nuevo en unos minutos."); } finally { setPreparando(false); } }; return (
{item && (
e.stopPropagation()}>  Descarga gratuita

{item.title} {conV(item.version)}

{[item.size_label, item.platform].filter(Boolean).join(" · ")}

Aviso legal

{item.title} es gratuito y se entrega tal cual, sin garantía de ningún tipo. Antes de descargarlo, lea el aviso legal y las limitaciones de su manual {item.manual_url ? <> (abrir el aviso legal). : "."}

Para contar las descargas se guarda la dirección IP y el país aproximado.

{error &&

{error}

} {iniciada &&

La descarga ha comenzado. El enlace es personal y caduca en una hora.

} {item.sha256 && (

{item.file_name} · SHA-256: {item.sha256} {" · "}fichero .sha256 {" · "}análisis en VirusTotal

)}
)}
); }; // Análisis del programa en VirusTotal, buscado por su huella SHA-256. const vtUrl = sha => `https://www.virustotal.com/gui/file/${encodeURIComponent(sha)}`; // Programas que también se publican en GitHub (repositorio público con las descargas en Releases). const GITHUB = { maclipboard: "MAClipBoard", macolor: "MAColor", madisk: "MADisk", maprocess: "MAProcess", mascrshot: "MAScrShot", maserv: "MAServ", }; const githubUrl = item => GITHUB[item.slug] ? `https://github.com/MaSoft/${GITHUB[item.slug]}/releases/latest` : null; // Resumen de una línea para el índice: la descripción hasta los dos puntos o // el primer punto (todas empiezan diciendo qué hace el programa). const resumenDe = item => { const d = (item.description || "").trim(); const corte = d.search(/[:.]/); return corte > 0 ? d.slice(0, corte) : d; }; /* ---------- Índice: una tarjeta por programa ---------- */ const Indice = ({ items, onIr }) => ( ); /* ---------- Barra fija con los programas (marca en cuál estás) ---------- Misma subcabecera que las páginas de producto (MADeCa, MASys, MAPerit). */ const BarraProgramas = ({ items, activo, onIr }) => { const caja = React.useRef(null); // En pantallas estrechas la barra se desliza: se centra el programa activo. useEffect(() => { const nav = caja.current; const el = nav && nav.querySelector(".is-active"); if (!el || nav.scrollWidth <= nav.clientWidth) return; nav.scrollTo({ left: el.offsetLeft - (nav.clientWidth - el.offsetWidth) / 2, behavior: "smooth" }); }, [activo]); return (
); }; /* ---------- Sección de un programa ---------- */ const Programa = ({ item, num, total, onDownload }) => { const det = item.details || {}; const caracteristicas = Array.isArray(det.caracteristicas) ? det.caracteristicas : []; const requisitos = Array.isArray(det.requisitos) ? det.requisitos : []; const capturas = capturasDe(item); return (
{String(num).padStart(2, "0")} / {String(total).padStart(2, "0")}

{item.title} {conV(item.version)}

{[item.platform, item.size_label].filter(Boolean).join(" · ")}

{item.description}

{(item.tags || []).length > 0 && (
{item.tags.map(t => {t})}
)}
{item.has_file ? : Descarga no disponible} {item.manual_url && ( Manual )} {githubUrl(item) && ( GitHub )}
{capturas[0] && (
{/* Sin carga diferida: si se cargara al pasar por ella, empujaría la página al saltar a otro programa. */} {`${item.title}:
)}
{(caracteristicas.length > 0 || requisitos.length > 0) && (
{caracteristicas.length > 0 && (

Qué hace

    {caracteristicas.map((c, i) => (
  • {c}
  • ))}
)} {requisitos.length > 0 && (

Requisitos

{requisitos.map(([k, v], i) => (
{k || "—"}
{v}
))}
{det.nota &&

{det.nota}

}
)}
)} {capturas.length > 1 && (

Capturas

)} {item.sha256 && (

{item.file_name} · SHA-256: {item.sha256} {" · "}fichero .sha256 {" · "}análisis en VirusTotal

)}
); }; /* ---------- Página Software gratis ---------- */ const SoftwareGratisPage = () => { const [t, setTweak] = useSiteTweaks(); const [items, setItems] = useState(null); // null = cargando const [fallo, setFallo] = useState(false); const [subOpen, setSubOpen] = useState(false); const [dlItem, setDlItem] = useState(null); const [contactInfo, setContactInfo] = useState({}); const [activo, setActivo] = useState(null); useReveal(items ? items.length : 0); const cargar = () => { setFallo(false); setItems(null); fetch("api/list.php") .then(r => r.json()) .then(d => { if (d && d.ok && Array.isArray(d.items)) setItems(d.items); else throw new Error(); }) .catch(() => { setItems([]); setFallo(true); }); }; useEffect(() => { cargar(); fetch("api/settings.php?action=get_public") .then(r => r.json()) .then(d => { if (d.ok && d.settings) setContactInfo(d.settings); }) .catch(() => {}); }, []); // Programa activo en la barra: el último cuya sección ya ha pasado bajo la // barra fija. Ninguno mientras se está en la cabecera o en el índice. useEffect(() => { if (!items || !items.length) return undefined; const calcular = () => { let actual = null; document.querySelectorAll("[data-sg]").forEach(s => { if (s.getBoundingClientRect().top <= 160) actual = s.dataset.sg; }); setActivo(actual); }; calcular(); window.addEventListener("scroll", calcular, { passive: true }); window.addEventListener("resize", calcular); return () => { window.removeEventListener("scroll", calcular); window.removeEventListener("resize", calcular); }; }, [items]); // Al llegar con #programa en la dirección, ir a su sección cuando ya existe. useEffect(() => { const h = decodeURIComponent((window.location.hash || "").slice(1)); if (items && h && document.getElementById(h)) irA(h, "instant"); }, [items]); // 128 px = menú principal + barra de programas. Si al terminar el desplazamiento // algo de encima ha cambiado de alto (imágenes que acaban de cargar), se reajusta. const irA = (slug, behavior = "smooth") => { const el = document.getElementById(slug); if (!el) return; const ir = b => window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 128, behavior: b }); ir(behavior); [700, 1400].forEach(ms => setTimeout(() => { if (Math.abs(el.getBoundingClientRect().top - 128) > 4) ir("instant"); }, ms)); if (window.history.replaceState) window.history.replaceState(null, "", "#" + slug); }; const onScroll = (id) => { if (id === "top") { window.scrollTo({ top: 0, behavior: "smooth" }); return; } irA(id); }; const adminEmail = contactInfo.contact_email || "hola@masoft.es"; const lista = items || []; return ( <> setTweak("theme", t.theme === "dark" ? "light" : "dark")} onScroll={onScroll}/> {lista.length > 0 && }
Software gratis

Utilidades gratuitas para Windows.

Programas desarrollados por Masoft.es que usamos en nuestro trabajo diario. Gratis y sin registro: un solo archivo que se copia y se abre, con su manual.

{/* Suscripción arriba, a la vista nada más entrar (antes estaba al final de la página) */}
¿Quieres estar al día?

Recibe un aviso cuando publiquemos nuevo software o actualizaciones. Sin spam.

{items === null &&

Cargando el software…

} {fallo && (

No se ha podido cargar la lista de programas.{" "}

)} {items && !fallo && items.length === 0 &&

Todavía no hay software publicado.

} {lista.length > 0 && (
Importante: al abrir el programa por primera vez

Los programas no están firmados digitalmente, así que Windows puede mostrar el aviso «Windows protegió su PC» (SmartScreen). Para abrirlo, pulse «Más información» y después «Ejecutar de todas formas». Si quiere comprobar que el archivo es el original, compare su huella SHA-256 con la publicada en la ficha. En PowerShell: Get-FileHash .\nombre_del_archivo.exe

Para mayor seguridad, cada ficha enlaza al análisis del programa en{" "} VirusTotal, que lo revisa con decenas de antivirus. También puede subir allí el archivo descargado y comprobar que su huella coincide.

En VirusTotal puede ver uno o dos avisos de antivirus poco conocidos que marcan cualquier programa sin firma digital, por su forma de empaquetarlo y no por lo que hace. Ninguno de los antivirus de uso habitual (Microsoft Defender, ESET, Kaspersky, Bitdefender…) detecta nada. Si un antivirus importante lo marcara, retiraríamos la descarga hasta aclararlo.

)} {lista.length > 0 && }
{lista.map((item, i) => ( ))}