Compare commits

..

No commits in common. "d0f7bdf1f20e03bec73803c0a45a823ed5ede200" and "745f41cc09efecbbf2179008b607736fbb910777" have entirely different histories.

7 changed files with 135 additions and 1130 deletions

1
.gitignore vendored
View File

@ -29,4 +29,3 @@ prisma/dev.db
# opencode - agentes y scripts locales (no subir a producción) # opencode - agentes y scripts locales (no subir a producción)
opencode/ opencode/
.opencode/ .opencode/
docs/

File diff suppressed because it is too large Load Diff

View File

@ -9,17 +9,17 @@ const { props } = Astro.props;
<div class="container mx-auto"> <div class="container mx-auto">
<div class="grid md:grid-cols-2 min-w-0"> <div class="grid md:grid-cols-2 min-w-0">
<div class="bg-[#EBE5D0] p-6 md:p-12 lg:p-24 grid lg:grid-cols-2 min-w-0"> <div class="bg-[#EBE5D0] p-6 md:p-12 lg:p-24 grid lg:grid-cols-2 min-w-0">
<div class="flex flex-col justify-between min-h-full min-w-0"> <div class="flex flex-col justify-between min-h-full break-all">
<h2 class="text-3xl lg:text-2xl xl:text-3xl 2xl:text-5xl font-secondary text-tertiary font-bold wrap-break-word">{tl("values.justice.title")}</h2> <h2 class="text-5xl font-secondary text-tertiary font-bold">{tl("values.justice.title")}</h2>
<p class="text-lg text-tertiary break-words">{tl("values.justice.text")}</p> <p class="text-lg text-tertiary break-words">{tl("values.justice.text")}</p>
</div> </div>
<div class="flex items-end justify-end min-h-full min-w-0 max-w-full"> <div class="flex items-end justify-end min-h-full">
<Icon name="icon_justice_1" class="text-6xl md:text-9xl text-tertiary max-w-full h-auto" /> <Icon name="icon_justice_1" class="text-6xl md:text-9xl text-tertiary" />
</div> </div>
</div> </div>
<div class="bg-tertiary p-6 md:p-12 lg:p-24 grid lg:grid-cols-2 min-w-0"> <div class="bg-tertiary p-6 md:p-12 lg:p-24 grid lg:grid-cols-2 min-w-0">
<div class="flex flex-col justify-between min-h-full min-w-0 max-w-full"> <div class="flex flex-col justify-between min-h-full min-w-0 max-w-full">
<h2 class="text-3xl lg:text-2xl xl:text-3xl 2xl:text-5xl font-secondary font-bold text-[#CBA16A] wrap-break-word">{tl("values.integrity.title")}</h2> <h2 class="text-5xl font-secondary font-bold text-[#CBA16A] break-all">{tl("values.integrity.title")}</h2>
<p class="text-[#CBA16A] text-lg break-words">{tl("values.integrity.text")}</p> <p class="text-[#CBA16A] text-lg break-words">{tl("values.integrity.text")}</p>
</div> </div>
<div class="flex items-end justify-end min-h-full min-w-0 max-w-full"> <div class="flex items-end justify-end min-h-full min-w-0 max-w-full">
@ -28,17 +28,17 @@ const { props } = Astro.props;
</div> </div>
</div> </div>
<div class="grid lg:grid-cols-3"> <div class="grid lg:grid-cols-3">
<div class="bg-tertiary p-12 xl:p-20 2xl:p-24 flex justify-between flex-col min-h-full lg:aspect-square min-w-0"> <div class="bg-tertiary p-12 xl:p-20 2xl:p-24 flex justify-between flex-col min-h-full lg:aspect-square">
<h2 class="text-3xl 2xl:text-5xl font-secondary font-bold text-white wrap-break-word">{tl("values.service.title")}</h2> <h2 class="text-3xl xl:text-5xl font-secondary font-bold text-white">{tl("values.service.title")}</h2>
<p class="text-xl text-[#EBE5D0] wrap-break-word">{tl("values.service.text")}</p> <p class="text-xl text-[#EBE5D0]">{tl("values.service.text")}</p>
</div> </div>
<div class="bg-[#BEA48D] p-12 xl:p-20 2xl:p-24 flex justify-between flex-col min-h-full lg:aspect-square min-w-0"> <div class="bg-[#BEA48D] p-12 xl:p-20 2xl:p-24 flex justify-between flex-col min-h-full lg:aspect-square">
<h2 class="text-3xl 2xl:text-5xl font-secondary font-bold text-tertiary wrap-break-word">{tl("values.excellence.title")}</h2> <h2 class="text-3xl xl:text-5xl font-secondary font-bold text-tertiary">{tl("values.excellence.title")}</h2>
<p class="text-xl text-tertiary wrap-break-word">{tl("values.excellence.text")}</p> <p class="text-xl text-tertiary">{tl("values.excellence.text")}</p>
</div> </div>
<div class="bg-[#22523F] p-12 xl:p-20 2xl:p-24 flex justify-between flex-col min-h-full lg:aspect-square min-w-0"> <div class="bg-[#22523F] p-12 xl:p-20 2xl:p-24 flex justify-between flex-col min-h-full lg:aspect-square">
<h2 class="text-3xl 2xl:text-5xl font-secondary font-bold text-white wrap-break-word">{tl("values.dialogue.title")}</h2> <h2 class="text-3xl xl:text-5xl font-secondary font-bold text-white">{tl("values.dialogue.title")}</h2>
<p class="text-white text-xl wrap-break-word">{tl("values.dialogue.text")}</p> <p class="text-white text-xl">{tl("values.dialogue.text")}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,5 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { prisma } from "../lib/prisma"; import { prisma } from "../lib/prisma";
import { sendEmailCf } from "../lib/email";
export const prerender = false; export const prerender = false;
@ -65,11 +64,32 @@ export const POST: APIRoute = async ({ request }) => {
}, },
}); });
} }
await sendEmailCf( const emailApiKey = import.meta.env.EMAIL_API_KEY;
{ nombre, email, mensaje },
"joseperez@centrodelreinodepazyjusticia.com", const emailResponse = await fetch("https://flows2.carpa.com/webhook/email/send", {
"CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO", method: "POST",
); headers: {
"Content-Type": "application/json",
"x-api-key": emailApiKey!,
},
body: JSON.stringify({
type: "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO",
data: {
nombre,
email,
mensaje,
senderName : "Centro del Reino de Paz y Justicia",
},
}),
});
if (!emailResponse.ok) {
console.error("Error enviando correo");
return new Response(
JSON.stringify({ error: "Error en servicio de correo" }),
{ status: 500 }
);
}
return new Response(null, { return new Response(null, {
status: 302, status: 302,

View File

@ -1,6 +1,5 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { appendToSheet, emailExists } from "../lib/googleSheets"; import { appendToSheet, emailExists } from "../lib/googleSheets";
import { sendEmailCf } from "../lib/email";
export const prerender = false; export const prerender = false;
export const POST: APIRoute = async ({ request }) => { export const POST: APIRoute = async ({ request }) => {
@ -30,6 +29,17 @@ export const POST: APIRoute = async ({ request }) => {
}) })
} }
const dataSend = {
nombres,
apellidos,
pais,
lugar,
direccion,
telefono,
email
};
const emailApiKey = import.meta.env.EMAIL_API_KEY;
const valuesForSheets = [ const valuesForSheets = [
nombres, nombres,
apellidos, apellidos,
@ -41,12 +51,31 @@ export const POST: APIRoute = async ({ request }) => {
new Date().toLocaleString() new Date().toLocaleString()
]; ];
// 🔹 Enviar correo (Cloudflare) y guardar en Google Sheets // 🔹 Enviar a tu backend real
await Promise.all([ const [emailResponse] = await Promise.all([
sendEmailCf({ nombres, apellidos }, email, "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION"), fetch("http://155.138.215.11:3050/email/send", {
appendToSheet(valuesForSheets) method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": emailApiKey!,
},
body: JSON.stringify({
type: "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION",
data: dataSend,
}),
}),
appendToSheet(valuesForSheets) // Guardar en Google Sheets
]); ]);
if (!emailResponse.ok) {
return new Response(null, {
status: 302,
headers: {
Location: "/",
},
});
}
// 🔹 Redirección elegante después de enviar // 🔹 Redirección elegante después de enviar
return new Response(null, { return new Response(null, {
status: 302, status: 302,

View File

@ -1,6 +1,6 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { appendRegistrationToSheet } from "../lib/googleSheets"; import { appendRegistrationToSheet } from "../lib/googleSheets";
import { sendEmailCf } from "../lib/email"; import { sendEmail } from "../lib/email";
export const prerender = false; export const prerender = false;
const COLUMNS = [ const COLUMNS = [
@ -106,9 +106,10 @@ export const POST: APIRoute = async ({ request }) => {
const result = await appendRegistrationToSheet(row); const result = await appendRegistrationToSheet(row);
await sendEmailCf({ await sendEmail({
nombres: formData.nombre_completo, nombres: formData.nombre,
apellidos: formData.apellido, apellidos: formData.apellido,
locale: formData.locale,
}, formData.correo, "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION"); }, formData.correo, "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION");
return Response.json(result); return Response.json(result);

View File

@ -1,4 +1,4 @@
import Cloudflare from "cloudflare"; // import Cloudflare from "cloudflare";
type TypeEmailData = "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO" | "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION"; type TypeEmailData = "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO" | "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION";
@ -32,74 +32,74 @@ export async function sendEmail(data: object, to: string, type: TypeEmailData) {
} }
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// Cloudflare (Email Sending) // CÓDIGO COMENTADO — Cloudflare (Email Workers)
// Usar sendEmailCf() en lugar de sendEmail() (n8n/Brevo) // Descomentar para volver a Cloudflare en lugar de n8n/Brevo
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
const TEMPLATE_URLS: Record<TypeEmailData, { subject: string; url: string }> = { // const TEMPLATE_URLS: Record<TypeEmailData, { subject: string; url: string }> = {
CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION: { // CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION: {
subject: "Confirmación de inscripción al voluntariado", // subject: "Confirmación de inscripción al voluntariado",
url: "https://assets.centrodelreinodepazyjusticia.com/centro_del_reino_paz_y_justicia_postulacion_user.html", // url: "https://pub-910ed90860804520b9202194cf43c0a6.r2.dev/centro_del_reino_paz_y_justicia_postulacion_user.html",
}, // },
CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO: { // CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO: {
subject: "Notificación de contacto institucional", // subject: "Notificación de contacto institucional",
url: "https://assets.centrodelreinodepazyjusticia.com/centro_del_reino_paz_y_justicia_info.html", // url: "https://pub-910ed90860804520b9202194cf43c0a6.r2.dev/centro_del_reino_paz_y_justicia_info.html",
}, // },
}; // };
const TEMPLATE_CACHE = new Map<string, string>(); // const TEMPLATE_CACHE = new Map<string, string>();
async function fetchTemplate(type: TypeEmailData): Promise<string> { // async function fetchTemplate(type: TypeEmailData): Promise<string> {
const cached = TEMPLATE_CACHE.get(type); // const cached = TEMPLATE_CACHE.get(type);
if (cached) return cached; // if (cached) return cached;
const { url } = TEMPLATE_URLS[type]; // const { url } = TEMPLATE_URLS[type];
const res = await fetch(url); // const res = await fetch(url);
if (!res.ok) { // if (!res.ok) {
throw new Error(`Error descargando template ${type}: HTTP ${res.status}`); // throw new Error(`Error descargando template ${type}: HTTP ${res.status}`);
} // }
const html = await res.text(); // const html = await res.text();
TEMPLATE_CACHE.set(type, html); // TEMPLATE_CACHE.set(type, html);
return html; // return html;
} // }
function renderTemplate(template: string, data: Record<string, string>): string { // function renderTemplate(template: string, data: Record<string, string>): string {
let html = template; // let html = template;
for (const [key, value] of Object.entries(data)) { // for (const [key, value] of Object.entries(data)) {
html = html.replaceAll(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value ?? ""); // html = html.replaceAll(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value ?? "");
} // }
return html; // return html;
} // }
function stripHtml(html: string): string { // function stripHtml(html: string): string {
return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim(); // return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
} // }
export async function sendEmailCf(data: Record<string, string>, to: string, type: TypeEmailData) { // export async function sendEmailCf(data: Record<string, string>, to: string, type: TypeEmailData) {
const apiToken = import.meta.env.CLOUDFLARE_API_TOKEN; // const apiToken = import.meta.env.CLOUDFLARE_API_TOKEN;
const accountId = import.meta.env.CLOUDFLARE_ACCOUNT_ID; // const accountId = import.meta.env.CLOUDFLARE_ACCOUNT_ID;
const client = new Cloudflare({ apiToken }); // const client = new Cloudflare({ apiToken });
const { subject } = TEMPLATE_URLS[type]; // const { subject } = TEMPLATE_URLS[type];
const rawHtml = await fetchTemplate(type); // const rawHtml = await fetchTemplate(type);
const html = renderTemplate(rawHtml, data); // const html = renderTemplate(rawHtml, data);
const text = stripHtml(html); // const text = stripHtml(html);
const response = await client.emailSending.send({ // const response = await client.emailSending.send({
account_id: accountId, // account_id: accountId,
from: "welcome@mail.centrodelreinodepazyjusticia.com", // from: "welcome@mail.centrodelreinodepazyjusticia.com",
to, // to,
subject, // subject,
html, // html,
text, // text,
}); // });
if (response.permanent_bounces.includes(to)) { // if (!response.delivered) {
console.error("Cloudflare email not delivered", response); // console.error("Cloudflare email not delivered", response);
throw new Error("El correo no fue entregado"); // throw new Error("El correo no fue entregado");
} // }
} // }
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// ALTERNATIVA: Brevo (SDK directo) // ALTERNATIVA: Brevo (SDK directo)