Compare commits

..

43 Commits

Author SHA1 Message Date
Esteban Paz 8fee1741ab Merge pull request 'create_form' (#48) from create_form into main
Deploy cdrdpyj / deploy (push) Successful in 58s Details
Reviewed-on: #48
2026-07-28 11:50:56 +00:00
Esteban Paz f4cce20233 merge main content 2026-07-28 06:49:32 -05:00
Esteban a5ca98ef49 Merge branch 'create_form' of https://gitea.carpa.com/LGCC/cdrdpyj into create_form 2026-07-27 09:00:14 -05:00
Esteban 3312ed020e change port for production 2026-07-27 08:59:19 -05:00
Esteban Paz bb1e3c2b3d change ortigrafia argentina 2026-07-26 11:30:38 -05:00
Esteban Paz 5ca8d9d4c3 change select_placeholder 2026-07-26 11:20:45 -05:00
Esteban Paz 63ba680af9 change documentos text es.json 2026-07-26 11:18:42 -05:00
Esteban Paz af83acf332 multple fixes from QA testers 2026-07-26 11:12:45 -05:00
Esteban Paz 738cc5e491 change label apellido 2026-07-25 10:41:14 -05:00
Esteban Paz 8e78083fa8 add phone numbers to telefono 2026-07-25 10:19:21 -05:00
Esteban Paz d80e01ad26 feat: add PR to countries, add second name and add full_name like value 2026-07-25 09:44:55 -05:00
Esteban Paz 9423e54ebf change pnpm 2026-07-24 21:38:30 -05:00
Esteban Paz 49dfb0d96f add search code country for whatsapp 2026-07-24 21:04:44 -05:00
Esteban Paz f93f9fd3ce feat: create send email confirmation, add code country, and other fixes 2026-07-24 20:34:36 -05:00
David Ascanio 7f146a3cf9 send email to voluteer 2026-07-24 22:12:58 -03:00
Esteban Paz 76f3246a43 create corepack enable and pnpm install --prod 2026-07-24 15:16:07 -05:00
Esteban Paz fa41b72c90 change source and pnpm install for project 2026-07-24 14:46:22 -05:00
Esteban Paz 69dd1471c1 change md again x2 2026-07-24 13:58:53 -05:00
Esteban Paz 4f29b1a2bd change md again 2026-07-24 13:54:45 -05:00
Esteban Paz 37766fa994 change path of deploy vps 2026-07-24 13:36:05 -05:00
Esteban Paz 10b478b136 change md for take deploy 2026-07-24 13:28:46 -05:00
Esteban Paz ef0e39ffff Merge pull request 'staging' (#31) from staging into create_form
Reviewed-on: #31
2026-07-24 18:28:28 +00:00
Esteban Paz 7da461421b change md for take deploy
Deploy cdrdpyj / deploy (push) Failing after 53s Details
2026-07-24 12:53:33 -05:00
Esteban Paz 62ac03340a change path for dev
Deploy cdrdpyj / deploy (push) Successful in 50s Details
2026-07-24 12:04:12 -05:00
Esteban Paz 987a58242c change md for take deploy
Deploy cdrdpyj / deploy (push) Successful in 50s Details
2026-07-24 11:59:10 -05:00
Esteban Paz 184edac52f change node version to 22
Deploy cdrdpyj / deploy (push) Successful in 53s Details
2026-07-24 11:55:07 -05:00
Esteban Paz ee7cb4332d change pnpm version
Deploy cdrdpyj / deploy (push) Failing after 8s Details
2026-07-24 11:53:03 -05:00
Esteban Paz 97001f3f61 change something for test
Deploy cdrdpyj / deploy (push) Failing after 9s Details
2026-07-24 11:38:49 -05:00
Esteban Paz 4bb7347f51 Merge pull request 'fix: name in package.json' (#30) from create_form into staging
Deploy cdrdpyj / deploy (push) Failing after 7s Details
Reviewed-on: #30
2026-07-24 16:34:24 +00:00
Esteban Paz 64f6a7be02 fix: name in package.json 2026-07-24 11:34:03 -05:00
Esteban Paz 86b6d92339 Merge pull request 'fix to node deploy' (#29) from create_form into staging
Deploy cdrdpyj / deploy (push) Failing after 7s Details
Reviewed-on: #29
2026-07-24 16:31:59 +00:00
Esteban Paz af2e32ab27 fix to node deploy 2026-07-24 11:31:24 -05:00
Esteban Paz 540083f099 Merge pull request 'change node version for pull cicd' (#28) from create_form into staging
Deploy cdrdpyj / deploy (push) Failing after 11s Details
Reviewed-on: #28
2026-07-24 16:28:42 +00:00
Esteban Paz d9b0bc58b1 change node version for pull cicd 2026-07-24 11:26:41 -05:00
Esteban Paz aeb32e4561 Merge pull request 'feat: change required for whatsapp and state, add dropdown for countries and add json with all countries' (#27) from create_form into staging
Deploy cdrdpyj / deploy (push) Failing after 7s Details
Reviewed-on: #27
2026-07-24 15:58:38 +00:00
Esteban Paz f4d128306a feat: change required for whatsapp and state, add dropdown for countries and add json with all countries 2026-07-24 10:57:54 -05:00
Esteban Paz 509b194e90 Merge pull request 'change something' (#26) from create_form into staging
Reviewed-on: #26
2026-07-24 08:27:49 +00:00
Esteban Paz d830bdcfb8 change something 2026-07-24 03:27:29 -05:00
Esteban Paz f2923b6b30 merge 2026-07-24 03:23:52 -05:00
Esteban Paz 25de7a53c1 create autodeploy 2026-07-24 03:22:32 -05:00
Esteban Paz 99f870808b Merge pull request 'new send emails' (#24) from new-form into create_form
Reviewed-on: #24
2026-07-24 08:10:19 +00:00
Esteban Paz 0e35531ec7 feat: conectar frontend a Google Sheets y ajustar responsive
- Agregar API route /api/formulario/send con mapeo de 46 columnas
- Agregar appendRegistrationToSheet() para escribir en tab VOLUNTARIOS
- Reemplazar simulación de envío por fetch real al endpoint
- Corregir validación de checkboxes, email y niveles
- Ajustar stepper para pantallas de laptop (gap, círculos, labels)
- Corregir header en iPad: ocultar hamburguesa y mostrar idioma desde md
2026-07-24 03:08:42 -05:00
David Ascanio 261473c60c new send emails 2026-07-23 00:08:21 -03:00
21 changed files with 2822 additions and 363 deletions

7
.env
View File

@ -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

6
.env.example Normal file
View File

@ -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...

View File

@ -1,14 +1,59 @@
name: Centro del Reino de Paz y Justicia
name: Deploy cdrdpyj
on: [push]
on:
push:
branches: [staging, production]
jobs:
explore-gitea-actions:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: List files in the repository
- uses: actions/checkout@v4
- name: Set target dir & PM2 env
run: |
ls ${{ gitea.workspace }}
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
if [ "${{ gitea.ref_name }}" = "production" ]; then
echo "TARGET_DIR=/var/www/node/cdrdpyj" >> $GITHUB_ENV
echo "PM2_ENV=production" >> $GITHUB_ENV
else
echo "TARGET_DIR=/var/www/node/dev.cdrdjyp" >> $GITHUB_ENV
echo "PM2_ENV=staging" >> $GITHUB_ENV
fi
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- name: Copy build to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
source: "dist/*,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,ecosystem.config.cjs"
target: "${{ env.TARGET_DIR }}"
- name: Restart PM2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
set -e
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
npm install -g pnpm@11 2>&1
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

1
.gitignore vendored
View File

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

View File

@ -44,4 +44,4 @@ Feel free to check [our documentation](https://docs.astro.build) or jump into ou
Adding gitea/workflows/deploy.yaml to test github ci/cd runner
Testing runner
Testing runner change spmeting v12

18
ecosystem.config.cjs Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
apps: [{
name: "cdrdpyj",
script: "dist/server/entry.mjs",
env: {
NODE_ENV: "staging",
PORT: 3310,
TURNSTILE_SITE_KEY: "",
TURNSTILE_SECRET_KEY: "",
},
env_production: {
NODE_ENV: "production",
PORT: 4321,
TURNSTILE_SITE_KEY: "",
TURNSTILE_SECRET_KEY: "",
}
}]
};

View File

@ -1,5 +1,5 @@
{
"name": "",
"name": "cdrdpyj",
"type": "module",
"version": "0.0.1",
"scripts": {

View File

@ -3,14 +3,36 @@
"submit_label": "form.submit",
"steps": [
{
"label": "form.step1",
"label": "form.step1_reglamento",
"fields": [
{
"key": "aceptacion_reglamento",
"label": "form.aceptacion_reglamento",
"type": "checkbox",
"required": true,
"options": [
{ "value": "leido_acepto", "label": "form.aceptacion.leido", "required": true },
{ "value": "codigo_etica", "label": "form.aceptacion.codigo_etica", "required": true },
{ "value": "datos_personales", "label": "form.aceptacion.datos", "required": true },
{ "value": "participacion_voluntaria", "label": "form.aceptacion.voluntaria", "required": true },
{ "value": "informacion_verdadera", "label": "form.aceptacion.verdadera", "required": true },
{ "value": "firma_electronica_validez", "label": "form.aceptacion.firma_validez", "required": true }
]
}
]
},
{
"label": "form.step2",
"column": 2,
"fields": [
{ "key": "nombre", "label": "form.nombre", "type": "text", "required": true },
{ "key": "segundo_nombre", "label": "form.segundo_nombre", "type": "text" },
{ "key": "apellido", "label": "form.apellido", "type": "text", "required": true },
{ "key": "documentos", "label": "form.documentos", "type": "text", "required": true },
{ "key": "correo", "label": "form.correo", "type": "email", "required": true, "colspan": 2 },
{ "key": "reglamento_firma", "label": "form.reglamento_firma", "type": "text", "required": true, "colspan": 2, "placeholder": "form.reglamento_firma_placeholder" },
{ "key": "fecha_nacimiento", "label": "form.fecha_nacimiento", "type": "date", "required": true },
{ "key": "nacionalidad", "label": "form.nacionalidad", "type": "text", "required": true },
{ "key": "documentos", "label": "form.documentos", "type": "text", "required": true, "colspan": 2 },
{ "key": "nacionalidad", "label": "form.nacionalidad", "type": "autocomplete", "required": true, "source": "/forms/paises.json" },
{
"key": "sexo",
"label": "form.sexo",
@ -19,22 +41,21 @@
"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 },
{ "key": "direccion_postal", "label": "form.direccion_postal", "type": "text", "required": false, "colspan": 2 },
{ "key": "ciudad", "label": "form.ciudad", "type": "text", "required": true },
{ "key": "estado", "label": "form.estado", "type": "text", "required": true },
{ "key": "pais", "label": "form.pais", "type": "text", "required": true },
{ "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", "required": true },
{ "key": "email", "label": "form.correo", "type": "email", "required": true, "colspan": 2 }
{ "key": "telefono", "label": "form.mobile", "type": "phone_country", "required": true, "source": "/forms/paises.json" },
{ "key": "whatsapp", "label": "form.whatsapp", "type": "phone_country", "source": "/forms/paises.json" }
]
},
{
"label": "form.step2",
"label": "form.step3",
"column": 2,
"fields": [
{ "key": "profesion", "label": "form.profesion", "type": "text", "required": true },
@ -58,6 +79,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" }
]
},
@ -72,7 +94,7 @@
]
},
{
"label": "form.step3",
"label": "form.step4",
"fields": [
{
"key": "areas_colaborar",
@ -101,6 +123,127 @@
},
{ "key": "areas_otra", "label": "form.areas_otra", "type": "text", "required": false, "showWhen": { "field": "areas_colaborar", "value": "Otra" } }
]
},
{
"label": "form.step5",
"fields": [
{
"key": "dias_disponibles",
"label": "form.dias_disponibles",
"type": "checkbox",
"required": true,
"options": [
{ "value": "Lunes", "label": "form.dias.lunes" },
{ "value": "Martes", "label": "form.dias.martes" },
{ "value": "Miercoles", "label": "form.dias.miercoles" },
{ "value": "Jueves", "label": "form.dias.jueves" },
{ "value": "Viernes", "label": "form.dias.viernes" },
{ "value": "Sabado", "label": "form.dias.sabado" },
{ "value": "Domingo", "label": "form.dias.domingo" }
]
},
{
"key": "horario_preferido",
"label": "form.horario_preferido",
"type": "checkbox",
"required": true,
"options": [
{ "value": "Manana", "label": "form.horario.manana" },
{ "value": "Tarde", "label": "form.horario.tarde" },
{ "value": "Noche", "label": "form.horario.noche" },
{ "value": "Segun necesidades", "label": "form.horario.segun" }
]
},
{
"key": "fuera_ciudad",
"label": "form.fuera_ciudad",
"type": "radio",
"required": true,
"options": [
{ "value": "si", "label": "form.si" },
{ "value": "no", "label": "form.no" }
]
},
{
"key": "misiones_internacionales",
"label": "form.misiones_internacionales",
"type": "radio",
"required": true,
"options": [
{ "value": "si", "label": "form.si" },
{ "value": "no", "label": "form.no" }
]
}
]
},
{
"label": "form.step6",
"fields": [
{
"key": "condicion_medica",
"label": "form.condicion_medica",
"type": "radio",
"required": true,
"options": [
{ "value": "si", "label": "form.si" },
{ "value": "no", "label": "form.no" }
]
},
{
"key": "condicion_medica_cual",
"label": "form.condicion_medica_cual",
"type": "textarea",
"required": true,
"showWhen": { "field": "condicion_medica", "value": "si" }
},
{
"key": "alergias",
"label": "form.alergias",
"type": "textarea",
"required": false
},
{
"key": "medicamentos",
"label": "form.medicamentos",
"type": "textarea",
"required": false
}
]
},
{
"label": "form.step7",
"fields": [
{ "key": "emergencia_nombre", "label": "form.emergencia_nombre", "type": "text", "required": true },
{ "key": "emergencia_parentesco", "label": "form.emergencia_parentesco", "type": "text", "required": true },
{ "key": "emergencia_telefono", "label": "form.emergencia_telefono", "type": "phone_country", "required": true, "source": "/forms/paises.json" },
{ "key": "emergencia_correo", "label": "form.emergencia_correo", "type": "email", "required": false }
]
},
{
"label": "form.step8",
"column": 2,
"fields": [
{ "key": "confirmar_nombre", "label": "form.confirmar_nombre", "type": "text", "readonly": true, "valueFrom": "nombre_completo" },
{ "key": "confirmar_firma", "label": "form.confirmar_firma", "type": "text", "readonly": true, "valueFrom": "reglamento_firma" },
{
"key": "declaracion_voluntario",
"label": "form.declaracion_voluntario",
"type": "checkbox",
"required": true,
"colspan": 2,
"options": [
{ "value": "informacion_verdadera", "label": "form.declaracion.verdadera", "required": true },
{ "value": "acepto_reglamento", "label": "form.declaracion.acepto_reglamento", "required": true },
{ "value": "compromiso_etica", "label": "form.declaracion.compromiso_etica", "required": true },
{ "value": "autorizo_datos", "label": "form.declaracion.autorizo_datos", "required": true },
{ "value": "confidencialidad", "label": "form.declaracion.confidencialidad", "required": true },
{ "value": "participacion_voluntaria", "label": "form.declaracion.voluntaria", "required": true }
]
},
{ "key": "confirmacion", "label": "form.confirmacion", "type": "checkbox", "colspan": 2, "required": true, "options": [
{ "value": "confirmado", "label": "form.confirmacion_label", "required": true }
] }
]
}
]
}

1166
public/forms/paises.json Normal file

File diff suppressed because it is too large Load Diff

187
public/reglamento/es.html Normal file
View File

@ -0,0 +1,187 @@
<h1>Reglamento General del Programa de Voluntariado</h1>
<p>Términos, Condiciones y Código de Ética para Voluntarios</p>
<section id="introduccion">
<h2>Introducción</h2>
<p>El Centro del Reino de Paz y Justicia (CRPJ) tiene como misión promover la paz, la justicia, el liderazgo, la educación, la ayuda humanitaria, la responsabilidad social y la cooperación entre comunidades y naciones.</p>
<p>El presente Reglamento establece el marco jurídico, ético e institucional que regula la participación de los voluntarios, definiendo sus derechos, obligaciones y principios de actuación, con el fin de garantizar que todas las actividades de voluntariado se desarrollen conforme a los valores del CRPJ y a la legislación aplicable.</p>
</section>
<section id="objeto-voluntariado">
<h2>1. Objeto del Voluntariado</h2>
<p>El voluntario participa libre y participa libremente, por un deseo propio y personal, en las actividades del CRPJ con el propósito de contribuir al cumplimiento de la misión institucional en las áreas de educación, desarrollo comunitario, asistencia humanitaria, liderazgo, diplomacia pública, promoción de la paz, la justicia y el servicio a la sociedad.</p>
<p>Toda actividad deberá desarrollarse con espíritu de servicio, responsabilidad, integridad, respeto, cooperación y compromiso con el bienestar común.</p>
</section>
<section id="naturaleza-voluntariado">
<h2>2. Naturaleza del Voluntariado</h2>
<p>La labor de quien participa tiene carácter exclusivamente voluntario y no constituye, bajo ninguna circunstancia, una relación laboral, contractual, societaria, de representación, mandato o subordinación con el CRPJ.</p>
<p>La participación en las actividades no genera derecho a salario, prestaciones laborales, seguridad social, indemnizaciones ni cualquier otra remuneración económica, salvo autorización expresa y por escrito del CRPJ respecto del reembolso de gastos.</p>
</section>
<section id="requisitos-voluntario">
<h2>3. Requisitos para ser Voluntario</h2>
<p>El voluntario declara que:</p>
<ul>
<li>La información proporcionada por el voluntario es veraz, completa y actualizada.</li>
<li>Se encuentra física y mentalmente apto para participar en las actividades.</li>
<li>No existe impedimento legal para su participación.</li>
<li>Cumplirá las políticas, reglamentos y procedimientos establecidos por el CRPJ.</li>
</ul>
</section>
<section id="seguro">
<h2>4. Seguro</h2>
<p>Salvo comunicación expresa y por escrito del CRPJ, cada voluntario será responsable de contar con la cobertura médica, de accidentes y demás seguros que considere necesarios para su participación.</p>
<p>La contratación de cualquier seguro por parte del CRPJ no sustituye la responsabilidad personal del voluntario.</p>
</section>
<section id="responsabilidad-medica">
<h2>5. Responsabilidad Médica</h2>
<p>El voluntario manifiesta que su estado de salud le permite participar en las actividades y se compromete a informar oportunamente cualquier condición médica que pueda afectar su seguridad o la de terceros.</p>
</section>
<section id="cumplimiento-normas">
<h2>6. Cumplimiento de Normas</h2>
<p>El voluntario se compromete a cumplir las instrucciones impartidas por los responsables del CRPJ, respetar las normas de seguridad y actuar siempre con responsabilidad, profesionalismo y respeto hacia todas las personas.</p>
</section>
<section id="confidencialidad">
<h2>7. Confidencialidad</h2>
<p>Toda la información a la que el voluntario tenga acceso durante su participación será considerada confidencial.</p>
<p>El voluntario se obliga a no divulgar información institucional, financiera, estratégica, personal o cualquier otra información reservada, aun después de finalizar su colaboración con el CRPJ.</p>
</section>
<section id="proteccion-datos">
<h2>8. Protección de Datos Personales</h2>
<p>El voluntario autoriza al CRPJ a recopilar, almacenar y tratar sus datos personales exclusivamente para fines relacionados con:</p>
<ul>
<li>la administración del programa de voluntariado;</li>
<li>la comunicación institucional;</li>
<li>la organización de actividades;</li>
<li>el cumplimiento de obligaciones legales;</li>
<li>la seguridad de los participantes.</li>
</ul>
</section>
<section id="uso-imagen">
<h2>9. Uso de Imagen</h2>
<p>El voluntario autoriza al CRPJ a utilizar fotografías, grabaciones de audio, videos e imágenes obtenidas durante las actividades para fines institucionales, educativos, informativos, promocionales y de recaudación de fondos, salvo manifestación expresa y escrita en caso contrario.</p>
</section>
<section id="propiedad-intelectual">
<h2>10. Propiedad Intelectual</h2>
<p>Todo documento, fotografía, video, material audiovisual, presentación, publicación, contenido o trabajo desarrollado por el voluntario dentro de las actividades institucionales será propiedad exclusiva del CRPJ, salvo acuerdo escrito en caso contrario.</p>
</section>
<section id="uso-bienes">
<h2>11. Uso y Cuidado de Bienes</h2>
<p>El voluntario deberá utilizar responsablemente los bienes, equipos y materiales del CRPJ, comprometiéndose a conservarlos y devolverlos cuando le sean requeridos o al finalizar su participación.</p>
</section>
<section id="terminacion-voluntariado">
<h2>12. Terminación del Voluntariado</h2>
<p>El CRPJ podrá suspender o finalizar la participación de cualquier voluntario cuando exista incumplimiento del presente Reglamento, conducta incompatible con los valores institucionales, riesgos para la organización o cualquier otra causa justificada.</p>
<p>El voluntario podrá retirarse libremente notificándolo con una anticipación razonable.</p>
</section>
<section id="limitacion-responsabilidad">
<h2>13. Limitación de Responsabilidad</h2>
<p>El voluntario reconoce que participa libremente en las actividades del CRPJ y acepta los riesgos normales inherentes a las mismas.</p>
<p>En la medida permitida por la legislación aplicable, el CRPJ, sus directivos, empleados, representantes, colaboradores y demás voluntarios no serán responsables por daños derivados de la participación del voluntario, salvo en casos de dolo o negligencia grave.</p>
</section>
<section id="declaracion-riesgos">
<h2>14. Declaración de Asunción de Riesgos</h2>
<p>El voluntario declara conocer que determinadas actividades pueden implicar riesgos derivados de desplazamientos, trabajo comunitario, asistencia humanitaria, eventos públicos, visitas institucionales u otras actividades propias del programa.</p>
<p>El voluntario acepta participar bajo su propia responsabilidad y asume voluntariamente dichos riesgos, salvo cuando exista dolo o negligencia grave imputable al CRPJ.</p>
</section>
<section id="conducta-institucional">
<h2>15. Conducta Institucional</h2>
<p>El voluntario se compromete a mantener una conducta acorde con los principios y valores del CRPJ.</p>
<p>Queda prohibido:</p>
<ul>
<li>realizar actos de violencia física o verbal;</li>
<li>discriminar o acosar a cualquier persona;</li>
<li>consumir drogas ilegales o presentarse bajo sus efectos;</li>
<li>portar armas sin autorización legal y expresa;</li>
<li>solicitar donaciones o administrar recursos económicos en nombre del CRPJ sin autorización escrita;</li>
<li>utilizar el nombre del CRPJ para fines personales, políticos o comerciales.</li>
</ul>
</section>
<section id="proteccion-menores">
<h2>16. Protección de Menores y Personas Vulnerables</h2>
<p>Cuando las actividades involucren menores de edad o personas en situación de vulnerabilidad, el voluntario deberá cumplir estrictamente los protocolos de protección establecidos por el CRPJ y la legislación aplicable.</p>
</section>
<section id="conflicto-intereses">
<h2>17. Conflicto de Intereses</h2>
<p>El voluntario deberá informar cualquier situación personal, profesional, económica o familiar que pudiera generar un conflicto de interés con las actividades del CRPJ.</p>
</section>
<section id="cumplimiento-normativo">
<h2>18. Cumplimiento Normativo</h2>
<p>El voluntario se compromete a cumplir toda la legislación aplicable relacionada con derechos humanos, protección de datos personales, anticorrupción, prevención del lavado de dinero, igualdad, no discriminación y demás normas aplicables.</p>
</section>
<section id="uso-imagen-institucional">
<h2>19. Uso del Nombre e Imagen Institucional</h2>
<p>El nombre, logotipo, marcas, imagen corporativa y demás signos distintivos del CRPJ únicamente podrán utilizarse con autorización previa y escrita.</p>
</section>
<section id="redes-sociales">
<h2>20. Redes Sociales y Comunicaciones</h2>
<p>El voluntario no podrá emitir declaraciones públicas, entrevistas, comunicados o publicaciones en representación del CRPJ sin autorización previa.</p>
<p>Asimismo, se compromete a utilizar responsablemente las redes sociales evitando publicaciones que puedan afectar la imagen institucional.</p>
</section>
<section id="donaciones">
<h2>21. Donaciones</h2>
<p>La condición de voluntario no otorga derecho alguno sobre los bienes, recursos, proyectos o donaciones administrados por el CRPJ.</p>
<p>Toda campaña de recaudación de fondos deberá contar con autorización previa y escrita.</p>
</section>
<section id="fuerza-mayor">
<h2>22. Fuerza Mayor</h2>
<p>El CRPJ podrá suspender, modificar o cancelar cualquier actividad debido a causas de fuerza mayor, razones de seguridad, conflictos armados, emergencias sanitarias, desastres naturales o cualquier otra circunstancia fuera de su control, sin que ello genere responsabilidad alguna.</p>
</section>
<section id="legislacion-jurisdiccion">
<h2>23. Legislación Aplicable y Jurisdicción</h2>
<p>El presente Reglamento se regirá por la legislación vigente del país donde se desarrollen las actividades del voluntariado.</p>
<p>Toda controversia será sometida a la jurisdicción de los tribunales competentes de dicho país.</p>
</section>
<section id="codigo-etica">
<h2>Código de Ética del Voluntario</h2>
<p>Todo voluntario se compromete a:</p>
<ul>
<li>Actuar con honestidad, integridad y transparencia.</li>
<li>Respetar la dignidad y los derechos de todas las personas.</li>
<li>Promover una cultura de paz, justicia y solidaridad.</li>
<li>Mantener absoluta confidencialidad.</li>
<li>Evitar conflictos de interés.</li>
<li>Representar al CRPJ con respeto y profesionalismo.</li>
<li>Cumplir la legislación aplicable y las políticas institucionales.</li>
<li>Proteger la reputación y los valores del CRPJ.</li>
<li>Actuar siempre con espíritu de servicio.</li>
</ul>
</section>
<section id="politica-privacidad">
<h2>Política de Privacidad y Tratamiento de Datos Personales</h2>
<p>El CRPJ reconoce la importancia de proteger la privacidad de sus voluntarios y se compromete a tratar toda información personal conforme a la legislación aplicable.</p>
<p>Los datos personales serán utilizados exclusivamente para:</p>
<ul>
<li>Administración del programa de voluntariado.</li>
<li>Organización de actividades y eventos.</li>
<li>Comunicación institucional.</li>
<li>Cumplimiento de obligaciones legales.</li>
<li>Protección y seguridad de los participantes.</li>
<li>Elaboración de estadísticas e informes institucionales.</li>
</ul>
<p>El CRPJ adoptará medidas técnicas, administrativas y organizativas razonables para proteger la información personal contra pérdida, acceso no autorizado, alteración, divulgación o uso indebido.</p>
<p>Los voluntarios podrán ejercer los derechos que les otorgue la legislación aplicable respecto del acceso, rectificación, actualización o eliminación de sus datos personales.</p>
</section>

View File

@ -2,9 +2,10 @@
const { props } = Astro.props;
import { Icon } from "astro-icon/components";
import Button from "./ui/Button.astro";
import RegisterModal from "../components/RegisterModal.vue";
import { getLocalizedRoute } from '@/i18n';
const background = props.bgImage || props.bgColor;
const formUrl = `/${Astro.currentLocale}/${getLocalizedRoute("formulario", Astro.currentLocale)}`;
---
<div style={{ background: props.bgImage ? `url('${props.bgImage}') center no-repeat; background-size:contain;` : props.bgColor }} class={`aspect-auto xl:aspect-square py-8 px-8 xl:px-16 flex flex-col justify-evenl`}>
@ -28,7 +29,7 @@ const background = props.bgImage || props.bgColor;
{props.hasInput && (
<div class="flex gap-2 w-full mt-5">
<RegisterModal client:load locale={Astro.currentLocale} />
<Button class="px-6 py-3 uppercase bg-[#22523F]" url={formUrl} variant="secondary" title={props.buttonLabel} />
</div>
)}
</div>

View File

@ -89,9 +89,9 @@ function translatePath(newLocale: string) {
</p>
</div>
<nav
class="flex justify-evenly gap-10 items-center uppercase text-md text-white"
class="flex justify-end md:justify-evenly gap-4 md:gap-6 lg:gap-10 items-center uppercase text-md text-white"
>
<div class="hidden md:flex gap-8 font-primary font-bold">
<div class="hidden md:flex gap-4 lg:gap-8 font-primary font-bold">
{
navItems.map((item) => (
<a
@ -105,7 +105,7 @@ function translatePath(newLocale: string) {
<!-- <a class="hover:text-colorPrimary transition" href={`/${currentLocale}/archive`}>{tl("nav.archive")}</a>
<a class="hover:text-colorPrimary transition" href={`/${currentLocale}/nations`}>{tl("nav.nations")}</a> -->
</div>
<div class="drawer lg:hidden">
<div class="drawer md:hidden">
<input id="my-drawer-1" type="checkbox" class="drawer-toggle" />
<div class="drawer-content">
<label for="my-drawer-1" class="btn-ghost drawer-button">
@ -202,7 +202,7 @@ function translatePath(newLocale: string) {
variant="primary"
/>
</div>
<div class="dropdown dropdown-end lg:block hidden">
<div class="dropdown dropdown-end md:block hidden">
<div
tabindex="0"
role="button"

File diff suppressed because it is too large Load Diff

View File

@ -202,26 +202,50 @@
"footer.form.button": "Enviar",
"footer.reserved": "©2026. Todos los Derechos Reservados. Centro del Reino de Paz y Justicia",
"form.page_title": "SOLICITUD DE INSCRIPCIÓN COMO VOLUNTARIO",
"form.page_desc": "Completá tus datos para formar parte del Centro del Reino de Paz y Justicia.",
"form.step1": "Datos Personales",
"form.step2": "Formación Academica y Experiencia",
"form.page_desc": "Complete sus datos para formar parte del Centro del Reino de Paz y Justicia.",
"form.step1_reglamento": "Reglamento y Aceptación",
"form.step2": "Datos Personales",
"form.step3": "Formación Académica y Experiencia",
"form.step4": "Áreas de Colaboración",
"form.step5": "Disponibilidad",
"form.step6": "Información Médica",
"form.step7": "Contacto de Emergencia",
"form.nombre": "Nombre",
"form.apellido": "Apellido",
"form.documentos": "Documento de Identidad / Pasaporte",
"form.segundo_nombre": "Segundo Nombre",
"form.apellido": "Apellidos",
"form.documentos": "Documento de Identidad",
"form.correo": "Correo Electrónico",
"form.fecha_nacimiento": "Fecha de Nacimiento",
"form.nacionalidad": "Nacionalidad",
"form.sexo": "Sexo",
"form.sexo.m": "Masculino",
"form.sexo.f": "Femenino",
"form.sexo.o": "Otro",
"form.direccion": "Dirección Completa",
"form.direccion": "Dirección Física",
"form.direccion_postal": "Dirección Postal",
"form.ciudad": "Ciudad",
"form.estado": "Estado / Provincia",
"form.pais": "País",
"form.codigo_postal": "Código Postal",
"form.mobile": "Teléfono Móvil",
"form.whatsapp": "WhatsApp",
"form.correo": "Correo Electrónico",
"form.aceptacion_reglamento": "Declaración de Aceptación",
"form.aceptacion.leido": "He leído y acepto el presente Reglamento.",
"form.aceptacion.codigo_etica": "Acepto el Código de Ética del Centro",
"form.aceptacion.datos": "Autorizo el tratamiento de mis datos personales conforme a la Política de Privacidad.",
"form.aceptacion.voluntaria": "Confirmo que mi participación es completamente voluntaria y que no existe relación laboral, contractual o de representación con el Centro",
"form.aceptacion.verdadera": "Confirmo que toda la información proporcionada es verdadera.",
"form.aceptacion.firma_validez": "Acepto que mi consentimiento electrónico tendrá la misma validez jurídica que una firma manuscrita, conforme a la legislación aplicable.",
"form.reglamento_firma": "Firma electrónica",
"form.reglamento_firma_placeholder": "Nombre completo como firma",
"form.leer_reglamento": "Leer Reglamento Completo",
"form.reglamento_titulo": "Reglamento General del Programa de Voluntariado",
"form.reglamento_confirmar": "He leído y comprendo el Reglamento en su totalidad",
"form.reglamento_cerrar": "Cerrar",
"form.reglamento_leido": "Has leído el Reglamento completo",
"form.reglamento_scroll_needed": "Desplácese hasta el final para confirmar que ha leído el Reglamento",
"form.reglamento_aviso": "Debe leer el Reglamento completo para poder marcar las casillas de aceptación y continuar con el formulario.",
"form.reglamento_tooltip": "Debe leer y desplazarse hasta el final del Reglamento para continuar",
"form.profesion": "Profesión u ocupación",
"form.lugar_trabajo_actual": "Lugar de trabajo actual",
"form.nivel_academico": "Nivel académico",
@ -230,6 +254,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?",
@ -239,7 +264,6 @@
"form.nivel.basico": "Básico",
"form.nivel.intermedio": "Intermedio",
"form.nivel.avanzado": "Avanzado",
"form.step3": "Áreas de Colaboración",
"form.areas_colaborar": "Seleccione las áreas en las que desea colaborar",
"form.areas.ayuda_humanitaria": "Ayuda Humanitaria",
"form.areas.educacion": "Educación",
@ -253,12 +277,50 @@
"form.areas.gestion_proyectos": "Gestión de Proyectos",
"form.areas.traduccion_interpretacion": "Traducción e Interpretación",
"form.areas.asesoria_juridica": "Asesoría Jurídica",
"form.areas.servicios_medicos": "Servicios Médicos / Primeros Auxilios",
"form.areas.servicios_medicos": "Servicios Médicos y Primeros Auxilios",
"form.areas.diplomacia_publica": "Diplomacia Pública",
"form.areas.administracion": "Administración",
"form.areas.tecnologia_innovacion": "Tecnología e Innovación",
"form.areas.otra": "Otra",
"form.areas_otra": "Otra (especificar)",
"form.dias_disponibles": "Días disponibles",
"form.dias.lunes": "Lunes",
"form.dias.martes": "Martes",
"form.dias.miercoles": "Miércoles",
"form.dias.jueves": "Jueves",
"form.dias.viernes": "Viernes",
"form.dias.sabado": "Sábado",
"form.dias.domingo": "Domingo",
"form.horario_preferido": "Horario preferido",
"form.horario.manana": "Mañana",
"form.horario.tarde": "Tarde",
"form.horario.noche": "Noche",
"form.horario.segun": "Según las necesidades del Centro",
"form.fuera_ciudad": "¿Está dispuesto(a) a participar en actividades fuera de su ciudad?",
"form.misiones_internacionales": "¿Está dispuesto(a) a participar en actividades o misiones internacionales?",
"form.condicion_medica": "¿Padece alguna condición médica que el Centro deba conocer?",
"form.condicion_medica_cual": "En caso afirmativo, especifique",
"form.alergias": "Alergias",
"form.medicamentos": "Medicamentos de uso permanente:",
"form.emergencia_nombre": "Nombre completo",
"form.emergencia_parentesco": "Parentesco",
"form.emergencia_telefono": "Teléfono",
"form.emergencia_correo": "Correo electrónico",
"form.step8": "Declaración del Voluntario",
"form.declaracion_voluntario": "Declaro bajo protesta de decir verdad que:",
"form.declaracion.verdadera": "Toda la información proporcionada en esta solicitud es verdadera y completa.",
"form.declaracion.acepto_reglamento": "He leído y acepto el Reglamento y las <a href=\"{terms_url}\" target=\"_blank\" class=\"underline\">Condiciones del Programa de Voluntariado</a> del Centro del Reino de Paz y Justicia.",
"form.declaracion.compromiso_etica": "Me comprometo a cumplir el Código de Ética, las políticas institucionales y las instrucciones impartidas por los responsables del Centro.",
"form.declaracion.autorizo_datos": "Autorizo el tratamiento de mis datos personales conforme a la Política de Privacidad del Centro y la legislación aplicable.",
"form.declaracion.confidencialidad": "Me comprometo a guardar absoluta confidencialidad sobre toda la información a la que tenga acceso durante mi participación.",
"form.declaracion.voluntaria": "Entiendo que mi participación es completamente voluntaria y que no genera relación laboral alguna con el Centro",
"form.confirmar_nombre": "Nombres",
"form.confirmar_firma": "Firma electrónica",
"form.confirmacion": "Confirmación final",
"form.confirmacion_label": "Confirmo que los datos personales y la firma mostrados arriba son correctos y corresponden a mi persona.",
"terminos.title": "Términos y Condiciones",
"terminos.text": "Reglamento General del Programa de Voluntariado",
"form.submit": "Enviar Formulario",
"form.next": "Siguiente",
"form.back": "Anterior",
@ -266,5 +328,24 @@
"form.success": "¡Enviado con éxito!",
"form.error": "Error al enviar",
"form.required": "Este campo es obligatorio",
"form.config_error": "Error al cargar el formulario. Intentá de nuevo más tarde."
"form.optional": "Este campo es opcional",
"form.remaining_required_one": "Falta 1 campo obligatorio por llenar",
"form.remaining_required": "Faltan {count} campos obligatorios por llenar",
"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. Inténtelo de nuevo más tarde.",
"form.si": "Sí",
"form.no": "No",
"form.select_placeholder": "Seleccione {label}",
"form.step_counter": "Paso {current} de {total}",
"form.success_title": "¡Registro completado con éxito!",
"form.success_info": "Tu solicitud de inscripción como voluntario ha sido recibida correctamente.",
"form.success_detail": "Para más información, puede comunicarse con nosotros a través de los canales oficiales del Centro del Reino de Paz y Justicia. Agradecemos su interés y compromiso.",
"form.back_home": "Volver al inicio",
"form.documentos_help": "Ingrese el número de un documento oficial de identidad emitido por su país (por ejemplo: cédula de identidad, DNI, tarjeta de identidad o pasaporte, según corresponda).",
"form.lugar_trabajo_actual_help": "Indique la ciudad, provincia y país donde se encuentra su lugar de trabajo actual.",
"form.nacionalidad_help": "Indique su país de nacimiento.",
"form.nivel_academico_help": "Indique su nivel académico alcanzado (ej.: secundario, universitario, posgrado)."
}

View File

@ -35,7 +35,29 @@ export const routeTranslations = {
ru: "редакция",
rw: "editorial",
kr: "editorial",
}
},
formulario: {
es: "formulario",
en: "form",
fr: "formulaire",
he: "טופס",
uk: "форма",
pt: "formulário",
ru: "форма",
rw: "formulaire",
kr: "fòm",
},
terminos: {
es: "terminos",
en: "terms",
fr: "termes",
he: "תנאים",
uk: "terminos",
pt: "termos",
ru: "условия",
rw: "terminos",
kr: "tèm",
},
} as const;
export function getRouteKeyFromSlug(slug: string): keyof typeof routeTranslations {

View File

@ -4,6 +4,7 @@ import Header from "@/components/Header.astro"
import NewsList from "@/components/cards/NewsList.astro";
import { getCollection } from "astro:content";
import FooterSection from "@/components/section/FooterSection.astro";
import DynamicForm from "@/components/forms/DynamicForm.vue";
import { createTranslator, getRouteKeyFromSlug } from '@/i18n';
const tl = createTranslator(Astro.currentLocale);
@ -11,79 +12,122 @@ const tl = createTranslator(Astro.currentLocale);
const { locale, section } = Astro.params;
const routeKey = getRouteKeyFromSlug(section);
const items = await getCollection(routeKey, (post)=>{
const currentLocale = Astro.currentLocale;
return post.data.locale == currentLocale
});
if (routeKey === "formulario") {
if (Astro.currentLocale !== "es") {
return Astro.redirect(`/${Astro.currentLocale}/`);
}
} else if (routeKey === "terminos") {
// rendered client-side
} else {
var items = await getCollection(routeKey, (post)=>{
const currentLocale = Astro.currentLocale;
return post.data.locale == currentLocale
});
const sortedPosts = [...items]
.sort((a, b) => {
const dateDiff = new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
if (dateDiff !== 0) return dateDiff
return (a.data.order ?? 0) - (b.data.order ?? 0)
});
var sortedPosts = [...items]
.sort((a, b) => {
const dateDiff = new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
if (dateDiff !== 0) return dateDiff
return (a.data.order ?? 0) - (b.data.order ?? 0)
});
const allTags = routeKey === "news" ? [...new Set(
sortedPosts
.filter(p => p.data.tags && p.data.tags.length > 0)
.flatMap(p => p.data.tags)
.filter((tag): tag is string => tag !== undefined)
)].sort() : [];
var allTags = routeKey === "news" ? [...new Set(
sortedPosts
.filter(p => p.data.tags && p.data.tags.length > 0)
.flatMap(p => p.data.tags)
.filter((tag): tag is string => tag !== undefined)
)].sort() : [];
const allYears = routeKey === "news" ? [...new Set(
sortedPosts.map(p => new Date(p.data.date).getFullYear())
)].sort((a, b) => b - a) : [];
var allYears = routeKey === "news" ? [...new Set(
sortedPosts.map(p => new Date(p.data.date).getFullYear())
)].sort((a, b) => b - a) : [];
}
---
<MainLayout >
<div class="top-16 relative mb container mx-auto">
<MainLayout title={routeKey === "formulario" ? tl("form.page_title") : routeKey === "terminos" ? tl("terminos.title") : undefined}>
{routeKey === "formulario" && (
<div class="pt-16 relative container mx-auto">
<Header />
</div>
<div class="container mx-auto mt-4">
<div class="flex flex-col lg:w-1/2 items-center mx-auto py-8">
<h1 class="text-white text-2xl uppercase font-bold text-center mb-4 font-primary md:mt-20 mt-10">{tl(routeKey + ".title")}</h1>
<h2 class="text-white text-3xl lg:text-5xl font-bold text-center font-secondary mb-4 md:p-0 px-2">{tl(routeKey + ".text")}</h2>
</div>
)}
{routeKey !== "formulario" && (
<div class="top-16 relative mb container mx-auto">
<Header />
</div>
)}
{routeKey === "formulario" && (
<main class="min-h-screen py-16 px-4">
<div class="md:container mx-auto">
<h1 class="text-3xl lg:text-4xl font-bold font-secondary text-[#EBE6D2] text-center mb-4 md:mb-9">
{tl("form.page_title")}
</h1>
<DynamicForm client:load locale={Astro.currentLocale} turnstileSiteKey={import.meta.env.TURNSTILE_SITE_KEY} />
</div>
</main>
)}
{routeKey === "news" && allTags.length > 0 && (
<div class="container mx-auto mb-8 px-4 md:px-0">
<div class="flex flex-nowrap md:flex-wrap gap-2 md:justify-center overflow-x-auto md:overflow-visible pb-2 md:pb-0">
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag="all">
{tl("news.all")}
</button>
{allTags.map((tag) => (
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag={tag!}>
{tag}
</button>
))}
{routeKey === "terminos" && (
<main class="min-h-screen py-16 px-4">
<div class="md:container mx-auto">
<h1 class="text-3xl lg:text-4xl font-bold font-secondary text-[#EBE6D2] text-center mb-4 md:mb-9">
{tl("terminos.title")}
</h1>
<div id="reglamento-content" class="reglamento-content bg-white p-6 md:p-10 max-w-4xl mx-auto" data-locale={Astro.currentLocale}>
<p class="text-gray-500 text-center">{tl("terminos.text")}...</p>
</div>
</div>
)}
</main>
)}
<div class="flex flex-col md:gap-0 gap-2 lg:max-w-4xl mx-auto bg-white p-4 md:p-8">
{routeKey === "news" && allYears.length > 0 && (
<div class="container mb-4">
<div class="flex justify-start md:justify-end">
<select id="year-filter" class="bg-[#003421] text-[#EBE5D0] px-4 py-2 font-primary text-sm cursor-pointer border-none outline-none">
<option value="all">{tl("news.allYears")}</option>
{allYears.map((year) => (
<option value={year}>{year}</option>
))}
</select>
{routeKey !== "formulario" && routeKey !== "terminos" && (
<div class="container mx-auto mt-4">
<div class="flex flex-col lg:w-1/2 items-center mx-auto py-8">
<h1 class="text-white text-2xl uppercase font-bold text-center mb-4 font-primary md:mt-20 mt-10">{tl(routeKey + ".title")}</h1>
<h2 class="text-white text-3xl lg:text-5xl font-bold text-center font-secondary mb-4 md:p-0 px-2">{tl(routeKey + ".text")}</h2>
</div>
{routeKey === "news" && allTags.length > 0 && (
<div class="container mx-auto mb-8 px-4 md:px-0">
<div class="flex flex-nowrap md:flex-wrap gap-2 md:justify-center overflow-x-auto md:overflow-visible pb-2 md:pb-0">
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag="all">
{tl("news.all")}
</button>
{allTags.map((tag) => (
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag={tag!}>
{tag}
</button>
))}
</div>
</div>
)}
{
sortedPosts.map((item) => (
<div class="news-item" data-tags={routeKey === "news" ? JSON.stringify(item.data.tags || []) : "[]"} data-year={routeKey === "news" ? new Date(item.data.date).getFullYear() : ""}>
<NewsList data={item} content={{ body: item.body }} routeKey={routeKey} />
</div>
))
}
<div class="flex flex-col md:gap-0 gap-2 lg:max-w-4xl mx-auto bg-white p-4 md:p-8">
{routeKey === "news" && allYears.length > 0 && (
<div class="container mb-4">
<div class="flex justify-start md:justify-end">
<select id="year-filter" class="bg-[#003421] text-[#EBE5D0] px-4 py-2 font-primary text-sm cursor-pointer border-none outline-none">
<option value="all">{tl("news.allYears")}</option>
{allYears.map((year) => (
<option value={year}>{year}</option>
))}
</select>
</div>
</div>
)}
{
sortedPosts.map((item) => (
<div class="news-item" data-tags={routeKey === "news" ? JSON.stringify(item.data.tags || []) : "[]"} data-year={routeKey === "news" ? new Date(item.data.date).getFullYear() : ""}>
<NewsList data={item} content={{ body: item.body }} routeKey={routeKey} />
</div>
))
}
</div>
</div>
</div>
<FooterSection />
)}
<FooterSection />
</MainLayout>
@ -168,6 +212,24 @@ const allYears = routeKey === "news" ? [...new Set(
}
}
const reglamentoContainer = document.getElementById('reglamento-content');
if (reglamentoContainer) {
const locale = reglamentoContainer.dataset.locale || 'es';
const locales = [locale, 'es'];
(async () => {
for (const l of locales) {
try {
const res = await fetch(`/reglamento/${l}.html`);
if (res.ok) {
reglamentoContainer.innerHTML = await res.text();
return;
}
} catch {}
}
reglamentoContainer.innerHTML = '<p class="text-red-500 text-center">Error al cargar el reglamento</p>';
})();
}
document.addEventListener('astro:page-load', () => {
initTags();
initYearFilter();

View File

@ -1,27 +0,0 @@
---
import MainLayout from "@/layouts/MainLayout.astro";
import Header from "@/components/Header.astro";
import FooterSection from "@/components/section/FooterSection.astro";
import DynamicForm from "@/components/forms/DynamicForm.vue";
import { createTranslator } from "@/i18n";
const tl = createTranslator(Astro.currentLocale);
---
<MainLayout title={tl("form.page_title")}>
<div class="pt-16 relative container mx-auto">
<Header />
</div>
<main class="min-h-screen bg-white py-16 px-4">
<div class="container mx-auto">
<h1 class="text-3xl lg:text-4xl font-bold text-[#22523F] text-center mb-4">
{tl("form.page_title")}
</h1>
<!-- <p class="text-gray-600 text-center mb-10 max-w-lg mx-auto">
{tl("form.page_desc")}
</p> -->
<DynamicForm client:load locale={Astro.currentLocale} />
</div>
</main>
<FooterSection />
</MainLayout>

View File

@ -66,7 +66,7 @@ export const POST: APIRoute = async ({ request }) => {
}
const emailApiKey = import.meta.env.EMAIL_API_KEY;
const emailResponse = await fetch("http://155.138.215.11:3050/email/send", {
const emailResponse = await fetch("https://flows2.carpa.com/webhook/email/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -78,6 +78,7 @@ export const POST: APIRoute = async ({ request }) => {
nombre,
email,
mensaje,
senderName : "Centro del Reino de Paz y Justicia",
},
}),
});

View File

@ -0,0 +1,124 @@
import type { APIRoute } from "astro";
import { appendRegistrationToSheet } from "../lib/googleSheets";
import { sendEmail } from "../lib/email";
export const prerender = false;
const COLUMNS = [
"aceptacion_reglamento",
"nombre_completo",
"apellido",
"documentos",
"correo",
"reglamento_firma",
"fecha_nacimiento",
"nacionalidad",
"sexo",
"direccion_completa",
"direccion_postal",
"ciudad",
"estado",
"pais",
"postal",
"telefono",
"whatsapp",
"profesion",
"lugar_trabajo_actual",
"nivel_academico",
"idioma",
"idioma_nivel_Espanol",
"idioma_nivel_Ingles",
"idioma_nivel_Hebreo",
"idioma_nivel_Portugues",
"idioma_nivel_Otro",
"idioma_otro",
"voluntariado_anterior",
"caso_si",
"areas_colaborar",
"areas_otra",
"dias_disponibles",
"horario_preferido",
"fuera_ciudad",
"misiones_internacionales",
"condicion_medica",
"condicion_medica_cual",
"alergias",
"medicamentos",
"emergencia_nombre",
"emergencia_parentesco",
"emergencia_telefono",
"emergencia_correo",
"confirmar_nombre",
"confirmar_firma",
"declaracion_voluntario",
"confirmacion",
];
function flattenValue(val: unknown): string {
if (Array.isArray(val)) {
return val.join(", ");
}
if (val === null || val === undefined) {
return "";
}
return String(val);
}
export const POST: APIRoute = async ({ request }) => {
try {
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",
});
const row = COLUMNS.map((key) => flattenValue(formData[key]));
row.push(timestamp);
const result = await appendRegistrationToSheet(row);
await sendEmail({
nombres: formData.nombre,
apellidos: formData.apellido,
}, formData.correo, "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION");
return Response.json(result);
} catch (error) {
console.error("Error en /api/formulario/send:", error);
return Response.json(
{
success: false,
message: error instanceof Error ? error.message : "Error al enviar el formulario",
},
{ status: 500 }
);
}
};

View File

@ -0,0 +1,30 @@
type TypeEmailData = "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_INFO" | "CENTRO_DEL_REINO_PAZ_Y_JUSTICIA_POSTULACION";
export async function sendEmail(data: object, to: string, type: TypeEmailData) {
const emailApiKey = import.meta.env.EMAIL_API_KEY;
const dataFinal = {
...data,
email: to,
senderName: "Centro del Reino de Paz y Justicia",
};
const emailResponse = await fetch("https://flows2.carpa.com/webhook/email/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": emailApiKey!,
},
body: JSON.stringify({
type: type,
data: dataFinal,
}),
});
const responseBody = await emailResponse.text();
if (!emailResponse.ok) {
console.error(`Error enviando correo (status ${emailResponse.status}): ${responseBody}`);
throw new Error(`Error en servicio de correo: ${emailResponse.status}`);
}
}

View File

@ -17,7 +17,7 @@ export async function emailExists(email: string): Promise<boolean> {
// Escribir fórmula en celda auxiliar
await sheets.spreadsheets.values.update({
spreadsheetId: SPREADSHEET_ID,
range: 'DATABASE!Z1', // usa una columna libre
range: 'VOLUNTARIOS!Z1', // usa una columna libre
valueInputOption: 'USER_ENTERED',
requestBody: {
values: [[formula]],
@ -27,7 +27,7 @@ export async function emailExists(email: string): Promise<boolean> {
// Leer resultado calculado
const result = await sheets.spreadsheets.values.get({
spreadsheetId: SPREADSHEET_ID,
range: 'DATABASE!Z1',
range: 'VOLUNTARIOS!Z1',
valueRenderOption: 'UNFORMATTED_VALUE',
});
@ -41,7 +41,7 @@ export async function appendToSheet(values: any[]) {
try {
await sheets.spreadsheets.values.append({
spreadsheetId: SPREADSHEET_ID,
range: 'DATABASE!A2:H2',
range: 'VOLUNTARIOS!A2:H2',
valueInputOption: 'RAW',
requestBody: {
values: [values],
@ -57,3 +57,20 @@ export async function appendToSheet(values: any[]) {
throw error;
}
}
export async function appendRegistrationToSheet(values: string[]) {
try {
await sheets.spreadsheets.values.append({
spreadsheetId: SPREADSHEET_ID,
range: 'VOLUNTARIOS!A2:AU',
valueInputOption: 'RAW',
requestBody: {
values: [values],
},
});
return { success: true, message: 'Registro guardado correctamente' };
} catch (error) {
throw error;
}
}