From 261473c60cf79bcf139ad6d07aad9a7936468b13 Mon Sep 17 00:00:00 2001
From: David Ascanio
Date: Thu, 23 Jul 2026 00:08:21 -0300
Subject: [PATCH 01/32] new send emails
---
src/pages/api/emailInfo/send.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/pages/api/emailInfo/send.ts b/src/pages/api/emailInfo/send.ts
index 27f05d3..858c543 100644
--- a/src/pages/api/emailInfo/send.ts
+++ b/src/pages/api/emailInfo/send.ts
@@ -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",
},
}),
});
From 0e35531ec713ee925a2ec6343476ba0964d009cf Mon Sep 17 00:00:00 2001
From: esteban
Date: Fri, 24 Jul 2026 03:08:42 -0500
Subject: [PATCH 02/32] feat: conectar frontend a Google Sheets y ajustar
responsive
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 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
---
.gitignore | 3 +-
public/forms/formulario-inscripcion.json | 153 ++++-
public/reglamento/es.html | 187 ++++++
src/components/BoxContainer.astro | 11 +-
src/components/Header.astro | 8 +-
src/components/forms/DynamicForm.vue | 667 ++++++++++++++--------
src/i18n/es.json | 73 ++-
src/i18n/index.ts | 11 +
src/pages/[locale]/[section]/index.astro | 147 +++--
src/pages/[locale]/formulario/index.astro | 27 -
src/pages/api/formulario/send.ts | 88 +++
src/pages/api/lib/googleSheets.ts | 23 +-
12 files changed, 1064 insertions(+), 334 deletions(-)
create mode 100644 public/reglamento/es.html
delete mode 100644 src/pages/[locale]/formulario/index.astro
create mode 100644 src/pages/api/formulario/send.ts
diff --git a/.gitignore b/.gitignore
index 6629b0d..b6515b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,5 @@ prisma/*.db
prisma/dev.db
# opencode - agentes y scripts locales (no subir a producción)
-opencode/
\ No newline at end of file
+opencode/
+.opencode/
diff --git a/public/forms/formulario-inscripcion.json b/public/forms/formulario-inscripcion.json
index dd3f451..428c71f 100644
--- a/public/forms/formulario-inscripcion.json
+++ b/public/forms/formulario-inscripcion.json
@@ -3,14 +3,35 @@
"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": "apellido", "label": "form.apellido", "type": "text", "required": true },
+ { "key": "documentos", "label": "form.documentos", "type": "text", "required": true, "colspan": 2 },
+ { "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": "sexo",
"label": "form.sexo",
@@ -29,12 +50,11 @@
{ "key": "pais", "label": "form.pais", "type": "text", "required": true },
{ "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": "whatsapp", "label": "form.whatsapp", "type": "text", "required": true }
]
},
{
- "label": "form.step2",
+ "label": "form.step3",
"column": 2,
"fields": [
{ "key": "profesion", "label": "form.profesion", "type": "text", "required": true },
@@ -72,7 +92,7 @@
]
},
{
- "label": "form.step3",
+ "label": "form.step4",
"fields": [
{
"key": "areas_colaborar",
@@ -101,6 +121,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", "required": true },
+ { "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" },
+ { "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 }
+ ] }
+ ]
}
]
}
diff --git a/public/reglamento/es.html b/public/reglamento/es.html
new file mode 100644
index 0000000..2de1ef5
--- /dev/null
+++ b/public/reglamento/es.html
@@ -0,0 +1,187 @@
+Reglamento General del Programa de Voluntariado
+Términos, Condiciones y Código de Ética para Voluntarios
+
+
+ Introducción
+ El Centro del Reino de Paz y Justicia 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.
+ 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 Centro y a la legislación aplicable.
+
+
+
+ 1. Objeto del Voluntariado
+ El voluntario participa libre y voluntariamente en las actividades del Centro con el propósito de contribuir al cumplimiento de su 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.
+ Toda actividad deberá desarrollarse con espíritu de servicio, responsabilidad, integridad, respeto, cooperación y compromiso con el bienestar común.
+
+
+
+ 2. Naturaleza del Voluntariado
+ La participación del voluntario 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 Centro.
+ 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 Centro respecto del reembolso de gastos.
+
+
+
+ 3. Requisitos para ser Voluntario
+ El voluntario declara que:
+
+ La información proporcionada es veraz, completa y actualizada.
+ Se encuentra física y mentalmente apto para participar en las actividades.
+ No existe impedimento legal para su participación.
+ Cumplirá las políticas, reglamentos y procedimientos establecidos por el Centro.
+
+
+
+
+ 4. Seguro
+ Salvo comunicación expresa y por escrito del Centro, cada voluntario será responsable de contar con la cobertura médica, de accidentes y demás seguros que considere necesarios para su participación.
+ La contratación de cualquier seguro por parte del Centro no sustituye la responsabilidad personal del voluntario.
+
+
+
+ 5. Responsabilidad Médica
+ 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.
+
+
+
+ 6. Cumplimiento de Normas
+ El voluntario se compromete a cumplir las instrucciones impartidas por los responsables del Centro, respetar las normas de seguridad y actuar siempre con responsabilidad, profesionalismo y respeto hacia todas las personas.
+
+
+
+ 7. Confidencialidad
+ Toda la información a la que el voluntario tenga acceso durante su participación será considerada confidencial.
+ 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 Centro.
+
+
+
+ 8. Protección de Datos Personales
+ El voluntario autoriza al Centro a recopilar, almacenar y tratar sus datos personales exclusivamente para fines relacionados con:
+
+ la administración del programa de voluntariado;
+ la comunicación institucional;
+ la organización de actividades;
+ el cumplimiento de obligaciones legales;
+ la seguridad de los participantes.
+
+
+
+
+ 9. Uso de Imagen
+ El voluntario autoriza al Centro 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 contrario.
+
+
+
+ 10. Propiedad Intelectual
+ 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 Centro, salvo acuerdo escrito en contrario.
+
+
+
+ 11. Uso y Cuidado de Bienes
+ El voluntario deberá utilizar responsablemente los bienes, equipos y materiales del Centro, comprometiéndose a conservarlos y devolverlos cuando le sean requeridos o al finalizar su participación.
+
+
+
+ 12. Terminación del Voluntariado
+ El Centro 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.
+ El voluntario podrá retirarse libremente notificándolo con una anticipación razonable.
+
+
+
+ 13. Limitación de Responsabilidad
+ El voluntario reconoce que participa libremente en las actividades del Centro y acepta los riesgos normales inherentes a las mismas.
+ En la medida permitida por la legislación aplicable, el Centro, 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.
+
+
+
+ 14. Declaración de Asunción de Riesgos
+ 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.
+ El voluntario acepta participar bajo su propia responsabilidad y asume voluntariamente dichos riesgos, salvo cuando exista dolo o negligencia grave imputable al Centro.
+
+
+
+ 15. Conducta Institucional
+ El voluntario se compromete a mantener una conducta acorde con los principios y valores del Centro.
+ Queda prohibido:
+
+ realizar actos de violencia física o verbal;
+ discriminar o acosar a cualquier persona;
+ consumir drogas ilegales o presentarse bajo sus efectos;
+ portar armas sin autorización legal y expresa;
+ solicitar donaciones o administrar recursos económicos en nombre del Centro sin autorización escrita;
+ utilizar el nombre del Centro para fines personales, políticos o comerciales.
+
+
+
+
+ 16. Protección de Menores y Personas Vulnerables
+ 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 Centro y la legislación aplicable.
+
+
+
+ 17. Conflicto de Intereses
+ 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 Centro.
+
+
+
+ 18. Cumplimiento Normativo
+ 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.
+
+
+
+ 19. Uso del Nombre e Imagen Institucional
+ El nombre, logotipo, marcas, imagen corporativa y demás signos distintivos del Centro únicamente podrán utilizarse con autorización previa y escrita.
+
+
+
+ 20. Redes Sociales y Comunicaciones
+ El voluntario no podrá emitir declaraciones públicas, entrevistas, comunicados o publicaciones en representación del Centro sin autorización previa.
+ Asimismo, se compromete a utilizar responsablemente las redes sociales evitando publicaciones que puedan afectar la imagen institucional.
+
+
+
+ 21. Donaciones
+ La condición de voluntario no otorga derecho alguno sobre los bienes, recursos, proyectos o donaciones administrados por el Centro.
+ Toda campaña de recaudación de fondos deberá contar con autorización previa y escrita.
+
+
+
+ 22. Fuerza Mayor
+ El Centro 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.
+
+
+
+ 23. Legislación Aplicable y Jurisdicción
+ El presente Reglamento se regirá por la legislación vigente del país donde se desarrollen las actividades del voluntariado.
+ Toda controversia será sometida a la jurisdicción de los tribunales competentes de dicho país.
+
+
+
+ Código de Ética del Voluntario
+ Todo voluntario se compromete a:
+
+ Actuar con honestidad, integridad y transparencia.
+ Respetar la dignidad y los derechos de todas las personas.
+ Promover una cultura de paz, justicia y solidaridad.
+ Mantener absoluta confidencialidad.
+ Evitar conflictos de interés.
+ Representar al Centro con respeto y profesionalismo.
+ Cumplir la legislación aplicable y las políticas institucionales.
+ Proteger la reputación y los valores del Centro.
+ Actuar siempre con espíritu de servicio.
+
+
+
+
+ Política de Privacidad y Tratamiento de Datos Personales
+ El Centro 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.
+ Los datos personales serán utilizados exclusivamente para:
+
+ Administración del programa de voluntariado.
+ Organización de actividades y eventos.
+ Comunicación institucional.
+ Cumplimiento de obligaciones legales.
+ Protección y seguridad de los participantes.
+ Elaboración de estadísticas e informes institucionales.
+
+ El Centro 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.
+ 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.
+
diff --git a/src/components/BoxContainer.astro b/src/components/BoxContainer.astro
index f31b747..e7433f9 100644
--- a/src/components/BoxContainer.astro
+++ b/src/components/BoxContainer.astro
@@ -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)}`;
---
@@ -18,8 +19,8 @@ const background = props.bgImage || props.bgColor;
{props.title}
{props.copy}
-
-
+
+
{props.hasButton && (
@@ -28,8 +29,8 @@ const background = props.bgImage || props.bgColor;
{props.hasInput && (
-
+
)}
-
\ No newline at end of file
+
diff --git a/src/components/Header.astro b/src/components/Header.astro
index 13ae3d9..31199ca 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -89,9 +89,9 @@ function translatePath(newLocale: string) {
-
+
-
+
@@ -202,7 +202,7 @@ function translatePath(newLocale: string) {
variant="primary"
/>
-
+
-import { ref, reactive, computed, onMounted } from "vue";
+import { ref, reactive, computed, onMounted, watch } from "vue";
import { Icon } from "@iconify/vue";
import { createTranslator } from "../../i18n";
const props = defineProps({
locale: String,
- formConfigUrl: { type: String, default: "/forms/formulario-inscripcion.json" }
+ formConfigUrl: { type: String, required: false, default: "/forms/formulario-inscripcion.json" }
});
const tl = createTranslator(props.locale);
@@ -19,6 +19,12 @@ const isSuccess = ref(false);
const isError = ref(false);
const errorMsg = ref("");
+const reglamentoScrollRef = ref(null);
+const reglamentoHTML = ref("");
+const reglamentoLoading = ref(false);
+const reglamentoScrolled = ref(false);
+const reglamentoFetchAttempted = ref(false);
+
const totalSteps = computed(() => config.value?.steps?.length ?? 0);
const currentStepData = computed(() => config.value?.steps?.[currentStep.value] ?? null);
@@ -28,6 +34,7 @@ const isLastStep = computed(() => currentStep.value === totalSteps.value - 1);
const stepErrors = reactive({});
const fetchConfig = async () => {
+ isSuccess.value = false;
try {
const res = await fetch(props.formConfigUrl);
if (!res.ok) throw new Error("Failed to load form config");
@@ -53,17 +60,42 @@ const validateStep = (stepIndex) => {
const step = config.value?.steps?.[stepIndex];
if (!step) return true;
let valid = true;
+ if (stepIndex === 0 && !reglamentoScrolled.value) {
+ valid = false;
+ }
step.fields.forEach((field) => {
+ if (field.showWhen) {
+ const sourceVal = formData[field.showWhen.field];
+ const sourceField = findField(field.showWhen.field);
+ const conditionMet = sourceField?.type === "checkbox"
+ ? Array.isArray(sourceVal) && sourceVal.includes(field.showWhen.value)
+ : sourceVal === field.showWhen.value;
+ if (!conditionMet) return;
+ }
const val = formData[field.key];
- if (field.required) {
- if (field.type === "checkbox") {
- if (!val || val.length === 0) {
- stepErrors[field.key] = true;
- valid = false;
+ if (field.type === "checkbox") {
+ const requiredOptions = field.options?.filter(o => o.required)?.map(o => o.value) || [];
+ if (field.required) {
+ if (requiredOptions.length > 0) {
+ const allRequiredSelected = requiredOptions.every(optVal => val?.includes(optVal));
+ if (!allRequiredSelected) {
+ stepErrors[field.key] = true;
+ valid = false;
+ } else {
+ stepErrors[field.key] = false;
+ }
} else {
- stepErrors[field.key] = false;
+ if (!val || val.length === 0) {
+ stepErrors[field.key] = true;
+ valid = false;
+ } else {
+ stepErrors[field.key] = false;
+ }
}
- } else if (!val || val.trim() === "") {
+ }
+ }
+ if (field.required && field.type !== "checkbox") {
+ if (!val || val.trim() === "") {
stepErrors[field.key] = true;
valid = false;
} else {
@@ -75,6 +107,8 @@ const validateStep = (stepIndex) => {
if (!emailRegex.test(val.trim())) {
stepErrors[field.key] = true;
valid = false;
+ } else {
+ stepErrors[field.key] = false;
}
}
if (field.type === "checkbox" && field.levels && Array.isArray(val)) {
@@ -82,6 +116,8 @@ const validateStep = (stepIndex) => {
if (!formData[`${field.key}_nivel_${optVal}`]) {
stepErrors[`${field.key}_nivel_${optVal}`] = true;
valid = false;
+ } else {
+ stepErrors[`${field.key}_nivel_${optVal}`] = false;
}
});
}
@@ -168,18 +204,20 @@ const handleSubmit = async () => {
try {
const payload = buildPayload();
- const response = await fetch(config.value.action, {
+ const res = await fetch("/api/formulario/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
- if (!response.ok) throw new Error("Error en el servidor");
- isSuccess.value = true;
- setTimeout(() => { isSuccess.value = false; }, 4000);
+ const data = await res.json();
+ if (data.success) {
+ isSuccess.value = true;
+ } else {
+ throw new Error(data.message || "Error al enviar el formulario");
+ }
} catch (error) {
isError.value = true;
errorMsg.value = error.message || "Error al enviar el formulario";
- setTimeout(() => { isError.value = false; }, 4000);
} finally {
isSubmitting.value = false;
}
@@ -206,6 +244,77 @@ const shouldShow = (field) => {
}
return value === field.showWhen.value;
};
+
+const fetchReglamento = async () => {
+ if (reglamentoFetchAttempted.value) return;
+ reglamentoFetchAttempted.value = true;
+ reglamentoScrolled.value = false;
+ reglamentoLoading.value = true;
+ reglamentoHTML.value = "";
+ try {
+ const locales = [props.locale, "es"];
+ let html = null;
+ for (const l of locales) {
+ const res = await fetch(`/reglamento/${l}.html`);
+ if (res.ok) { html = await res.text(); break; }
+ }
+ reglamentoHTML.value = html || "
Error al cargar el reglamento
";
+ } catch {
+ reglamentoHTML.value = "
Error al cargar el reglamento
";
+ } finally {
+ reglamentoLoading.value = false;
+ }
+};
+
+const onReglamentoScroll = () => {
+ const el = reglamentoScrollRef.value;
+ if (!el) return;
+ const threshold = 10;
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - threshold) {
+ reglamentoScrolled.value = true;
+ }
+};
+
+watch(currentStep, (step) => {
+ if (step === 0) {
+ fetchReglamento();
+ } else {
+ reglamentoFetchAttempted.value = false;
+ }
+}, { immediate: true });
+
+const getFieldIcon = (field) => {
+ const k = field.key;
+ if (k === "nombre" || k === "emergencia_nombre") return "ph:user";
+ if (k === "apellido") return "ph:identification-card";
+ if (k === "documentos") return "ph:cardholder";
+ if (k === "correo" || k === "emergencia_correo") return "ph:envelope";
+ if (k === "reglamento_firma") return "ph:signature";
+ if (k === "fecha_nacimiento") return "ph:calendar";
+ if (k === "nacionalidad" || k === "pais") return "ph:flag";
+ if (k === "direccion_completa") return "ph:house";
+ if (k === "ciudad") return "ph:city";
+ if (k === "estado" || k === "postal") return "ph:map-pin";
+ if (k === "telefono" || k === "whatsapp" || k === "emergencia_telefono") return "ph:phone";
+ if (k === "profesion") return "ph:briefcase";
+ if (k === "lugar_trabajo_actual") return "ph:buildings";
+ if (k === "nivel_academico") return "ph:graduation-cap";
+ if (k === "idioma_otro") return "ph:translate";
+ if (k === "areas_otra") return "ph:plus-circle";
+ if (k === "emergencia_parentesco") return "ph:users-three";
+ if (field.type === "phone") return "ph:phone";
+ if (field.type === "email") return "ph:envelope";
+ if (field.type === "textarea") return "ph:note-pencil";
+ if (field.type === "date") return "ph:calendar";
+ return "ph:newspaper-clip";
+};
+
+const getFieldInputType = (field) => {
+ if (field.type === "email") return "email";
+ if (field.type === "phone") return "tel";
+ if (field.type === "date") return "date";
+ return "text";
+};
@@ -214,228 +323,338 @@ const shouldShow = (field) => {
-
+
+
-
-
-
-
-
+
+
+
- {{ index < currentStep ? '✓' : index + 1 }}
-
- {{ tl(step.label) }}
-
-
-
-
-
-
+
+ {{ tl(currentStepData.label) }}
+
-
-
-
- {{ tl("form.back") }}
-
-
+
+
+
+
+
+
+
+
+
+ {{ reglamentoScrolled ? tl("form.reglamento_leido") : tl("form.reglamento_scroll_needed") }}
+
+
+
+
+ {{ tl("form.reglamento_aviso") }}
+
+
+
-
-
- {{ tl("form.next") }}
-
-
+
+
+
+ {{ errorMsg }}
+
+
+
+
+
+
+
+
+ {{ tl("form.step_counter", { current: currentStep + 1, total: totalSteps }) }}
+
+
+
+
+
+
+ {{ tl("form.back") }}
+
+
+
+
+
+ {{ tl("form.step_counter", { current: currentStep + 1, total: totalSteps }) }}
+
+
+
+
+ {{ tl("form.next") }}
+
+
+
+ {{ tl("form.reglamento_tooltip") }}
+
+
+
+
-
-
- {{ tl("form.sending") }}
-
-
-
- {{ tl("form.success") }}
-
-
-
- {{ tl("form.error") }}
-
-
- {{ tl(config.submit_label) }}
-
-
+ >
+
+
+ {{ tl("form.sending") }}
+
+
+ {{ tl(config.submit_label) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ tl("form.success_title") }}
+
+
+ {{ tl("form.success_info") }}
+
+
+
+
+
{{ tl("form.success_detail") }}
-
+
+ {{ tl("form.back_home") }}
+
+
+
+
+
diff --git a/src/i18n/es.json b/src/i18n/es.json
index da530ce..4458e09 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -203,11 +203,17 @@
"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.step1_reglamento": "Reglamento y Aceptación",
+ "form.step2": "Datos Personales",
+ "form.step3": "Formación Academica 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.correo": "Correo Electrónico",
"form.fecha_nacimiento": "Fecha de Nacimiento",
"form.nacionalidad": "Nacionalidad",
"form.sexo": "Sexo",
@@ -221,7 +227,23 @@
"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ázate hasta el final para confirmar que has leído el Reglamento",
+ "form.reglamento_aviso": "Debes leer el Reglamento completo para poder marcar las casillas de aceptación y continuar con el formulario.",
+ "form.reglamento_tooltip": "Debes leer y desplazarte 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",
@@ -239,7 +261,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",
@@ -259,6 +280,41 @@
"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 Condiciones del Programa de Voluntariado 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 de 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 respecto de 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": "Nombre",
+ "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.",
"form.submit": "Enviar Formulario",
"form.next": "Siguiente",
"form.back": "Anterior",
@@ -266,5 +322,12 @@
"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.config_error": "Error al cargar el formulario. Intentá de nuevo más tarde.",
+ "form.si": "Sí",
+ "form.no": "No",
+ "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, puedes comunicarte con nosotros a través de los canales oficiales del Centro del Reino de Paz y Justicia. Agradecemos tu interés y compromiso.",
+ "form.back_home": "Volver al inicio"
}
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index 38bc909..a325a95 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -35,6 +35,17 @@ 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",
}
} as const;
diff --git a/src/pages/[locale]/[section]/index.astro b/src/pages/[locale]/[section]/index.astro
index ea3f244..53275fa 100644
--- a/src/pages/[locale]/[section]/index.astro
+++ b/src/pages/[locale]/[section]/index.astro
@@ -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,107 @@ 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 {
+ 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) : [];
+}
---
-
-
+
+ {routeKey === "formulario" && (
+
-
-
-
-
{tl(routeKey + ".title")}
- {tl(routeKey + ".text")}
+ )}
+ {routeKey !== "formulario" && (
+
+
+
+ )}
-
- {routeKey === "news" && allTags.length > 0 && (
-
-
-
- {tl("news.all")}
-
- {allTags.map((tag) => (
-
- {tag}
-
- ))}
-
+ {routeKey === "formulario" && (
+
+
+
+ {tl("form.page_title")}
+
+
- )}
+
+ )}
-
- {routeKey === "news" && allYears.length > 0 && (
-
-
-
- {tl("news.allYears")}
- {allYears.map((year) => (
- {year}
- ))}
-
+ {routeKey !== "formulario" && (
+
+
+
{tl(routeKey + ".title")}
+ {tl(routeKey + ".text")}
+
+
+
+ {routeKey === "news" && allTags.length > 0 && (
+
+
+
+ {tl("news.all")}
+
+ {allTags.map((tag) => (
+
+ {tag}
+
+ ))}
)}
- {
- sortedPosts.map((item) => (
-
-
-
- ))
- }
+
+
+ {routeKey === "news" && allYears.length > 0 && (
+
+
+
+ {tl("news.allYears")}
+ {allYears.map((year) => (
+ {year}
+ ))}
+
+
+
+ )}
+ {
+ sortedPosts.map((item) => (
+
+
+
+ ))
+ }
+
-
-
+ )}
+
+
diff --git a/src/pages/[locale]/formulario/index.astro b/src/pages/[locale]/formulario/index.astro
deleted file mode 100644
index 956d16e..0000000
--- a/src/pages/[locale]/formulario/index.astro
+++ /dev/null
@@ -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);
----
-
-
-
-
-
-
-
-
- {tl("form.page_title")}
-
-
-
-
-
-
-
diff --git a/src/pages/api/formulario/send.ts b/src/pages/api/formulario/send.ts
new file mode 100644
index 0000000..1bd143d
--- /dev/null
+++ b/src/pages/api/formulario/send.ts
@@ -0,0 +1,88 @@
+import type { APIRoute } from "astro";
+import { appendRegistrationToSheet } from "../lib/googleSheets";
+export const prerender = false;
+
+const COLUMNS = [
+ "aceptacion_reglamento",
+ "nombre",
+ "apellido",
+ "documentos",
+ "correo",
+ "reglamento_firma",
+ "fecha_nacimiento",
+ "nacionalidad",
+ "sexo",
+ "direccion_completa",
+ "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 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);
+
+ return Response.json(result);
+ } catch (error) {
+ return Response.json(
+ {
+ success: false,
+ message: error instanceof Error ? error.message : "Error al enviar el formulario",
+ },
+ { status: 500 }
+ );
+ }
+};
diff --git a/src/pages/api/lib/googleSheets.ts b/src/pages/api/lib/googleSheets.ts
index b963bda..d2cde81 100644
--- a/src/pages/api/lib/googleSheets.ts
+++ b/src/pages/api/lib/googleSheets.ts
@@ -17,7 +17,7 @@ export async function emailExists(email: string): Promise
{
// 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 {
// 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;
+ }
+}
From 25de7a53c12b5d30491e552092bc33a3f1c01600 Mon Sep 17 00:00:00 2001
From: esteban
Date: Fri, 24 Jul 2026 03:22:32 -0500
Subject: [PATCH 03/32] create autodeploy
---
.gitea/workflows/deploy.yaml | 53 ++++++++++++++++++++++++++++++------
ecosystem.config.cjs | 14 ++++++++++
2 files changed, 59 insertions(+), 8 deletions(-)
create mode 100644 ecosystem.config.cjs
diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml
index 2620d9e..7b655f1 100644
--- a/.gitea/workflows/deploy.yaml
+++ b/.gitea/workflows/deploy.yaml
@@ -1,14 +1,51 @@
-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/cdrdpyj" >> $GITHUB_ENV
+ echo "PM2_ENV=production" >> $GITHUB_ENV
+ else
+ echo "TARGET_DIR=/var/www/dev.cdrdjyp" >> $GITHUB_ENV
+ echo "PM2_ENV=staging" >> $GITHUB_ENV
+ fi
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ - run: npm ci
+ - run: npm 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,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: |
+ export NVM_DIR="$HOME/.nvm"
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ cd ${{ env.TARGET_DIR }}
+ 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
new file mode 100644
index 0000000..7a2e0d4
--- /dev/null
+++ b/ecosystem.config.cjs
@@ -0,0 +1,14 @@
+module.exports = {
+ apps: [{
+ name: "cdrdpyj",
+ script: "dist/server/entry.mjs",
+ env: {
+ NODE_ENV: "staging",
+ PORT: 3310,
+ },
+ env_production: {
+ NODE_ENV: "production",
+ PORT: 3310,
+ }
+ }]
+};
From d830bdcfb85cc58b5abc53b76da270166c144360 Mon Sep 17 00:00:00 2001
From: esteban
Date: Fri, 24 Jul 2026 03:27:29 -0500
Subject: [PATCH 04/32] change something
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 024e254..03a4182 100644
--- a/README.md
+++ b/README.md
@@ -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
\ No newline at end of file
+Testing runner change spmeting
From f4d128306ad54168ce2981d71cd613d4b0986d51 Mon Sep 17 00:00:00 2001
From: esteban
Date: Fri, 24 Jul 2026 10:57:54 -0500
Subject: [PATCH 05/32] feat: change required for whatsapp and state, add
dropdown for countries and add json with all countries
---
public/forms/formulario-inscripcion.json | 8 +-
public/forms/paises.json | 195 +++++++++++++++++++++++
src/components/forms/DynamicForm.vue | 101 +++++++++++-
src/i18n/es.json | 1 +
4 files changed, 296 insertions(+), 9 deletions(-)
create mode 100644 public/forms/paises.json
diff --git a/public/forms/formulario-inscripcion.json b/public/forms/formulario-inscripcion.json
index 428c71f..ccb45d2 100644
--- a/public/forms/formulario-inscripcion.json
+++ b/public/forms/formulario-inscripcion.json
@@ -31,7 +31,7 @@
{ "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": "nacionalidad", "label": "form.nacionalidad", "type": "autocomplete", "required": true, "source": "/forms/paises.json" },
{
"key": "sexo",
"label": "form.sexo",
@@ -46,11 +46,11 @@
},
{ "key": "direccion_completa", "label": "form.direccion", "type": "text", "required": true, "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": "whatsapp", "label": "form.whatsapp", "type": "text" }
]
},
{
diff --git a/public/forms/paises.json b/public/forms/paises.json
new file mode 100644
index 0000000..0d08a5d
--- /dev/null
+++ b/public/forms/paises.json
@@ -0,0 +1,195 @@
+[
+ { "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" }
+]
diff --git a/src/components/forms/DynamicForm.vue b/src/components/forms/DynamicForm.vue
index fda6876..94f2c68 100644
--- a/src/components/forms/DynamicForm.vue
+++ b/src/components/forms/DynamicForm.vue
@@ -225,6 +225,44 @@ const handleSubmit = async () => {
const getFieldError = (key) => stepErrors[key] || false;
+const suggestionsCache = ref({});
+const showSuggestions = reactive({});
+
+const loadSuggestions = async (source) => {
+ if (suggestionsCache.value[source]) return;
+ try {
+ const res = await fetch(source);
+ suggestionsCache.value[source] = await res.json();
+ } catch {}
+};
+
+const filteredSuggestions = (field) => {
+ const val = formData[field.key]?.toLowerCase().trim() || "";
+ if (!val) return [];
+ const list = suggestionsCache.value[field.source] || [];
+ return list.filter(item => item.label.toLowerCase().includes(val));
+};
+
+const selectSuggestion = (field, item) => {
+ formData[field.key] = item.value;
+ showSuggestions[field.key] = false;
+ stepErrors[field.key] = false;
+};
+
+const onAutocompleteFocus = (field) => {
+ loadSuggestions(field.source);
+ showSuggestions[field.key] = true;
+};
+
+const onAutocompleteBlur = (field) => {
+ setTimeout(() => { showSuggestions[field.key] = false; }, 200);
+};
+
+const getSelectOptions = (field) => {
+ loadSuggestions(field.source);
+ return suggestionsCache.value[field.source] || [];
+};
+
const findField = (key) => {
if (!config.value) return null;
for (const step of config.value.steps) {
@@ -325,9 +363,9 @@ const getFieldInputType = (field) => {
-
-
+
+
{
/>
+
+
+
+
+ {{ tl("form.select_placeholder", { label: tl(field.label) }) }}
+
+ {{ item.label }}
+
+
+
+
+
+
+
{
-
+
-
+
+
{
{{ tl("form.back_home") }}
-
+
+