From f93f9fd3ce3094f4b0278a1c1b6a7bf446c9f0ae Mon Sep 17 00:00:00 2001 From: esteban Date: Fri, 24 Jul 2026 20:34:36 -0500 Subject: [PATCH] feat: create send email confirmation, add code country, and other fixes --- .env | 7 + .env.example | 6 + .gitea/workflows/deploy.yaml | 2 + ecosystem.config.cjs | 4 + public/forms/formulario-inscripcion.json | 8 +- public/forms/paises.json | 1353 ++++++++++++++++++---- src/components/forms/DynamicForm.vue | 151 ++- src/i18n/es.json | 4 + src/pages/[locale]/[section]/index.astro | 2 +- src/pages/api/formulario/send.ts | 28 + 10 files changed, 1358 insertions(+), 207 deletions(-) create mode 100644 .env.example diff --git a/.env b/.env index 01caddd..b746ff8 100644 --- a/.env +++ b/.env @@ -22,3 +22,10 @@ DATABASE_URL="file:./dev.db" N8N_WEBHOOK_URL="https://flows2.carpa.com/webhook/news-summary" N8N_API_KEY="sk_live_JwjpTdZrLVvijCKuWylWZbUuhBm6zPDH" + +# Cloudflare Turnstile — public key (visible in frontend) +TURNSTILE_SITE_KEY=0x4AAAAAAD9IYWC6HPflpneO + +# Cloudflare Turnstile — secret key (server-side, keep safe!) +# In production, set this as a Gitea Actions secret named TURNSTILE_SECRET_KEY +TURNSTILE_SECRET_KEY=0x4AAAAAAD9IYXASjQSpmo2iS902SDoXnT4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..86e3c4d --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Cloudflare Turnstile — public key (visible in frontend) +TURNSTILE_SITE_KEY=0x4AA... + +# Cloudflare Turnstile — secret key (server-side, keep safe!) +# In production, set this as a Gitea Actions secret named TURNSTILE_SECRET_KEY +TURNSTILE_SECRET_KEY=0x4AA... diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 7b68e4e..0eb9000 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -54,5 +54,7 @@ jobs: corepack prepare pnpm@11 --activate cd ${{ env.TARGET_DIR }} pnpm install --prod 2>&1 + sed -i "s|TURNSTILE_SITE_KEY: \"\"|TURNSTILE_SITE_KEY: \"${{ secrets.TURNSTILE_SITE_KEY }}\"|" ecosystem.config.cjs + sed -i "s|TURNSTILE_SECRET_KEY: \"\"|TURNSTILE_SECRET_KEY: \"${{ secrets.TURNSTILE_SECRET_KEY }}\"|" ecosystem.config.cjs pm2 reload ecosystem.config.cjs --env ${{ env.PM2_ENV }} --update-env || \ pm2 start ecosystem.config.cjs --env ${{ env.PM2_ENV }} --update-env diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 7a2e0d4..ef79c19 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -5,10 +5,14 @@ module.exports = { env: { NODE_ENV: "staging", PORT: 3310, + TURNSTILE_SITE_KEY: "", + TURNSTILE_SECRET_KEY: "", }, env_production: { NODE_ENV: "production", PORT: 3310, + TURNSTILE_SITE_KEY: "", + TURNSTILE_SECRET_KEY: "", } }] }; diff --git a/public/forms/formulario-inscripcion.json b/public/forms/formulario-inscripcion.json index ccb45d2..df346b5 100644 --- a/public/forms/formulario-inscripcion.json +++ b/public/forms/formulario-inscripcion.json @@ -40,8 +40,7 @@ "required": true, "options": [ { "value": "M", "label": "form.sexo.m" }, - { "value": "F", "label": "form.sexo.f" }, - { "value": "Otro", "label": "form.sexo.o" } + { "value": "F", "label": "form.sexo.f" } ] }, { "key": "direccion_completa", "label": "form.direccion", "type": "text", "required": true, "colspan": 2 }, @@ -49,8 +48,8 @@ { "key": "estado", "label": "form.estado", "type": "text" }, { "key": "pais", "label": "form.pais", "type": "select", "required": true, "source": "/forms/paises.json" }, { "key": "postal", "label": "form.codigo_postal", "type": "text", "required": true }, - { "key": "telefono", "label": "form.mobile", "type": "text", "required": true }, - { "key": "whatsapp", "label": "form.whatsapp", "type": "text" } + { "key": "telefono", "label": "form.mobile", "type": "phone", "required": true }, + { "key": "whatsapp", "label": "form.whatsapp", "type": "phone_country", "source": "/forms/paises.json" } ] }, { @@ -78,6 +77,7 @@ { "value": "Ingles", "label": "form.idioma.ing" }, { "value": "Hebreo", "label": "form.idioma.heb" }, { "value": "Portugues", "label": "form.idioma.port" }, + { "value": "Frances", "label": "form.idioma.franc" }, { "value": "Otro", "label": "form.idioma.otro" } ] }, diff --git a/public/forms/paises.json b/public/forms/paises.json index 0d08a5d..5197469 100644 --- a/public/forms/paises.json +++ b/public/forms/paises.json @@ -1,195 +1,1160 @@ [ - { "label": "Afganistán", "value": "Afganistán" }, - { "label": "Albania", "value": "Albania" }, - { "label": "Alemania", "value": "Alemania" }, - { "label": "Andorra", "value": "Andorra" }, - { "label": "Angola", "value": "Angola" }, - { "label": "Antigua y Barbuda", "value": "Antigua y Barbuda" }, - { "label": "Arabia Saudita", "value": "Arabia Saudita" }, - { "label": "Argelia", "value": "Argelia" }, - { "label": "Argentina", "value": "Argentina" }, - { "label": "Armenia", "value": "Armenia" }, - { "label": "Australia", "value": "Australia" }, - { "label": "Austria", "value": "Austria" }, - { "label": "Azerbaiyán", "value": "Azerbaiyán" }, - { "label": "Bahamas", "value": "Bahamas" }, - { "label": "Bangladés", "value": "Bangladés" }, - { "label": "Barbados", "value": "Barbados" }, - { "label": "Baréin", "value": "Baréin" }, - { "label": "Bélgica", "value": "Bélgica" }, - { "label": "Belice", "value": "Belice" }, - { "label": "Benín", "value": "Benín" }, - { "label": "Bielorrusia", "value": "Bielorrusia" }, - { "label": "Birmania", "value": "Birmania" }, - { "label": "Bolivia", "value": "Bolivia" }, - { "label": "Bosnia y Herzegovina", "value": "Bosnia y Herzegovina" }, - { "label": "Botsuana", "value": "Botsuana" }, - { "label": "Brasil", "value": "Brasil" }, - { "label": "Brunéi", "value": "Brunéi" }, - { "label": "Bulgaria", "value": "Bulgaria" }, - { "label": "Burkina Faso", "value": "Burkina Faso" }, - { "label": "Burundi", "value": "Burundi" }, - { "label": "Cabo Verde", "value": "Cabo Verde" }, - { "label": "Camboya", "value": "Camboya" }, - { "label": "Camerún", "value": "Camerún" }, - { "label": "Canadá", "value": "Canadá" }, - { "label": "Catar", "value": "Catar" }, - { "label": "Chad", "value": "Chad" }, - { "label": "Chile", "value": "Chile" }, - { "label": "China", "value": "China" }, - { "label": "Chipre", "value": "Chipre" }, - { "label": "Colombia", "value": "Colombia" }, - { "label": "Comoras", "value": "Comoras" }, - { "label": "Congo", "value": "Congo" }, - { "label": "Corea del Norte", "value": "Corea del Norte" }, - { "label": "Corea del Sur", "value": "Corea del Sur" }, - { "label": "Costa de Marfil", "value": "Costa de Marfil" }, - { "label": "Costa Rica", "value": "Costa Rica" }, - { "label": "Croacia", "value": "Croacia" }, - { "label": "Cuba", "value": "Cuba" }, - { "label": "Dinamarca", "value": "Dinamarca" }, - { "label": "Dominica", "value": "Dominica" }, - { "label": "Ecuador", "value": "Ecuador" }, - { "label": "Egipto", "value": "Egipto" }, - { "label": "El Salvador", "value": "El Salvador" }, - { "label": "Emiratos Árabes Unidos", "value": "Emiratos Árabes Unidos" }, - { "label": "Eritrea", "value": "Eritrea" }, - { "label": "Eslovaquia", "value": "Eslovaquia" }, - { "label": "Eslovenia", "value": "Eslovenia" }, - { "label": "España", "value": "España" }, - { "label": "Estados Unidos", "value": "Estados Unidos" }, - { "label": "Estonia", "value": "Estonia" }, - { "label": "Etiopía", "value": "Etiopía" }, - { "label": "Filipinas", "value": "Filipinas" }, - { "label": "Finlandia", "value": "Finlandia" }, - { "label": "Fiyi", "value": "Fiyi" }, - { "label": "Francia", "value": "Francia" }, - { "label": "Gabón", "value": "Gabón" }, - { "label": "Gambia", "value": "Gambia" }, - { "label": "Georgia", "value": "Georgia" }, - { "label": "Ghana", "value": "Ghana" }, - { "label": "Granada", "value": "Granada" }, - { "label": "Grecia", "value": "Grecia" }, - { "label": "Guatemala", "value": "Guatemala" }, - { "label": "Guinea", "value": "Guinea" }, - { "label": "Guinea Ecuatorial", "value": "Guinea Ecuatorial" }, - { "label": "Guinea-Bisáu", "value": "Guinea-Bisáu" }, - { "label": "Guyana", "value": "Guyana" }, - { "label": "Haití", "value": "Haití" }, - { "label": "Honduras", "value": "Honduras" }, - { "label": "Hungría", "value": "Hungría" }, - { "label": "India", "value": "India" }, - { "label": "Indonesia", "value": "Indonesia" }, - { "label": "Irak", "value": "Irak" }, - { "label": "Irán", "value": "Irán" }, - { "label": "Irlanda", "value": "Irlanda" }, - { "label": "Islandia", "value": "Islandia" }, - { "label": "Islas Marshall", "value": "Islas Marshall" }, - { "label": "Islas Salomón", "value": "Islas Salomón" }, - { "label": "Israel", "value": "Israel" }, - { "label": "Italia", "value": "Italia" }, - { "label": "Jamaica", "value": "Jamaica" }, - { "label": "Japón", "value": "Japón" }, - { "label": "Jordania", "value": "Jordania" }, - { "label": "Kazajistán", "value": "Kazajistán" }, - { "label": "Kenia", "value": "Kenia" }, - { "label": "Kirguistán", "value": "Kirguistán" }, - { "label": "Kiribati", "value": "Kiribati" }, - { "label": "Kuwait", "value": "Kuwait" }, - { "label": "Laos", "value": "Laos" }, - { "label": "Lesoto", "value": "Lesoto" }, - { "label": "Letonia", "value": "Letonia" }, - { "label": "Líbano", "value": "Líbano" }, - { "label": "Liberia", "value": "Liberia" }, - { "label": "Libia", "value": "Libia" }, - { "label": "Liechtenstein", "value": "Liechtenstein" }, - { "label": "Lituania", "value": "Lituania" }, - { "label": "Luxemburgo", "value": "Luxemburgo" }, - { "label": "Madagascar", "value": "Madagascar" }, - { "label": "Malasia", "value": "Malasia" }, - { "label": "Malaui", "value": "Malaui" }, - { "label": "Maldivas", "value": "Maldivas" }, - { "label": "Mali", "value": "Mali" }, - { "label": "Malta", "value": "Malta" }, - { "label": "Marruecos", "value": "Marruecos" }, - { "label": "Mauricio", "value": "Mauricio" }, - { "label": "Mauritania", "value": "Mauritania" }, - { "label": "México", "value": "México" }, - { "label": "Micronesia", "value": "Micronesia" }, - { "label": "Moldavia", "value": "Moldavia" }, - { "label": "Mónaco", "value": "Mónaco" }, - { "label": "Mongolia", "value": "Mongolia" }, - { "label": "Montenegro", "value": "Montenegro" }, - { "label": "Mozambique", "value": "Mozambique" }, - { "label": "Namibia", "value": "Namibia" }, - { "label": "Nauru", "value": "Nauru" }, - { "label": "Nepal", "value": "Nepal" }, - { "label": "Nicaragua", "value": "Nicaragua" }, - { "label": "Níger", "value": "Níger" }, - { "label": "Nigeria", "value": "Nigeria" }, - { "label": "Noruega", "value": "Noruega" }, - { "label": "Nueva Zelanda", "value": "Nueva Zelanda" }, - { "label": "Omán", "value": "Omán" }, - { "label": "Países Bajos", "value": "Países Bajos" }, - { "label": "Pakistán", "value": "Pakistán" }, - { "label": "Palaos", "value": "Palaos" }, - { "label": "Palestina", "value": "Palestina" }, - { "label": "Panamá", "value": "Panamá" }, - { "label": "Papúa Nueva Guinea", "value": "Papúa Nueva Guinea" }, - { "label": "Paraguay", "value": "Paraguay" }, - { "label": "Perú", "value": "Perú" }, - { "label": "Polonia", "value": "Polonia" }, - { "label": "Portugal", "value": "Portugal" }, - { "label": "Reino Unido", "value": "Reino Unido" }, - { "label": "República Centroafricana", "value": "República Centroafricana" }, - { "label": "República Checa", "value": "República Checa" }, - { "label": "República Dominicana", "value": "República Dominicana" }, - { "label": "República del Congo", "value": "República del Congo" }, - { "label": "Ruanda", "value": "Ruanda" }, - { "label": "Rumania", "value": "Rumania" }, - { "label": "Rusia", "value": "Rusia" }, - { "label": "Samoa", "value": "Samoa" }, - { "label": "San Cristóbal y Nieves", "value": "San Cristóbal y Nieves" }, - { "label": "San Marino", "value": "San Marino" }, - { "label": "San Vicente y las Granadinas", "value": "San Vicente y las Granadinas" }, - { "label": "Santa Lucía", "value": "Santa Lucía" }, - { "label": "Santo Tomé y Príncipe", "value": "Santo Tomé y Príncipe" }, - { "label": "Senegal", "value": "Senegal" }, - { "label": "Serbia", "value": "Serbia" }, - { "label": "Seychelles", "value": "Seychelles" }, - { "label": "Sierra Leona", "value": "Sierra Leona" }, - { "label": "Singapur", "value": "Singapur" }, - { "label": "Siria", "value": "Siria" }, - { "label": "Somalia", "value": "Somalia" }, - { "label": "Sri Lanka", "value": "Sri Lanka" }, - { "label": "Suazilandia", "value": "Suazilandia" }, - { "label": "Sudáfrica", "value": "Sudáfrica" }, - { "label": "Sudán", "value": "Sudán" }, - { "label": "Sudán del Sur", "value": "Sudán del Sur" }, - { "label": "Suecia", "value": "Suecia" }, - { "label": "Suiza", "value": "Suiza" }, - { "label": "Surinam", "value": "Surinam" }, - { "label": "Tailandia", "value": "Tailandia" }, - { "label": "Tanzania", "value": "Tanzania" }, - { "label": "Tayikistán", "value": "Tayikistán" }, - { "label": "Timor Oriental", "value": "Timor Oriental" }, - { "label": "Togo", "value": "Togo" }, - { "label": "Tonga", "value": "Tonga" }, - { "label": "Trinidad y Tobago", "value": "Trinidad y Tobago" }, - { "label": "Túnez", "value": "Túnez" }, - { "label": "Turkmenistán", "value": "Turkmenistán" }, - { "label": "Turquía", "value": "Turquía" }, - { "label": "Tuvalu", "value": "Tuvalu" }, - { "label": "Ucrania", "value": "Ucrania" }, - { "label": "Uganda", "value": "Uganda" }, - { "label": "Uruguay", "value": "Uruguay" }, - { "label": "Uzbekistán", "value": "Uzbekistán" }, - { "label": "Vanuatu", "value": "Vanuatu" }, - { "label": "Vaticano", "value": "Vaticano" }, - { "label": "Venezuela", "value": "Venezuela" }, - { "label": "Vietnam", "value": "Vietnam" }, - { "label": "Yemen", "value": "Yemen" }, - { "label": "Yibuti", "value": "Yibuti" }, - { "label": "Zambia", "value": "Zambia" }, - { "label": "Zimbabue", "value": "Zimbabue" } -] + { + "label": "Afganistán", + "value": "Afganistán", + "dial_code": "+93", + "flag": "🇦🇫" + }, + { + "label": "Albania", + "value": "Albania", + "dial_code": "+355", + "flag": "🇦🇱" + }, + { + "label": "Alemania", + "value": "Alemania", + "dial_code": "+49", + "flag": "🇩🇪" + }, + { + "label": "Andorra", + "value": "Andorra", + "dial_code": "+376", + "flag": "🇦🇩" + }, + { + "label": "Angola", + "value": "Angola", + "dial_code": "+244", + "flag": "🇦🇴" + }, + { + "label": "Antigua y Barbuda", + "value": "Antigua y Barbuda", + "dial_code": "+1", + "flag": "🇦🇬" + }, + { + "label": "Arabia Saudita", + "value": "Arabia Saudita", + "dial_code": "+966", + "flag": "🇸🇦" + }, + { + "label": "Argelia", + "value": "Argelia", + "dial_code": "+213", + "flag": "🇩🇿" + }, + { + "label": "Argentina", + "value": "Argentina", + "dial_code": "+54", + "flag": "🇦🇷" + }, + { + "label": "Armenia", + "value": "Armenia", + "dial_code": "+374", + "flag": "🇦🇲" + }, + { + "label": "Australia", + "value": "Australia", + "dial_code": "+61", + "flag": "🇦🇺" + }, + { + "label": "Austria", + "value": "Austria", + "dial_code": "+43", + "flag": "🇦🇹" + }, + { + "label": "Azerbaiyán", + "value": "Azerbaiyán", + "dial_code": "+994", + "flag": "🇦🇿" + }, + { + "label": "Bahamas", + "value": "Bahamas", + "dial_code": "+1", + "flag": "🇧🇸" + }, + { + "label": "Bangladés", + "value": "Bangladés", + "dial_code": "+880", + "flag": "🇧🇩" + }, + { + "label": "Barbados", + "value": "Barbados", + "dial_code": "+1", + "flag": "🇧🇧" + }, + { + "label": "Baréin", + "value": "Baréin", + "dial_code": "+973", + "flag": "🇧🇭" + }, + { + "label": "Bélgica", + "value": "Bélgica", + "dial_code": "+32", + "flag": "🇧🇪" + }, + { + "label": "Belice", + "value": "Belice", + "dial_code": "+501", + "flag": "🇧🇿" + }, + { + "label": "Benín", + "value": "Benín", + "dial_code": "+229", + "flag": "🇧🇯" + }, + { + "label": "Bielorrusia", + "value": "Bielorrusia", + "dial_code": "+375", + "flag": "🇧🇾" + }, + { + "label": "Birmania", + "value": "Birmania", + "dial_code": "+95", + "flag": "🇲🇲" + }, + { + "label": "Bolivia", + "value": "Bolivia", + "dial_code": "+591", + "flag": "🇧🇴" + }, + { + "label": "Bosnia y Herzegovina", + "value": "Bosnia y Herzegovina", + "dial_code": "+387", + "flag": "🇧🇦" + }, + { + "label": "Botsuana", + "value": "Botsuana", + "dial_code": "+267", + "flag": "🇧🇼" + }, + { + "label": "Brasil", + "value": "Brasil", + "dial_code": "+55", + "flag": "🇧🇷" + }, + { + "label": "Brunéi", + "value": "Brunéi", + "dial_code": "+673", + "flag": "🇧🇳" + }, + { + "label": "Bulgaria", + "value": "Bulgaria", + "dial_code": "+359", + "flag": "🇧🇬" + }, + { + "label": "Burkina Faso", + "value": "Burkina Faso", + "dial_code": "+226", + "flag": "🇧🇫" + }, + { + "label": "Burundi", + "value": "Burundi", + "dial_code": "+257", + "flag": "🇧🇮" + }, + { + "label": "Cabo Verde", + "value": "Cabo Verde", + "dial_code": "+238", + "flag": "🇨🇻" + }, + { + "label": "Camboya", + "value": "Camboya", + "dial_code": "+855", + "flag": "🇰🇭" + }, + { + "label": "Camerún", + "value": "Camerún", + "dial_code": "+237", + "flag": "🇨🇲" + }, + { + "label": "Canadá", + "value": "Canadá", + "dial_code": "+1", + "flag": "🇨🇦" + }, + { + "label": "Catar", + "value": "Catar", + "dial_code": "+974", + "flag": "🇶🇦" + }, + { + "label": "Chad", + "value": "Chad", + "dial_code": "+235", + "flag": "🇹🇩" + }, + { + "label": "Chile", + "value": "Chile", + "dial_code": "+56", + "flag": "🇨🇱" + }, + { + "label": "China", + "value": "China", + "dial_code": "+86", + "flag": "🇨🇳" + }, + { + "label": "Chipre", + "value": "Chipre", + "dial_code": "+357", + "flag": "🇨🇾" + }, + { + "label": "Colombia", + "value": "Colombia", + "dial_code": "+57", + "flag": "🇨🇴" + }, + { + "label": "Comoras", + "value": "Comoras", + "dial_code": "+269", + "flag": "🇰🇲" + }, + { + "label": "Congo", + "value": "Congo", + "dial_code": "+242", + "flag": "🇨🇬" + }, + { + "label": "Corea del Norte", + "value": "Corea del Norte", + "dial_code": "+850", + "flag": "🇰🇵" + }, + { + "label": "Corea del Sur", + "value": "Corea del Sur", + "dial_code": "+82", + "flag": "🇰🇷" + }, + { + "label": "Costa de Marfil", + "value": "Costa de Marfil", + "dial_code": "+225", + "flag": "🇨🇮" + }, + { + "label": "Costa Rica", + "value": "Costa Rica", + "dial_code": "+506", + "flag": "🇨🇷" + }, + { + "label": "Croacia", + "value": "Croacia", + "dial_code": "+385", + "flag": "🇭🇷" + }, + { + "label": "Cuba", + "value": "Cuba", + "dial_code": "+53", + "flag": "🇨🇺" + }, + { + "label": "Dinamarca", + "value": "Dinamarca", + "dial_code": "+45", + "flag": "🇩🇰" + }, + { + "label": "Dominica", + "value": "Dominica", + "dial_code": "+1", + "flag": "🇩🇲" + }, + { + "label": "Ecuador", + "value": "Ecuador", + "dial_code": "+593", + "flag": "🇪🇨" + }, + { + "label": "Egipto", + "value": "Egipto", + "dial_code": "+20", + "flag": "🇪🇬" + }, + { + "label": "El Salvador", + "value": "El Salvador", + "dial_code": "+503", + "flag": "🇸🇻" + }, + { + "label": "Emiratos Árabes Unidos", + "value": "Emiratos Árabes Unidos", + "dial_code": "+971", + "flag": "🇦🇪" + }, + { + "label": "Eritrea", + "value": "Eritrea", + "dial_code": "+291", + "flag": "🇪🇷" + }, + { + "label": "Eslovaquia", + "value": "Eslovaquia", + "dial_code": "+421", + "flag": "🇸🇰" + }, + { + "label": "Eslovenia", + "value": "Eslovenia", + "dial_code": "+386", + "flag": "🇸🇮" + }, + { + "label": "España", + "value": "España", + "dial_code": "+34", + "flag": "🇪🇸" + }, + { + "label": "Estados Unidos", + "value": "Estados Unidos", + "dial_code": "+1", + "flag": "🇺🇸" + }, + { + "label": "Estonia", + "value": "Estonia", + "dial_code": "+372", + "flag": "🇪🇪" + }, + { + "label": "Etiopía", + "value": "Etiopía", + "dial_code": "+251", + "flag": "🇪🇹" + }, + { + "label": "Filipinas", + "value": "Filipinas", + "dial_code": "+63", + "flag": "🇵🇭" + }, + { + "label": "Finlandia", + "value": "Finlandia", + "dial_code": "+358", + "flag": "🇫🇮" + }, + { + "label": "Fiyi", + "value": "Fiyi", + "dial_code": "+679", + "flag": "🇫🇯" + }, + { + "label": "Francia", + "value": "Francia", + "dial_code": "+33", + "flag": "🇫🇷" + }, + { + "label": "Gabón", + "value": "Gabón", + "dial_code": "+241", + "flag": "🇬🇦" + }, + { + "label": "Gambia", + "value": "Gambia", + "dial_code": "+220", + "flag": "🇬🇲" + }, + { + "label": "Georgia", + "value": "Georgia", + "dial_code": "+995", + "flag": "🇬🇪" + }, + { + "label": "Ghana", + "value": "Ghana", + "dial_code": "+233", + "flag": "🇬🇭" + }, + { + "label": "Granada", + "value": "Granada", + "dial_code": "+1", + "flag": "🇬🇩" + }, + { + "label": "Grecia", + "value": "Grecia", + "dial_code": "+30", + "flag": "🇬🇷" + }, + { + "label": "Guatemala", + "value": "Guatemala", + "dial_code": "+502", + "flag": "🇬🇹" + }, + { + "label": "Guinea", + "value": "Guinea", + "dial_code": "+224", + "flag": "🇬🇳" + }, + { + "label": "Guinea Ecuatorial", + "value": "Guinea Ecuatorial", + "dial_code": "+240", + "flag": "🇬🇶" + }, + { + "label": "Guinea-Bisáu", + "value": "Guinea-Bisáu", + "dial_code": "+245", + "flag": "🇬🇼" + }, + { + "label": "Guyana", + "value": "Guyana", + "dial_code": "+592", + "flag": "🇬🇾" + }, + { + "label": "Haití", + "value": "Haití", + "dial_code": "+509", + "flag": "🇭🇹" + }, + { + "label": "Honduras", + "value": "Honduras", + "dial_code": "+504", + "flag": "🇭🇳" + }, + { + "label": "Hungría", + "value": "Hungría", + "dial_code": "+36", + "flag": "🇭🇺" + }, + { + "label": "India", + "value": "India", + "dial_code": "+91", + "flag": "🇮🇳" + }, + { + "label": "Indonesia", + "value": "Indonesia", + "dial_code": "+62", + "flag": "🇮🇩" + }, + { + "label": "Irak", + "value": "Irak", + "dial_code": "+964", + "flag": "🇮🇶" + }, + { + "label": "Irán", + "value": "Irán", + "dial_code": "+98", + "flag": "🇮🇷" + }, + { + "label": "Irlanda", + "value": "Irlanda", + "dial_code": "+353", + "flag": "🇮🇪" + }, + { + "label": "Islandia", + "value": "Islandia", + "dial_code": "+354", + "flag": "🇮🇸" + }, + { + "label": "Islas Marshall", + "value": "Islas Marshall", + "dial_code": "+692", + "flag": "🇲🇭" + }, + { + "label": "Islas Salomón", + "value": "Islas Salomón", + "dial_code": "+677", + "flag": "🇸🇧" + }, + { + "label": "Israel", + "value": "Israel", + "dial_code": "+972", + "flag": "🇮🇱" + }, + { + "label": "Italia", + "value": "Italia", + "dial_code": "+39", + "flag": "🇮🇹" + }, + { + "label": "Jamaica", + "value": "Jamaica", + "dial_code": "+1", + "flag": "🇯🇲" + }, + { + "label": "Japón", + "value": "Japón", + "dial_code": "+81", + "flag": "🇯🇵" + }, + { + "label": "Jordania", + "value": "Jordania", + "dial_code": "+962", + "flag": "🇯🇴" + }, + { + "label": "Kazajistán", + "value": "Kazajistán", + "dial_code": "+7", + "flag": "🇰🇿" + }, + { + "label": "Kenia", + "value": "Kenia", + "dial_code": "+254", + "flag": "🇰🇪" + }, + { + "label": "Kirguistán", + "value": "Kirguistán", + "dial_code": "+996", + "flag": "🇰🇬" + }, + { + "label": "Kiribati", + "value": "Kiribati", + "dial_code": "+686", + "flag": "🇰🇮" + }, + { + "label": "Kuwait", + "value": "Kuwait", + "dial_code": "+965", + "flag": "🇰🇼" + }, + { + "label": "Laos", + "value": "Laos", + "dial_code": "+856", + "flag": "🇱🇦" + }, + { + "label": "Lesoto", + "value": "Lesoto", + "dial_code": "+266", + "flag": "🇱🇸" + }, + { + "label": "Letonia", + "value": "Letonia", + "dial_code": "+371", + "flag": "🇱🇻" + }, + { + "label": "Líbano", + "value": "Líbano", + "dial_code": "+961", + "flag": "🇱🇧" + }, + { + "label": "Liberia", + "value": "Liberia", + "dial_code": "+231", + "flag": "🇱🇷" + }, + { + "label": "Libia", + "value": "Libia", + "dial_code": "+218", + "flag": "🇱🇾" + }, + { + "label": "Liechtenstein", + "value": "Liechtenstein", + "dial_code": "+423", + "flag": "🇱🇮" + }, + { + "label": "Lituania", + "value": "Lituania", + "dial_code": "+370", + "flag": "🇱🇹" + }, + { + "label": "Luxemburgo", + "value": "Luxemburgo", + "dial_code": "+352", + "flag": "🇱🇺" + }, + { + "label": "Madagascar", + "value": "Madagascar", + "dial_code": "+261", + "flag": "🇲🇬" + }, + { + "label": "Malasia", + "value": "Malasia", + "dial_code": "+60", + "flag": "🇲🇾" + }, + { + "label": "Malaui", + "value": "Malaui", + "dial_code": "+265", + "flag": "🇲🇼" + }, + { + "label": "Maldivas", + "value": "Maldivas", + "dial_code": "+960", + "flag": "🇲🇻" + }, + { + "label": "Mali", + "value": "Mali", + "dial_code": "+223", + "flag": "🇲🇱" + }, + { + "label": "Malta", + "value": "Malta", + "dial_code": "+356", + "flag": "🇲🇹" + }, + { + "label": "Marruecos", + "value": "Marruecos", + "dial_code": "+212", + "flag": "🇲🇦" + }, + { + "label": "Mauricio", + "value": "Mauricio", + "dial_code": "+230", + "flag": "🇲🇺" + }, + { + "label": "Mauritania", + "value": "Mauritania", + "dial_code": "+222", + "flag": "🇲🇷" + }, + { + "label": "México", + "value": "México", + "dial_code": "+52", + "flag": "🇲🇽" + }, + { + "label": "Micronesia", + "value": "Micronesia", + "dial_code": "+691", + "flag": "🇫🇲" + }, + { + "label": "Moldavia", + "value": "Moldavia", + "dial_code": "+373", + "flag": "🇲🇩" + }, + { + "label": "Mónaco", + "value": "Mónaco", + "dial_code": "+377", + "flag": "🇲🇨" + }, + { + "label": "Mongolia", + "value": "Mongolia", + "dial_code": "+976", + "flag": "🇲🇳" + }, + { + "label": "Montenegro", + "value": "Montenegro", + "dial_code": "+382", + "flag": "🇲🇪" + }, + { + "label": "Mozambique", + "value": "Mozambique", + "dial_code": "+258", + "flag": "🇲🇿" + }, + { + "label": "Namibia", + "value": "Namibia", + "dial_code": "+264", + "flag": "🇳🇦" + }, + { + "label": "Nauru", + "value": "Nauru", + "dial_code": "+674", + "flag": "🇳🇷" + }, + { + "label": "Nepal", + "value": "Nepal", + "dial_code": "+977", + "flag": "🇳🇵" + }, + { + "label": "Nicaragua", + "value": "Nicaragua", + "dial_code": "+505", + "flag": "🇳🇮" + }, + { + "label": "Níger", + "value": "Níger", + "dial_code": "+227", + "flag": "🇳🇪" + }, + { + "label": "Nigeria", + "value": "Nigeria", + "dial_code": "+234", + "flag": "🇳🇬" + }, + { + "label": "Noruega", + "value": "Noruega", + "dial_code": "+47", + "flag": "🇳🇴" + }, + { + "label": "Nueva Zelanda", + "value": "Nueva Zelanda", + "dial_code": "+64", + "flag": "🇳🇿" + }, + { + "label": "Omán", + "value": "Omán", + "dial_code": "+968", + "flag": "🇴🇲" + }, + { + "label": "Países Bajos", + "value": "Países Bajos", + "dial_code": "+31", + "flag": "🇳🇱" + }, + { + "label": "Pakistán", + "value": "Pakistán", + "dial_code": "+92", + "flag": "🇵🇰" + }, + { + "label": "Palaos", + "value": "Palaos", + "dial_code": "+680", + "flag": "🇵🇼" + }, + { + "label": "Palestina", + "value": "Palestina", + "dial_code": "+970", + "flag": "🇵🇸" + }, + { + "label": "Panamá", + "value": "Panamá", + "dial_code": "+507", + "flag": "🇵🇦" + }, + { + "label": "Papúa Nueva Guinea", + "value": "Papúa Nueva Guinea", + "dial_code": "+675", + "flag": "🇵🇬" + }, + { + "label": "Paraguay", + "value": "Paraguay", + "dial_code": "+595", + "flag": "🇵🇾" + }, + { + "label": "Perú", + "value": "Perú", + "dial_code": "+51", + "flag": "🇵🇪" + }, + { + "label": "Polonia", + "value": "Polonia", + "dial_code": "+48", + "flag": "🇵🇱" + }, + { + "label": "Portugal", + "value": "Portugal", + "dial_code": "+351", + "flag": "🇵🇹" + }, + { + "label": "Reino Unido", + "value": "Reino Unido", + "dial_code": "+44", + "flag": "🇬🇧" + }, + { + "label": "República Centroafricana", + "value": "República Centroafricana", + "dial_code": "+236", + "flag": "🇨🇫" + }, + { + "label": "República Checa", + "value": "República Checa", + "dial_code": "+420", + "flag": "🇨🇿" + }, + { + "label": "República Dominicana", + "value": "República Dominicana", + "dial_code": "+1", + "flag": "🇩🇴" + }, + { + "label": "República del Congo", + "value": "República del Congo", + "dial_code": "+242", + "flag": "🇨🇬" + }, + { + "label": "Ruanda", + "value": "Ruanda", + "dial_code": "+250", + "flag": "🇷🇼" + }, + { + "label": "Rumania", + "value": "Rumania", + "dial_code": "+40", + "flag": "🇷🇴" + }, + { + "label": "Rusia", + "value": "Rusia", + "dial_code": "+7", + "flag": "🇷🇺" + }, + { + "label": "Samoa", + "value": "Samoa", + "dial_code": "+685", + "flag": "🇼🇸" + }, + { + "label": "San Cristóbal y Nieves", + "value": "San Cristóbal y Nieves", + "dial_code": "+1", + "flag": "🇰🇳" + }, + { + "label": "San Marino", + "value": "San Marino", + "dial_code": "+378", + "flag": "🇸🇲" + }, + { + "label": "San Vicente y las Granadinas", + "value": "San Vicente y las Granadinas", + "dial_code": "+1", + "flag": "🇻🇨" + }, + { + "label": "Santa Lucía", + "value": "Santa Lucía", + "dial_code": "+1", + "flag": "🇱🇨" + }, + { + "label": "Santo Tomé y Príncipe", + "value": "Santo Tomé y Príncipe", + "dial_code": "+239", + "flag": "🇸🇹" + }, + { + "label": "Senegal", + "value": "Senegal", + "dial_code": "+221", + "flag": "🇸🇳" + }, + { + "label": "Serbia", + "value": "Serbia", + "dial_code": "+381", + "flag": "🇷🇸" + }, + { + "label": "Seychelles", + "value": "Seychelles", + "dial_code": "+248", + "flag": "🇸🇨" + }, + { + "label": "Sierra Leona", + "value": "Sierra Leona", + "dial_code": "+232", + "flag": "🇸🇱" + }, + { + "label": "Singapur", + "value": "Singapur", + "dial_code": "+65", + "flag": "🇸🇬" + }, + { + "label": "Siria", + "value": "Siria", + "dial_code": "+963", + "flag": "🇸🇾" + }, + { + "label": "Somalia", + "value": "Somalia", + "dial_code": "+252", + "flag": "🇸🇴" + }, + { + "label": "Sri Lanka", + "value": "Sri Lanka", + "dial_code": "+94", + "flag": "🇱🇰" + }, + { + "label": "Suazilandia", + "value": "Suazilandia", + "dial_code": "+268", + "flag": "🇸🇿" + }, + { + "label": "Sudáfrica", + "value": "Sudáfrica", + "dial_code": "+27", + "flag": "🇿🇦" + }, + { + "label": "Sudán", + "value": "Sudán", + "dial_code": "+249", + "flag": "🇸🇩" + }, + { + "label": "Sudán del Sur", + "value": "Sudán del Sur", + "dial_code": "+211", + "flag": "🇸🇸" + }, + { + "label": "Suecia", + "value": "Suecia", + "dial_code": "+46", + "flag": "🇸🇪" + }, + { + "label": "Suiza", + "value": "Suiza", + "dial_code": "+41", + "flag": "🇨🇭" + }, + { + "label": "Surinam", + "value": "Surinam", + "dial_code": "+597", + "flag": "🇸🇷" + }, + { + "label": "Tailandia", + "value": "Tailandia", + "dial_code": "+66", + "flag": "🇹🇭" + }, + { + "label": "Tanzania", + "value": "Tanzania", + "dial_code": "+255", + "flag": "🇹🇿" + }, + { + "label": "Tayikistán", + "value": "Tayikistán", + "dial_code": "+992", + "flag": "🇹🇯" + }, + { + "label": "Timor Oriental", + "value": "Timor Oriental", + "dial_code": "+670", + "flag": "🇹🇱" + }, + { + "label": "Togo", + "value": "Togo", + "dial_code": "+228", + "flag": "🇹🇬" + }, + { + "label": "Tonga", + "value": "Tonga", + "dial_code": "+676", + "flag": "🇹🇴" + }, + { + "label": "Trinidad y Tobago", + "value": "Trinidad y Tobago", + "dial_code": "+1", + "flag": "🇹🇹" + }, + { + "label": "Túnez", + "value": "Túnez", + "dial_code": "+216", + "flag": "🇹🇳" + }, + { + "label": "Turkmenistán", + "value": "Turkmenistán", + "dial_code": "+993", + "flag": "🇹🇲" + }, + { + "label": "Turquía", + "value": "Turquía", + "dial_code": "+90", + "flag": "🇹🇷" + }, + { + "label": "Tuvalu", + "value": "Tuvalu", + "dial_code": "+688", + "flag": "🇹🇻" + }, + { + "label": "Ucrania", + "value": "Ucrania", + "dial_code": "+380", + "flag": "🇺🇦" + }, + { + "label": "Uganda", + "value": "Uganda", + "dial_code": "+256", + "flag": "🇺🇬" + }, + { + "label": "Uruguay", + "value": "Uruguay", + "dial_code": "+598", + "flag": "🇺🇾" + }, + { + "label": "Uzbekistán", + "value": "Uzbekistán", + "dial_code": "+998", + "flag": "🇺🇿" + }, + { + "label": "Vanuatu", + "value": "Vanuatu", + "dial_code": "+678", + "flag": "🇻🇺" + }, + { + "label": "Vaticano", + "value": "Vaticano", + "dial_code": "+379", + "flag": "🇻🇦" + }, + { + "label": "Venezuela", + "value": "Venezuela", + "dial_code": "+58", + "flag": "🇻🇪" + }, + { + "label": "Vietnam", + "value": "Vietnam", + "dial_code": "+84", + "flag": "🇻🇳" + }, + { + "label": "Yemen", + "value": "Yemen", + "dial_code": "+967", + "flag": "🇾🇪" + }, + { + "label": "Yibuti", + "value": "Yibuti", + "dial_code": "+253", + "flag": "🇩🇯" + }, + { + "label": "Zambia", + "value": "Zambia", + "dial_code": "+260", + "flag": "🇿🇲" + }, + { + "label": "Zimbabue", + "value": "Zimbabue", + "dial_code": "+263", + "flag": "🇿🇼" + } +] \ No newline at end of file diff --git a/src/components/forms/DynamicForm.vue b/src/components/forms/DynamicForm.vue index 94f2c68..97bb2b0 100644 --- a/src/components/forms/DynamicForm.vue +++ b/src/components/forms/DynamicForm.vue @@ -5,7 +5,8 @@ import { createTranslator } from "../../i18n"; const props = defineProps({ locale: String, - formConfigUrl: { type: String, required: false, default: "/forms/formulario-inscripcion.json" } + formConfigUrl: { type: String, required: false, default: "/forms/formulario-inscripcion.json" }, + turnstileSiteKey: { type: String, required: false, default: "" } }); const tl = createTranslator(props.locale); @@ -25,6 +26,12 @@ const reglamentoLoading = ref(false); const reglamentoScrolled = ref(false); const reglamentoFetchAttempted = ref(false); +const turnstileToken = ref(""); +const turnstileContainer = ref(null); +const honeypot = ref(""); + +const turnstileReady = ref(false); + const totalSteps = computed(() => config.value?.steps?.length ?? 0); const currentStepData = computed(() => config.value?.steps?.[currentStep.value] ?? null); @@ -32,6 +39,8 @@ const isFirstStep = computed(() => currentStep.value === 0); const isLastStep = computed(() => currentStep.value === totalSteps.value - 1); const stepErrors = reactive({}); +const formatErrors = reactive({}); +const phoneParts = reactive({}); const fetchConfig = async () => { isSuccess.value = false; @@ -44,6 +53,9 @@ const fetchConfig = async () => { step.fields.forEach((field) => { if (field.type === "checkbox") { formData[field.key] = []; + } else if (field.type === "phone_country") { + formData[field.key] = ""; + phoneParts[field.key] = { code: "", number: "" }; } else { formData[field.key] = ""; } @@ -54,7 +66,37 @@ const fetchConfig = async () => { } }; -onMounted(fetchConfig); +const initTurnstile = () => { + if (!props.turnstileSiteKey || !turnstileContainer.value) return; + if (window.turnstile) { + window.turnstile.render(turnstileContainer.value, { + sitekey: props.turnstileSiteKey, + callback: (token) => { turnstileToken.value = token; }, + }); + turnstileReady.value = true; + } +}; + +onMounted(() => { + fetchConfig(); + if (props.turnstileSiteKey) { + if (document.querySelector('script[src*="turnstile"]')) { + const check = setInterval(() => { + if (window.turnstile) { + clearInterval(check); + initTurnstile(); + } + }, 100); + } else { + const script = document.createElement("script"); + script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js"; + script.async = true; + script.defer = true; + script.onload = initTurnstile; + document.head.appendChild(script); + } + } +}); const validateStep = (stepIndex) => { const step = config.value?.steps?.[stepIndex]; @@ -111,6 +153,37 @@ const validateStep = (stepIndex) => { stepErrors[field.key] = false; } } + if (field.type === "phone" && val && val.trim() !== "") { + const phoneRegex = /^\d+$/; + if (!phoneRegex.test(val.trim())) { + formatErrors[field.key] = true; + stepErrors[field.key] = true; + valid = false; + } else { + formatErrors[field.key] = false; + } + } + if (field.type === "phone_country") { + const parts = phoneParts[field.key]; + if (field.required && (!parts || !parts.code || !parts.number)) { + stepErrors[field.key] = true; + valid = false; + } else if (parts && parts.number) { + const full = parts.code + parts.number; + if (full.length > 15) { + formatErrors[field.key] = true; + stepErrors[field.key] = true; + valid = false; + } else if (parts.number.length < 7) { + formatErrors[field.key] = true; + stepErrors[field.key] = true; + valid = false; + } else { + stepErrors[field.key] = false; + formatErrors[field.key] = false; + } + } + } if (field.type === "checkbox" && field.levels && Array.isArray(val)) { val.forEach((optVal) => { if (!formData[`${field.key}_nivel_${optVal}`]) { @@ -186,7 +259,7 @@ const buildPayload = () => { } }); }); - return { formData: { ...formData }, responses }; + return { formData: { ...formData }, responses, turnstileToken: turnstileToken.value, honeypot: honeypot.value }; }; const handleSubmit = async () => { @@ -225,6 +298,29 @@ const handleSubmit = async () => { const getFieldError = (key) => stepErrors[key] || false; +const onPhoneInput = (key, event) => { + const cleaned = event.target.value.replace(/\D/g, ""); + formData[key] = cleaned; + stepErrors[key] = false; + formatErrors[key] = false; +}; + +const onPhoneCountryCodeChange = (key, code) => { + if (!phoneParts[key]) return; + phoneParts[key].code = code; + formData[key] = code + phoneParts[key].number; + stepErrors[key] = false; +}; + +const onPhoneCountryNumberInput = (key, event) => { + if (!phoneParts[key]) return; + const cleaned = event.target.value.replace(/\D/g, ""); + event.target.value = cleaned; + phoneParts[key].number = cleaned; + formData[key] = phoneParts[key].code + cleaned; + stepErrors[key] = false; +}; + const suggestionsCache = ref({}); const showSuggestions = reactive({}); @@ -448,7 +544,7 @@ const getFieldInputType = (field) => { :type="getFieldInputType(field)" v-model="formData[field.key]" :placeholder="tl(field.placeholder || field.label)" - @input="stepErrors[field.key] = false" + @input="field.type === 'phone' ? onPhoneInput(field.key, $event) : (stepErrors[field.key] = false)" /> @@ -485,6 +581,32 @@ const getFieldInputType = (field) => { +
+
+ + +
+
+
@@ -600,7 +722,7 @@ const getFieldInputType = (field) => { class="text-xs text-red-500 flex items-center gap-1 mt-0.5" > - {{ tl("form.required") }} + {{ formatErrors[field.key] ? (field.type === "phone_country" ? tl("form.phone_number_invalid") : tl("form.phone_invalid")) : tl("form.required") }}

@@ -610,6 +732,19 @@ const getFieldInputType = (field) => { {{ errorMsg }} +
+
+
+ +
diff --git a/src/i18n/es.json b/src/i18n/es.json index 97b50fe..6db6054 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -252,6 +252,7 @@ "form.idioma.ing": "Inglés", "form.idioma.heb": "Hebreo", "form.idioma.port": "Portugués", + "form.idioma.franc": "Francés", "form.idioma.otro": "Otro", "form.idioma_otro": "Otro (especificar)", "form.voluntariado_anterior": "¿Ha realizado voluntariado anteriormente?", @@ -322,6 +323,9 @@ "form.success": "¡Enviado con éxito!", "form.error": "Error al enviar", "form.required": "Este campo es obligatorio", + "form.phone_invalid": "Solo se permiten números", + "form.phone_number_invalid": "Número inválido (mín. 7 dígitos, máx. 15)", + "form.phone_code_select": "Código", "form.config_error": "Error al cargar el formulario. Intentá de nuevo más tarde.", "form.si": "Sí", "form.no": "No", diff --git a/src/pages/[locale]/[section]/index.astro b/src/pages/[locale]/[section]/index.astro index 53275fa..33296c6 100644 --- a/src/pages/[locale]/[section]/index.astro +++ b/src/pages/[locale]/[section]/index.astro @@ -60,7 +60,7 @@ if (routeKey === "formulario") {

{tl("form.page_title")}

- +
)} diff --git a/src/pages/api/formulario/send.ts b/src/pages/api/formulario/send.ts index bcf3d63..6204c10 100644 --- a/src/pages/api/formulario/send.ts +++ b/src/pages/api/formulario/send.ts @@ -67,6 +67,34 @@ export const POST: APIRoute = async ({ request }) => { const body = await request.json(); const formData = body.formData || {}; + const honeypot = body.honeypot || ""; + if (honeypot) { + return Response.json( + { success: false, message: "Solicitud rechazada" }, + { status: 403 } + ); + } + + const turnstileToken = body.turnstileToken || ""; + const turnstileSecret = import.meta.env.TURNSTILE_SECRET_KEY; + if (turnstileSecret && turnstileToken) { + const verify = await fetch( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: `secret=${turnstileSecret}&response=${turnstileToken}`, + } + ); + const outcome = await verify.json(); + if (!outcome.success) { + return Response.json( + { success: false, message: "Captcha inválido" }, + { status: 403 } + ); + } + } + const timestamp = new Date().toLocaleString("es-ES", { timeZone: "America/Puerto_Rico", });