multiple changes about status of all news and fix of name files
This commit is contained in:
parent
95e71131be
commit
4c30a7892b
8
.env
8
.env
|
|
@ -6,16 +6,16 @@ GOOGLE_SHEET_ID=12X6qeU0W4ZDw00vS4OreyC4nFjKBeBYo7rq3wyRsrNQ
|
||||||
# Archivo de variables de entorno para la configuración de envío de emails
|
# Archivo de variables de entorno para la configuración de envío de emails
|
||||||
EMAIL_API_KEY=9UShpS8oh5Iun92TJSfevElI3Lp99TCv
|
EMAIL_API_KEY=9UShpS8oh5Iun92TJSfevElI3Lp99TCv
|
||||||
|
|
||||||
# This was inserted by `prisma init`:
|
# This was inserted by prisma init:
|
||||||
# Environment variables declared in this file are NOT automatically loaded by Prisma.
|
# Environment variables declared in this file are NOT automatically loaded by Prisma.
|
||||||
# Please add `import "dotenv/config";` to your `prisma.config.ts` file, or use the Prisma CLI with Bun
|
# Please add import "dotenv/config"; to your prisma.config.ts file, or use the Prisma CLI with Bun
|
||||||
# to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.
|
# to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.
|
||||||
|
|
||||||
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||||
|
|
||||||
# The following `prisma+postgres` URL is similar to the URL produced by running a local Prisma Postgres
|
# The following prisma+postgres URL is similar to the URL produced by running a local Prisma Postgres
|
||||||
# server with the `prisma dev` CLI command, when not choosing any non-default ports or settings. The API key, unlike the
|
# server with the prisma dev CLI command, when not choosing any non-default ports or settings. The API key, unlike the
|
||||||
# one found in a remote Prisma Postgres URL, does not contain any sensitive information.
|
# one found in a remote Prisma Postgres URL, does not contain any sensitive information.
|
||||||
|
|
||||||
DATABASE_URL="file:./dev.db"
|
DATABASE_URL="file:./dev.db"
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"position": [480, 300],
|
"position": [480, 300],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"language": "javaScript",
|
"language": "javaScript",
|
||||||
"code": "const langNames = {\n es: 'Español',\n en: 'English',\n fr: 'Français',\n pt: 'Português',\n rw: 'Kinyarwanda',\n he: 'עברית',\n uk: 'Українська',\n ru: 'Русский',\n kr: 'Kreyòl',\n};\n\nconst ORDER = ['es', 'en', 'fr', 'pt', 'rw', 'he', 'uk', 'ru', 'kr'];\nconst payload = $input.first().json;\n\nconst summaryRows = [];\nfor (const code of ORDER) {\n const s = payload.summary[code];\n if (!s) continue;\n summaryRows.push({\n language: langNames[code] || code,\n code,\n totalEs: payload.totals.spanishArticles,\n translated: s.translated,\n missing: s.missing,\n percent: s.percent,\n });\n}\n\nconst detailRows = [];\nfor (const art of payload.articles) {\n const row = {\n date: art.date,\n spanishTitle: art.spanishTitle,\n spanishFile: art.spanishFile,\n };\n for (const lang of ORDER) {\n row[lang] = art.files[lang] ? '✅' : '❌';\n }\n detailRows.push(row);\n}\n\nfor (const orphan of payload.orphaned) {\n const row = {\n date: orphan.groupId,\n spanishTitle: `⚠️ Huérfano: solo en ${orphan.languages.join(', ')}`,\n spanishFile: '',\n };\n for (const lang of ORDER) {\n row[lang] = orphan.languages.includes(lang) ? '⚠️' : '—';\n }\n detailRows.push(row);\n}\n\nreturn [summaryRows, detailRows];\n",
|
"code": "const langNames = {\n es: 'Español',\n en: 'English',\n fr: 'Français',\n pt: 'Português',\n rw: 'Kinyarwanda',\n he: 'עברית',\n uk: 'Українська',\n ru: 'Русский',\n kr: 'Kreyòl',\n};\n\nconst ORDER = ['es', 'en', 'fr', 'pt', 'rw', 'he', 'uk', 'ru', 'kr'];\nconst payload = $input.first().json.body;\n\nconst summaryRows = [];\nfor (const code of ORDER) {\n const s = payload.summary[code];\n if (!s) continue;\n summaryRows.push({\n json: {\n language: langNames[code] || code,\n code,\n totalEs: payload.totals.spanishArticles,\n translated: s.translated,\n missing: s.missing,\n percent: s.percent,\n },\n });\n}\n\nconst detailRows = [];\nconst orphanCount = payload.orphaned?.length || 0;\n\nif (orphanCount > 0) {\n const warnRow = { json: { date: '', spanishTitle: `⚠️ ${orphanCount} archivo(s) huérfano(s) — sin versión en español`, spanishUrl: '' } };\n for (const lang of ORDER) warnRow.json[lang] = '';\n detailRows.push(warnRow);\n}\n\nfor (const art of payload.articles) {\n const row = { json: { date: art.date, spanishTitle: art.spanishTitle, spanishUrl: art.spanishUrl || '' } };\n for (const lang of ORDER) row.json[lang] = art.files[lang] ? '✅' : '❌';\n detailRows.push(row);\n}\n\nfor (const orphan of payload.orphaned) {\n const row = { json: { date: orphan.groupId, spanishTitle: `⚠️ Huérfano: solo en ${orphan.languages.join(', ')}`, spanishUrl: orphan.urls?.[orphan.languages[0]] || '' } };\n for (const lang of ORDER) row.json[lang] = orphan.languages.includes(lang) ? '⚠️' : '—';\n detailRows.push(row);\n}\n\nreturn [summaryRows, detailRows];\n",
|
||||||
"mode": "raw"
|
"mode": "raw"
|
||||||
},
|
},
|
||||||
"nodesOnOutput": {
|
"nodesOnOutput": {
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
"value": {
|
"value": {
|
||||||
"A": "={{ $json.date }}",
|
"A": "={{ $json.date }}",
|
||||||
"B": "={{ $json.spanishTitle }}",
|
"B": "={{ $json.spanishTitle }}",
|
||||||
"C": "={{ $json.spanishFile }}",
|
"C": "={{ $json.spanishUrl }}",
|
||||||
"D": "={{ $json.es }}",
|
"D": "={{ $json.es }}",
|
||||||
"E": "={{ $json.en }}",
|
"E": "={{ $json.en }}",
|
||||||
"F": "={{ $json.fr }}",
|
"F": "={{ $json.fr }}",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import '@dotenvx/dotenvx/config';
|
||||||
import { readFileSync, readdirSync, existsSync } from 'fs';
|
import { readFileSync, readdirSync, existsSync } from 'fs';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { google } from 'googleapis';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const ROOT = join(__dirname, '..');
|
const ROOT = join(__dirname, '..');
|
||||||
|
|
@ -20,6 +21,40 @@ function extractField(fm, field) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROUTE_TRANSLATIONS = {
|
||||||
|
es: "noticias", en: "news", fr: "informations",
|
||||||
|
he: "\u05d7\u05d3\u05e9\u05d5\u05ea", uk: "noticias", pt: "noticias",
|
||||||
|
ru: "\u043d\u043e\u0432\u043e\u0441\u0442\u0438", rw: "amakuru", kr: "nouvel",
|
||||||
|
};
|
||||||
|
|
||||||
|
function articleUrl(locale, slug) {
|
||||||
|
const route = ROUTE_TRANSLATIONS[locale] || 'news';
|
||||||
|
return `https://www.centrodelreinodepazyjusticia.com/${locale}/${route}/${slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearSheet(sheetName) {
|
||||||
|
const saEmail = process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL;
|
||||||
|
const pk = process.env.GOOGLE_PRIVATE_KEY;
|
||||||
|
const sid = process.env.GOOGLE_SHEET_ID;
|
||||||
|
if (!saEmail || !pk || !sid) {
|
||||||
|
console.log(`[send-to-n8n] Skipping sheet clear — missing Google credentials`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auth = new google.auth.GoogleAuth({
|
||||||
|
credentials: {
|
||||||
|
client_email: saEmail,
|
||||||
|
private_key: pk.replace(/\\n/g, '\n'),
|
||||||
|
},
|
||||||
|
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
||||||
|
});
|
||||||
|
const sheets = google.sheets({ version: 'v4', auth });
|
||||||
|
await sheets.spreadsheets.values.clear({
|
||||||
|
spreadsheetId: sid,
|
||||||
|
range: `${sheetName}!A:Z`,
|
||||||
|
});
|
||||||
|
console.log(`[send-to-n8n] Cleared sheet: ${sheetName}`);
|
||||||
|
}
|
||||||
|
|
||||||
function parseMeta(filePath) {
|
function parseMeta(filePath) {
|
||||||
const raw = readFileSync(filePath, 'utf-8');
|
const raw = readFileSync(filePath, 'utf-8');
|
||||||
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||||
|
|
@ -30,14 +65,16 @@ function parseMeta(filePath) {
|
||||||
title: extractField(fm, 'title'),
|
title: extractField(fm, 'title'),
|
||||||
date: extractField(fm, 'date'),
|
date: extractField(fm, 'date'),
|
||||||
draft: extractField(fm, 'draft'),
|
draft: extractField(fm, 'draft'),
|
||||||
|
slug: extractField(fm, 'slug'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normFilename(fileName) {
|
function normFilename(fileName) {
|
||||||
return fileName.replace(/\.md$/, '').replace(/-0(\d)$/, '-$1');
|
const name = fileName.replace(/\.md$/, '');
|
||||||
|
return name.replace(/^(\d{4}-\d{2}-\d{2})-0(\d)$/, '$1-$2');
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
async function main() {
|
||||||
const groups = {};
|
const groups = {};
|
||||||
|
|
||||||
for (const lang of LANGUAGES) {
|
for (const lang of LANGUAGES) {
|
||||||
|
|
@ -54,6 +91,7 @@ function main() {
|
||||||
file,
|
file,
|
||||||
title: meta.title || '',
|
title: meta.title || '',
|
||||||
date: meta.date || '',
|
date: meta.date || '',
|
||||||
|
slug: meta.slug || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,20 +111,41 @@ function main() {
|
||||||
|
|
||||||
const articles = esKeys.map(k => {
|
const articles = esKeys.map(k => {
|
||||||
const es = groups[k].es;
|
const es = groups[k].es;
|
||||||
|
const files = Object.fromEntries(LANGUAGES.map(l => [l, groups[k][l]?.file || null]));
|
||||||
|
const slugs = Object.fromEntries(LANGUAGES.map(l => [l, groups[k][l]?.slug || null]));
|
||||||
|
const urls = Object.fromEntries(LANGUAGES.map(l => {
|
||||||
|
const entry = groups[k][l];
|
||||||
|
return [l, entry?.slug ? articleUrl(l, entry.slug) : null];
|
||||||
|
}));
|
||||||
return {
|
return {
|
||||||
groupId: k,
|
groupId: k,
|
||||||
date: es?.date || '',
|
date: es?.date || '',
|
||||||
spanishTitle: es?.title || '',
|
spanishTitle: es?.title || '',
|
||||||
spanishFile: es?.file || '',
|
spanishFile: es?.file || '',
|
||||||
files: Object.fromEntries(LANGUAGES.map(l => [l, groups[k][l]?.file || null])),
|
spanishSlug: es?.slug || '',
|
||||||
|
spanishUrl: es?.slug ? articleUrl('es', es.slug) : '',
|
||||||
|
files,
|
||||||
|
slugs,
|
||||||
|
urls,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const orphaned = allKeys.filter(k => !groups[k].es).map(k => ({
|
const orphaned = allKeys.filter(k => !groups[k].es).map(k => {
|
||||||
groupId: k,
|
const langs = Object.keys(groups[k]);
|
||||||
languages: Object.keys(groups[k]),
|
const files = Object.fromEntries(langs.map(l => [l, groups[k][l].file]));
|
||||||
files: Object.fromEntries(Object.keys(groups[k]).map(l => [l, groups[k][l].file])),
|
const slugs = Object.fromEntries(langs.map(l => [l, groups[k][l].slug]));
|
||||||
|
const urls = Object.fromEntries(langs.map(l => {
|
||||||
|
const entry = groups[k][l];
|
||||||
|
return [l, entry?.slug ? articleUrl(l, entry.slug) : null];
|
||||||
}));
|
}));
|
||||||
|
return {
|
||||||
|
groupId: k,
|
||||||
|
languages: langs,
|
||||||
|
files,
|
||||||
|
slugs,
|
||||||
|
urls,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
|
|
@ -106,6 +165,15 @@ function main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const orphanCount = orphaned.length;
|
||||||
|
if (orphanCount > 0) {
|
||||||
|
console.log(`[send-to-n8n] ⚠️ ${orphanCount} orphaned article(s) — missing Spanish version`);
|
||||||
|
orphaned.forEach(o => {
|
||||||
|
console.log(` ⚠️ ${o.groupId} solo en: ${o.languages.join(', ')}`);
|
||||||
|
Object.entries(o.urls).forEach(([l, u]) => console.log(` ${l}: ${u}`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const url = process.env.N8N_WEBHOOK_URL;
|
const url = process.env.N8N_WEBHOOK_URL;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
console.log('[send-to-n8n] N8N_WEBHOOK_URL not set — showing sample payload');
|
console.log('[send-to-n8n] N8N_WEBHOOK_URL not set — showing sample payload');
|
||||||
|
|
@ -113,17 +181,30 @@ function main() {
|
||||||
console.log('Sample articles (first 5):');
|
console.log('Sample articles (first 5):');
|
||||||
payload.articles.slice(0, 5).forEach(a => {
|
payload.articles.slice(0, 5).forEach(a => {
|
||||||
console.log(` ${a.groupId} | ${a.date} | ${a.spanishTitle.slice(0, 60)}...`);
|
console.log(` ${a.groupId} | ${a.date} | ${a.spanishTitle.slice(0, 60)}...`);
|
||||||
const langs = Object.entries(a.files).filter(([_, v]) => v).map(([k]) => k).join(', ');
|
console.log(` → ES: ${a.spanishUrl}`);
|
||||||
|
const langs = Object.entries(a.urls).filter(([_, v]) => v).map(([k]) => k).join(', ');
|
||||||
console.log(` → Present in: ${langs || 'ES only'}`);
|
console.log(` → Present in: ${langs || 'ES only'}`);
|
||||||
});
|
});
|
||||||
if (payload.orphaned.length) {
|
if (orphanCount) {
|
||||||
console.log('Orphaned articles:');
|
console.log('Orphaned articles:');
|
||||||
payload.orphaned.forEach(o => console.log(` ${o.groupId} | languages: ${o.languages.join(', ')}`));
|
payload.orphaned.forEach(o => {
|
||||||
|
console.log(` ${o.groupId}`);
|
||||||
|
Object.entries(o.urls).forEach(([l, u]) => console.log(` ${l}: ${u}`));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
console.log(`[send-to-n8n] Total: ${articles.length} ES articles, ${orphaned.length} orphans`);
|
console.log(`[send-to-n8n] Total: ${articles.length} ES articles, ${orphanCount} orphans`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL && process.env.GOOGLE_SHEET_ID) {
|
||||||
|
try {
|
||||||
|
await clearSheet('Resumen');
|
||||||
|
await clearSheet('Detalle');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[send-to-n8n] Warning: could not clear sheets (${e.message})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const apiKey = process.env.N8N_API_KEY;
|
const apiKey = process.env.N8N_API_KEY;
|
||||||
const apiKeyHeader = process.env.N8N_API_KEY_HEADER || 'X-API-Key';
|
const apiKeyHeader = process.env.N8N_API_KEY_HEADER || 'X-API-Key';
|
||||||
const headers = { 'Content-Type': 'application/json' };
|
const headers = { 'Content-Type': 'application/json' };
|
||||||
|
|
@ -136,11 +217,14 @@ function main() {
|
||||||
})
|
})
|
||||||
.then(r => {
|
.then(r => {
|
||||||
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
|
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
|
||||||
console.log(`[send-to-n8n] OK — ${articles.length} ES articles, ${orphaned.length} orphans`);
|
console.log(`[send-to-n8n] OK — ${articles.length} ES articles, ${orphanCount} orphans`);
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error(`[send-to-n8n] Warning: n8n unreachable (${e.message}) — build continues`);
|
console.error(`[send-to-n8n] Warning: n8n unreachable (${e.message}) — build continues`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main().catch(e => {
|
||||||
|
console.error(`[send-to-n8n] Fatal error: ${e.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ El primer reconocimiento fue entregado por la senadora María Fernanda Cabal, qu
|
||||||
|
|
||||||
Posteriormente, el representante a la Cámara Jhon Jairo Berrío López tomó la palabra para profundizar en el sentido del reconocimiento, destacando la necesidad de líderes con vocación de servicio en el contexto actual. En su intervención, afirmó que el mundo requiere “más líderes que traigan luz, que cuiden, escuchen y caminen al lado del pueblo”.
|
Posteriormente, el representante a la Cámara Jhon Jairo Berrío López tomó la palabra para profundizar en el sentido del reconocimiento, destacando la necesidad de líderes con vocación de servicio en el contexto actual. En su intervención, afirmó que el mundo requiere “más líderes que traigan luz, que cuiden, escuchen y caminen al lado del pueblo”.
|
||||||
|
|
||||||
Asimismo, subrayó el alcance internacional de la labor del Dr. José Benjamín Pérez Matos, destacando que “su labor trasciende fronteras: ha predicado y realizado muchos eventos importantes de fe en distintos países; no solamente de latinoamérica, sino también del mundo. entre esos países está colombia”.
|
Asimismo, subrayó el alcance internacional de la labor del Dr. José Benjamín Pérez Matos, destacando que “su labor trasciende fronteras: ha predicado y realizado muchos eventos importantes de fe en distintos países; no solamente de latinoamérica, sino también del mundo. Entre esos países está colombia”.
|
||||||
|
|
||||||
En un tono personal, el legislador agregó: “hoy, al entregar esta condecoración, lo hago no solo como congresista, sino como ciudadano que reconoce que en personas como usted, doctor josé benjamín, se ve lo mejor de la fe hecha acción. personas que no solo prometen, sino que cumplen; que no solo hablan, sino que aman; que no solo reciben aplausos momentáneos, sino que siembran para generaciones”.
|
En un tono personal, el legislador agregó: “hoy, al entregar esta condecoración, lo hago no solo como congresista, sino como ciudadano que reconoce que en personas como usted, doctor josé benjamín, se ve lo mejor de la fe hecha acción. personas que no solo prometen, sino que cumplen; que no solo hablan, sino que aman; que no solo reciben aplausos momentáneos, sino que siembran para generaciones”.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue