function onOpen() {
SpreadsheetApp.getUi()
.createMenu("🔍 SEO Técnico Máximo")
.addItem("Crear hoja de análisis", "crearHojaAnalisis")
.addItem("Procesar URLs en lote", "procesarTodasURLs")
.addToUi();
}
function crearHojaAnalisis() {
const ss = SpreadsheetApp.getActive();
let hoja = ss.getSheetByName("Análisis SEO Máximo");
if (!hoja) hoja = ss.insertSheet("Análisis SEO Máximo");
hoja.clear();
hoja.appendRow([
"URL",
"HTTP Code", "Title", "H1",
"Meta Description", "Canonical", "Noindex",
"Imgs Count", "Internal Links", "External Links"
]);
SpreadsheetApp.getUi().alert("🟢 Hoja 'Análisis SEO Máximo' lista.");
}
function procesarTodasURLs() {
const hoja = SpreadsheetApp.getActive().getSheetByName("Análisis SEO Máximo");
if (!hoja) return SpreadsheetApp.getUi().alert("Primero crea la hoja de análisis.");
const urls = hoja.getRange(2,1,hoja.getLastRow()-1).getValues().flat();
const resultados = urls.map(url => url && SEO_INFO(url));
resultados.forEach((fila, i) => hoja.getRange(i+2,2,1,fila.length).setValues([fila]));
SpreadsheetApp.getUi().alert("✅ Procesado completo.");
}
function SEO_INFO(url) {
try {
const html = fetchHTML(url);
const code = fetchCode(url);
const title = extract(html, /<title>(.*?)<\/title>/i);
const h1 = extract(html, /<h1[^>]*>(.*?)<\/h1>/i);
const metaDesc = extract(html, /<meta\s+name=["']description["']\s+content=["'](.*?)["']/i);
const canonical = extract(html, /<link\s+rel=["']canonical["']\s+href=["'](.*?)["']/i);
const noindex = /<meta\s+name=["']robots["']\s+content=["'][^"']*noindex[^"']*["']/i.test(html) ? "Sí" : "No";
const imgsCount = (html.match(/<img\s+[^>]*>/gi) || []).length;
const base = url.match(/https?:\/\/[^\/]+/)[0];
const internal = countLinks(html, base, true);
const external = countLinks(html, base, false);
return [code, title, h1, metaDesc, canonical, noindex, imgsCount, internal, external];
} catch (e) {
console.error("Error en SEO_INFO:", e);
return ["Error", "", "", "", "", "", "", "", ""];
}
}
function fetchHTML(url) {
return UrlFetchApp.fetch(url, { muteHttpExceptions: true }).getContentText();
}
function fetchCode(url) {
try {
return UrlFetchApp.fetch(url, { followRedirects: false, muteHttpExceptions: true }).getResponseCode();
} catch {
return "Error";
}
}
function extract(html, regex) {
const m = html.match(regex);
return m?.[1]?.trim() || "";
}
function countLinks(html, base, internal=true) {
const body = html.split(/<\/head>/i)[1]?.split(/<\/body>/i)[0] || "";
const regex = internal
? /href=["']((?!http)[^"']+|http[^"']*)["']/gi
: /href=["'](http[^"']+)["']/gi;
const matches = Array.from(body.matchAll(regex));
const unique = new Set(matches.map(m => {
const link = m[1].startsWith("http")
? m[1]
: (base + (m[1].startsWith("/") ? "" : "/") + m[1]);
return internal
? link.startsWith(base) && link
: !link.startsWith(base) && link;
}).filter(Boolean));
return unique.size;
}