Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0c34ae0
Añadir secciones de comentarios para organización y claridad en el có…
PGSCOM Sep 29, 2025
a8a7ed0
Ahora la animación se calcula en base a la distancia recorrida y no a…
PGSCOM Sep 29, 2025
0d9575e
Mejorar la función padNum para manejar el ancho de relleno de forma p…
PGSCOM Sep 30, 2025
67f9378
LiquidGlass Component añadido para los iconos de los proyectos
PGSCOM Oct 1, 2025
a02e529
Añadir detección de dispositivos móviles y optimizar LiquidGlassComp…
PGSCOM Oct 1, 2025
3a8c673
Más optimización para teléfonos móviles
PGSCOM Oct 2, 2025
b595d60
Ajustar propiedades de LiquidGlassComponent y reemplazar contenido en…
PGSCOM Oct 24, 2025
7335b03
Actualización Astro
PGSCOM Oct 24, 2025
51d1b48
Menu de aptitudes
PGSCOM Dec 12, 2025
e3755e5
Tarjetas alrededor del logo
PGSCOM Dec 12, 2025
cf6f465
Iconos alrededor del logo
PGSCOM Dec 12, 2025
111f623
Disposición de los iconos
PGSCOM Dec 12, 2025
fc6cdbf
Animación scroll para proyectos
PGSCOM Dec 12, 2025
9702a51
Actualizar la importación del logo a la versión correcta de pgscom.png
PGSCOM Dec 13, 2025
8c8e651
Agregar detalles de proyectos y mejorar la estructura de la sección d…
PGSCOM Dec 13, 2025
db5b97a
Quitar el logo en dispositivos móviles
PGSCOM Dec 13, 2025
60b1015
Animación del texto al cargar
PGSCOM Dec 13, 2025
ea24ea6
Optimización
PGSCOM Dec 13, 2025
ebc411b
Fix: Sticky en CSS
PGSCOM Dec 13, 2025
83dc33d
Centrado para moviles
PGSCOM Dec 13, 2025
cdb1a05
Reducir altura de las secciónes de proyectos
PGSCOM Dec 13, 2025
e75f40f
Fix: Scroll teléfonos
PGSCOM Dec 13, 2025
4121205
Forzar a que el animlogo se cargue como un svg
PGSCOM Dec 13, 2025
f87568a
Formulario de contacto
PGSCOM Dec 13, 2025
1f3dea6
Sección de contacto actualizada
PGSCOM Dec 13, 2025
4b7a3df
Ajustar estilos de la sección de contacto para mejorar la responsivid…
PGSCOM Dec 13, 2025
bab0a8b
Mejorar la gestión de peticiones de contacto: agregar logs para el ti…
PGSCOM Dec 13, 2025
5b70873
Mejora del diseño
PGSCOM Dec 13, 2025
ea9a9ce
Mejoras en el diseño de la sección de proyectos
PGSCOM Dec 13, 2025
545cbf2
Página de contacto
PGSCOM Dec 14, 2025
e61173a
Protección anti bots para el formulario
PGSCOM Dec 14, 2025
9686f1d
Agregar validación de Turnstile en el formulario de contacto
PGSCOM Dec 14, 2025
753d4ba
Link URL Botón proyecto arreglado
PGSCOM Dec 15, 2025
6bad128
Mejorar la sección de proyectos con nuevas tarjetas y un modal para d…
PGSCOM Dec 15, 2025
6aac62a
Info e iconos de los proyectos añadido
PGSCOM Dec 18, 2025
2536057
Mejorar el modal de proyectos con nuevo diseño cinematográfico, inclu…
PGSCOM Dec 18, 2025
8612fe0
Mejorar el modal de proyectos: ajustar estilos de superposición y mod…
PGSCOM Dec 18, 2025
3192077
Ajustar estilos del modal de proyectos: modificar paddings, márgenes …
PGSCOM Dec 18, 2025
995ee91
Fix degradado descolocado en la página del proyecto versión móvil
PGSCOM Dec 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// @ts-check
import { defineConfig } from 'astro/config';

import react from '@astrojs/react';

// https://astro.build/config
export default defineConfig({});
export default defineConfig({
integrations: [react()]
});
147 changes: 147 additions & 0 deletions functions/contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
export const onRequestPost: PagesFunction = async ({ request, env }) => {
console.log('📨 Recibiendo petición de contacto...');

try {
const contentType = request.headers.get('content-type') || '';
console.log('Content-Type:', contentType);
let data: Record<string, string> = {};

if (contentType.includes('application/json')) {
data = await request.json();
console.log('✅ Datos JSON parseados');
} else if (contentType.includes('application/x-www-form-urlencoded')) {
const form = await request.formData();
data = Object.fromEntries([...form.entries()].map(([k, v]) => [k, String(v)]));
console.log('✅ Datos form-data parseados');
} else {
console.error('❌ Content-type no soportado:', contentType);
return new Response(JSON.stringify({ ok: false, error: 'Unsupported content type' }), {
status: 415,
headers: { 'content-type': 'application/json' },
});
}

const { nombre, email, asunto, mensaje } = data;
console.log('Datos recibidos:', { nombre, email, asunto, mensajeLength: mensaje?.length });

if (!nombre || !email || !asunto || !mensaje) {
console.error('❌ Campos faltantes');
return new Response(JSON.stringify({ ok: false, error: 'Missing fields' }), {
status: 400,
headers: { 'content-type': 'application/json' },
});
}

// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
console.error('❌ Email inválido:', email);
return new Response(JSON.stringify({ ok: false, error: 'Invalid email' }), {
status: 400,
headers: { 'content-type': 'application/json' },
});
}

Comment on lines +35 to +44
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email validation regex is basic and may not catch all invalid email formats. Consider using a more comprehensive email validation library or regex pattern that handles edge cases like multiple @ symbols, invalid domains, etc.

Suggested change
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
console.error('❌ Email inválido:', email);
return new Response(JSON.stringify({ ok: false, error: 'Invalid email' }), {
status: 400,
headers: { 'content-type': 'application/json' },
});
}
// Email validation (more robust than a basic pattern)
const normalizedEmail = email.trim();
// Enforce common length constraints
if (normalizedEmail.length > 320) { // 64 (local) + 1 + 255 (domain) is typical max
console.error('❌ Email demasiado largo:', normalizedEmail);
return new Response(JSON.stringify({ ok: false, error: 'Invalid email' }), {
status: 400,
headers: { 'content-type': 'application/json' },
});
}
// Allow typical local-part characters and require a plausible domain with TLD
const emailRegex =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
if (!emailRegex.test(normalizedEmail)) {
console.error('❌ Email inválido:', normalizedEmail);
return new Response(JSON.stringify({ ok: false, error: 'Invalid email' }), {
status: 400,
headers: { 'content-type': 'application/json' },
});
}

Copilot uses AI. Check for mistakes.
// Validar Turnstile
const turnstileToken = data['cf-turnstile-response'];
if (!turnstileToken) {
console.error('❌ Token de Turnstile faltante');
return new Response(JSON.stringify({ ok: false, error: 'Turnstile token missing' }), {
status: 400,
headers: { 'content-type': 'application/json' },
});
}

const ip = request.headers.get('CF-Connecting-IP');
const formData = new FormData();
formData.append('secret', env.TURNSTILE_SECRET_KEY || '1x0000000000000000000000000000000AA');
Comment on lines +56 to +57
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback secret key '1x0000000000000000000000000000000AA' is a test key. While this is acceptable for development, ensure that in production, the actual TURNSTILE_SECRET_KEY environment variable is always set, as this fallback key will allow all verification attempts to pass.

Suggested change
const formData = new FormData();
formData.append('secret', env.TURNSTILE_SECRET_KEY || '1x0000000000000000000000000000000AA');
if (!env.TURNSTILE_SECRET_KEY) {
console.error('❌ TURNSTILE_SECRET_KEY no está configurado en el entorno');
return new Response(JSON.stringify({ ok: false, error: 'Server misconfiguration' }), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}
const formData = new FormData();
formData.append('secret', env.TURNSTILE_SECRET_KEY);

Copilot uses AI. Check for mistakes.
formData.append('response', turnstileToken);
formData.append('remoteip', ip || '');

const turnstileUrl = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
const turnstileResult = await fetch(turnstileUrl, {
body: formData,
method: 'POST',
});

const turnstileOutcome: any = await turnstileResult.json();
if (!turnstileOutcome.success) {
console.error('❌ Validación de Turnstile fallida:', turnstileOutcome);
return new Response(JSON.stringify({ ok: false, error: 'Invalid Turnstile token' }), {
status: 403,
headers: { 'content-type': 'application/json' },
});
}

// Enviar mensaje via Telegram Bot
console.log('🔍 Verificando variables de Telegram...');
console.log('TELEGRAM_BOT_TOKEN presente:', !!env.TELEGRAM_BOT_TOKEN);
console.log('TELEGRAM_CHAT_ID presente:', !!env.TELEGRAM_CHAT_ID);

if (!env.TELEGRAM_BOT_TOKEN || !env.TELEGRAM_CHAT_ID) {
console.error('❌ Variables de entorno de Telegram NO configuradas');
return new Response(JSON.stringify({
ok: false,
error: 'Telegram no configurado. Contacta al administrador.'
}), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}

const tgApi = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`;
const text = [
`✨ Nuevo contacto en PGSCOM`,
``,
`👤 Nombre: ${nombre}`,
`📧 Email: ${email}`,
`📝 Asunto: ${asunto}`,
``,
`💬 Mensaje:`,
`${mensaje}`,
``,
`🕐 ${new Date().toLocaleString('es-ES', { timeZone: 'Europe/Madrid' })}`,
].join('\n');

console.log('📤 Enviando mensaje a Telegram...');
const tgResp = await fetch(tgApi, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
chat_id: env.TELEGRAM_CHAT_ID,
text,
disable_web_page_preview: true,
}),
});

const tgData = await tgResp.json();
console.log('Respuesta de Telegram:', { status: tgResp.status, ok: tgData.ok });

if (!tgResp.ok || !tgData.ok) {
console.error('❌ Error de Telegram:', JSON.stringify(tgData));
return new Response(JSON.stringify({
ok: false,
error: 'Error al enviar mensaje a Telegram'
}), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}

console.log('✅ Mensaje enviado exitosamente a Telegram');
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
} catch (error) {
console.error('❌ Error en la función:', error);
return new Response(JSON.stringify({
ok: false,
error: 'Server error',
details: error instanceof Error ? error.message : String(error)
}), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}
};
Loading
Loading