create_form #48
|
|
@ -28,3 +28,4 @@ prisma/dev.db
|
|||
|
||||
# opencode - agentes y scripts locales (no subir a producción)
|
||||
opencode/
|
||||
.opencode/
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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 Centro y a la legislación aplicable.</p>
|
||||
</section>
|
||||
|
||||
<section id="objeto-voluntariado">
|
||||
<h2>1. Objeto del Voluntariado</h2>
|
||||
<p>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.</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 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.</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 Centro 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 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 Centro.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="seguro">
|
||||
<h2>4. Seguro</h2>
|
||||
<p>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.</p>
|
||||
<p>La contratación de cualquier seguro por parte del Centro 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 Centro, 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 Centro.</p>
|
||||
</section>
|
||||
|
||||
<section id="proteccion-datos">
|
||||
<h2>8. Protección de Datos Personales</h2>
|
||||
<p>El voluntario autoriza al Centro 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 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.</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 Centro, salvo acuerdo escrito en 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 Centro, 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 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.</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 Centro y acepta los riesgos normales inherentes a las mismas.</p>
|
||||
<p>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.</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 Centro.</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 Centro.</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 Centro sin autorización escrita;</li>
|
||||
<li>utilizar el nombre del Centro 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 Centro 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 Centro.</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 Centro ú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 Centro 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 Centro.</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 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.</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 Centro 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 Centro.</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 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.</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 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.</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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<script setup>
|
||||
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") {
|
||||
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 {
|
||||
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");
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
isSuccess.value = true;
|
||||
setTimeout(() => { isSuccess.value = false; }, 4000);
|
||||
} 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 || "<p>Error al cargar el reglamento</p>";
|
||||
} catch {
|
||||
reglamentoHTML.value = "<p>Error al cargar el reglamento</p>";
|
||||
} 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";
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -214,149 +323,178 @@ const shouldShow = (field) => {
|
|||
</div>
|
||||
|
||||
<div v-else-if="!config" class="flex justify-center py-12">
|
||||
<span class="w-8 h-8 border-2 border-[#22523F] border-t-transparent rounded-full animate-spin"></span>
|
||||
<span class="w-8 h-8 border-2 border-[#22523F] font-secondary border-t-transparent rounded-full animate-spin"></span>
|
||||
</div>
|
||||
<div v-else class="w-full max-w-3xl mx-auto md:px-4 sm:px-0">
|
||||
|
||||
<div v-else class="w-full max-w-2xl mx-auto">
|
||||
<div class="flex items-center justify-center gap-2 mb-8">
|
||||
<template v-if="!isSuccess">
|
||||
<div class="flex flex-wrap sm:flex-nowrap items-center justify-center gap-x-1 md:gap-x-2 gap-y-2 sm:gap-y-3 mb-4 sm:mb-6 lg:mb-8">
|
||||
<template v-for="(step, index) in config.steps" :key="index">
|
||||
<button
|
||||
type="button"
|
||||
@click="goToStep(index)"
|
||||
class="flex items-center gap-1 text-sm transition-colors"
|
||||
:class="index === currentStep ? 'text-[#22523F] font-bold' : 'text-gray-400 hover:text-[#22523F]'"
|
||||
class="flex flex-col lg:flex-row items-center gap-1 text-sm transition-colors text-center"
|
||||
:class="index === currentStep ? 'text-[#EBE6D2] font-bold' : 'text-[#EBE6D2]/60 hover:text-[#EBE6D2]'"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center w-8 h-8 rounded-full text-xs font-bold border-2 transition-colors"
|
||||
class="flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold border-2 transition-colors shrink-0"
|
||||
:class="index === currentStep
|
||||
? 'bg-[#22523F] text-white border-[#22523F]'
|
||||
? 'bg-[#CBA16A] text-[#22523F] border-[#CBA16A]'
|
||||
: index < currentStep
|
||||
? 'bg-[#CBA16A] text-white border-[#CBA16A]'
|
||||
: 'border-gray-300 text-gray-400'"
|
||||
? 'bg-[#EBE6D2] text-[#22523F] border-[#EBE6D2]'
|
||||
: 'border-[#EBE6D2]/50 text-[#EBE6D2]/60'"
|
||||
>
|
||||
{{ index < currentStep ? '✓' : index + 1 }}
|
||||
</span>
|
||||
<span class="hidden sm:inline">{{ tl(step.label) }}</span>
|
||||
<span class="text-[10px] lg:text-xs leading-tight max-w-[60px] md:max-w-[70px] lg:max-w-none font-secondary">{{ tl(step.label) }}</span>
|
||||
</button>
|
||||
<span v-if="index < config.steps.length - 1" class="w-8 h-px bg-gray-300"></span>
|
||||
<span v-if="index < config.steps.length - 1" class="w-4 md:w-6 lg:w-8 h-px bg-[#EBE6D2]/30 shrink-0 hidden sm:inline"></span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="bg-[#EBE6D2] p-8 rounded-none">
|
||||
<h3 class="text-xl font-bold text-[#22523F] mb-6 text-center">
|
||||
<form @submit.prevent="handleSubmit" class="bg-[#EBE6D2] p-5 sm:p-8 lg:p-10 rounded-none">
|
||||
<h3 class="text-lg sm:text-xl font-bold text-[#22523F] mb-4 sm:mb-6 text-center font-secondary">
|
||||
{{ tl(currentStepData.label) }}
|
||||
</h3>
|
||||
|
||||
<div class="grid gap-5" :class="{ 'grid-cols-1': !currentStepData.column || currentStepData.column === 1, 'md:grid-cols-2': currentStepData.column === 2, 'md:grid-cols-3': currentStepData.column === 3 }">
|
||||
<template v-for="field in currentStepData.fields" :key="field.key">
|
||||
<div v-if="shouldShow(field)" class="form-control" :class="{ 'md:col-span-2': field.colspan === 2 && currentStepData.column > 1, 'md:col-span-3': field.colspan === 3 && currentStepData.column > 1 }">
|
||||
<label class="label">
|
||||
<span class="label-text text-[#22523F] font-medium">
|
||||
{{ tl(field.label) }}
|
||||
<span v-if="field.required" class="text-red-500 ml-1">*</span>
|
||||
<div v-if="currentStep === 0" class="mb-4 sm:mb-6">
|
||||
<div v-if="reglamentoLoading" class="flex justify-center py-8">
|
||||
<span class="w-8 h-8 border-2 border-[#22523F] border-t-transparent rounded-full animate-spin"></span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
ref="reglamentoScrollRef"
|
||||
class="md:max-h-[720px] max-h-[500px] overflow-y-auto border scrollbar-thin scrollbar-thumb-[#22523F] border-gray-300 p-4 bg-white reglamento-content"
|
||||
@scroll="onReglamentoScroll"
|
||||
>
|
||||
<div v-html="reglamentoHTML"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-3">
|
||||
<Icon
|
||||
:icon="reglamentoScrolled ? 'ph:check-circle-fill' : 'ph:arrow-down'"
|
||||
:class="reglamentoScrolled ? 'text-green-600' : 'text-gray-400'"
|
||||
class="text-lg"
|
||||
/>
|
||||
<span :class="reglamentoScrolled ? 'text-green-700 font-medium' : 'text-gray-500'" class="text-sm font-secondary">
|
||||
{{ reglamentoScrolled ? tl("form.reglamento_leido") : tl("form.reglamento_scroll_needed") }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!reglamentoScrolled" class="bg-amber-50 border-l-4 border-amber-400 text-amber-800 p-3 mt-3 text-sm flex items-start gap-2">
|
||||
<Icon icon="ph:info" class="text-lg mt-0.5 shrink-0" />
|
||||
<span class="font-secondary">{{ tl("form.reglamento_aviso") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 sm:gap-5" :class="{ 'grid-cols-1': !currentStepData.column || currentStepData.column === 1, 'md:grid-cols-2': currentStepData.column === 2, 'md:grid-cols-3': currentStepData.column === 3 }">
|
||||
<template v-for="field in currentStepData.fields" :key="field.key">
|
||||
<div v-if="shouldShow(field)" class="flex flex-col gap-1.5" :class="{ 'md:col-span-2': field.colspan === 2 && currentStepData.column > 1, 'md:col-span-3': field.colspan === 3 && currentStepData.column > 1 }">
|
||||
|
||||
<span class="text-[#22523F] font-secondary font-medium text-sm leading-tight">
|
||||
{{ tl(field.label) }}
|
||||
<span v-if="field.required" class="text-red-500 ml-0.5">*</span>
|
||||
</span>
|
||||
|
||||
<div v-if="field.readonly" class="py-2.5 px-3 bg-gray-100 border border-gray-200 text-[#6B7280] rounded-xl min-h-[2.5rem] flex items-center text-sm">
|
||||
{{ field.valueFrom ? (formData[field.valueFrom] || "—") : (formData[field.key] || "—") }}
|
||||
</div>
|
||||
|
||||
<label
|
||||
v-else-if="field.type === 'text' || field.type === 'email' || field.type === 'phone'"
|
||||
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
|
||||
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
|
||||
>
|
||||
<Icon :icon="getFieldIcon(field)" class="shrink-0 opacity-50 text-gray-400 text-lg" />
|
||||
<input
|
||||
class="grow text-[#1a1a1a]"
|
||||
:type="getFieldInputType(field)"
|
||||
v-model="formData[field.key]"
|
||||
:placeholder="tl(field.placeholder || field.label)"
|
||||
@input="stepErrors[field.key] = false"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<input
|
||||
v-if="field.type === 'text'"
|
||||
v-model="formData[field.key]"
|
||||
type="text"
|
||||
:placeholder="tl(field.label)"
|
||||
class="input input-bordered w-full rounded-none bg-white border-gray-300 focus:border-[#22523F]"
|
||||
:class="{ 'border-red-500': getFieldError(field.key) }"
|
||||
@input="stepErrors[field.key] = false"
|
||||
/>
|
||||
|
||||
<input
|
||||
v-else-if="field.type === 'email'"
|
||||
v-model="formData[field.key]"
|
||||
type="email"
|
||||
placeholder="email@ejemplo.com"
|
||||
class="input input-bordered w-full rounded-none bg-white border-gray-300 focus:border-[#22523F]"
|
||||
:class="{ 'border-red-500': getFieldError(field.key) }"
|
||||
@input="stepErrors[field.key] = false"
|
||||
/>
|
||||
|
||||
<input
|
||||
v-else-if="field.type === 'phone'"
|
||||
v-model="formData[field.key]"
|
||||
type="tel"
|
||||
:placeholder="tl(field.label)"
|
||||
class="input input-bordered w-full rounded-none bg-white border-gray-300 focus:border-[#22523F]"
|
||||
:class="{ 'border-red-500': getFieldError(field.key) }"
|
||||
@input="stepErrors[field.key] = false"
|
||||
/>
|
||||
|
||||
<input
|
||||
<label
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="formData[field.key]"
|
||||
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
|
||||
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
|
||||
>
|
||||
<Icon icon="ph:calendar" class="shrink-0 opacity-50 text-gray-400 text-lg" />
|
||||
<input
|
||||
class="grow text-[#1a1a1a]"
|
||||
type="date"
|
||||
class="input input-bordered w-full rounded-none bg-white border-gray-300 focus:border-[#22523F]"
|
||||
:class="{ 'border-red-500': getFieldError(field.key) }"
|
||||
v-model="formData[field.key]"
|
||||
@change="stepErrors[field.key] = false"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div v-else-if="field.type === 'textarea'" class="relative">
|
||||
<Icon icon="ph:note-pencil" class="absolute left-3 top-3.5 text-gray-400 opacity-50 shrink-0 pointer-events-none text-lg" />
|
||||
<textarea
|
||||
v-else-if="field.type === 'textarea'"
|
||||
class="textarea w-full bg-white text-[#1a1a1a] pl-10 rounded-xl resize-none min-h-[6rem] focus:outline-2 focus:outline-[#22523F] focus:outline-offset-[-1px]"
|
||||
:class="{ 'border-red-500 focus:outline-red-500': getFieldError(field.key) }"
|
||||
v-model="formData[field.key]"
|
||||
:placeholder="tl(field.label)"
|
||||
:placeholder="tl(field.placeholder || field.label)"
|
||||
rows="4"
|
||||
class="textarea textarea-bordered w-full rounded-none bg-white border-gray-300 focus:border-[#22523F] resize-none"
|
||||
:class="{ 'border-red-500': getFieldError(field.key) }"
|
||||
@input="stepErrors[field.key] = false"
|
||||
></textarea>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="field.type === 'radio'" class="flex flex-col gap-2">
|
||||
<div v-else-if="field.type === 'radio'" class="flex flex-wrap gap-x-5 gap-y-1">
|
||||
<label
|
||||
v-for="opt in field.options"
|
||||
:key="opt.value"
|
||||
class="flex flex-row items-center gap-2 cursor-pointer"
|
||||
class="flex items-center gap-2 cursor-pointer p-1 -ml-1 rounded-lg hover:bg-[#22523F]/5 transition-colors"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
:name="field.key"
|
||||
:value="opt.value"
|
||||
v-model="formData[field.key]"
|
||||
class="radio radio-md radio-[#22523F]"
|
||||
class="radio radio-sm border-gray-300 checked:border-[#4A8C6F] checked:bg-[#4A8C6F]"
|
||||
@change="stepErrors[field.key] = false"
|
||||
/>
|
||||
<span class="text-[#22523F]">{{ tl(opt.label) }}</span>
|
||||
<span class="text-[#1a1a1a] text-sm">{{ tl(opt.label) }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-else-if="field.type === 'checkbox'" class="flex flex-col gap-2">
|
||||
<div v-else-if="field.type === 'checkbox'" class="flex flex-col gap-1">
|
||||
<div
|
||||
v-for="opt in field.options"
|
||||
:key="opt.value"
|
||||
class="flex flex-col gap-1"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<label
|
||||
class="flex items-center gap-3 cursor-pointer p-1.5 rounded-lg transition-colors"
|
||||
:class="currentStep === 0 && !reglamentoScrolled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-[#22523F]/5'"
|
||||
>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="opt.value"
|
||||
class="checkbox checkbox-sm checkbox-[#22523F]"
|
||||
:disabled="currentStep === 0 && !reglamentoScrolled"
|
||||
class="checkbox checkbox-sm border-gray-300 checked:border-[#4A8C6F] checked:bg-[#4A8C6F]"
|
||||
:checked="formData[field.key]?.includes(opt.value)"
|
||||
@change="handleCheckboxChange(field.key, opt.value, $event.target.checked)"
|
||||
/>
|
||||
<span class="text-[#22523F]">{{ tl(opt.label) }}</span>
|
||||
<span class="text-[#1a1a1a] text-sm">{{ tl(opt.label) }}</span>
|
||||
</label>
|
||||
<div
|
||||
v-if="formData[field.key]?.includes(opt.value) && field.levels"
|
||||
class="flex gap-3 ml-7"
|
||||
class="flex flex-wrap gap-x-4 gap-y-1 ml-9 mt-1"
|
||||
>
|
||||
<label
|
||||
v-for="lvl in field.levels.options"
|
||||
:key="lvl.value"
|
||||
class="flex items-center gap-1 cursor-pointer text-sm"
|
||||
class="flex items-center gap-1.5 cursor-pointer text-sm"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
:name="`${field.key}_nivel_${opt.value}`"
|
||||
:value="lvl.value"
|
||||
v-model="formData[`${field.key}_nivel_${opt.value}`]"
|
||||
class="radio radio-sm radio-[#22523F]"
|
||||
class="radio radio-sm border-gray-300 checked:border-[#4A8C6F] checked:bg-[#4A8C6F]"
|
||||
/>
|
||||
<span class="text-[#22523F]">{{ tl(lvl.label) }}</span>
|
||||
<span class="text-[#1a1a1a]">{{ tl(lvl.label) }}</span>
|
||||
</label>
|
||||
<span
|
||||
v-if="getFieldError(`${field.key}_nivel_${opt.value}`)"
|
||||
|
|
@ -368,12 +506,13 @@ const shouldShow = (field) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="getFieldError(field.key)"
|
||||
class="text-red-500 text-xs mt-1"
|
||||
<p
|
||||
v-if="getFieldError(field.key) && (field.type !== 'checkbox' || currentStep > 0)"
|
||||
class="text-xs text-red-500 flex items-center gap-1 mt-0.5"
|
||||
>
|
||||
<Icon icon="ph:warning-circle" class="text-sm shrink-0" />
|
||||
{{ tl("form.required") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -382,60 +521,140 @@ const shouldShow = (field) => {
|
|||
{{ errorMsg }}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between mt-8">
|
||||
<div class="border-t border-gray-300/30 mt-6 sm:mt-8 mb-4 sm:mb-6"></div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
|
||||
|
||||
<div class="flex justify-center sm:hidden">
|
||||
<span class="text-xs text-gray-500 font-secondary font-medium">
|
||||
{{ tl("form.step_counter", { current: currentStep + 1, total: totalSteps }) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center w-full sm:contents">
|
||||
<button
|
||||
v-if="!isFirstStep"
|
||||
type="button"
|
||||
@click="prevStep"
|
||||
class="btn rounded-none border-[#22523F] text-[#22523F] hover:bg-[#22523F] hover:text-white"
|
||||
class="btn bg-white border border-gray-200 text-gray-500 hover:bg-gray-50 hover:text-gray-700"
|
||||
>
|
||||
<Icon icon="ph:arrow-left" class="text-lg" />
|
||||
{{ tl("form.back") }}
|
||||
</button>
|
||||
<div v-else></div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="hidden sm:inline text-xs text-gray-500 font-secondary font-medium whitespace-nowrap">
|
||||
{{ tl("form.step_counter", { current: currentStep + 1, total: totalSteps }) }}
|
||||
</span>
|
||||
|
||||
<div class="relative group">
|
||||
<button
|
||||
v-if="!isLastStep"
|
||||
type="button"
|
||||
@click="nextStep"
|
||||
class="btn rounded-none bg-[#22523F] text-white hover:bg-[#1a3d2f]"
|
||||
>
|
||||
{{ tl("form.next") }}
|
||||
<Icon icon="ph:arrow-right" class="text-lg" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else
|
||||
type="submit"
|
||||
:disabled="isSubmitting"
|
||||
class="btn rounded-none px-8 transition-colors"
|
||||
:class="isSubmitting
|
||||
? 'bg-gray-400 text-white cursor-not-allowed'
|
||||
: isSuccess
|
||||
? 'bg-green-600 text-white'
|
||||
: isError
|
||||
? 'bg-red-600 text-white'
|
||||
:disabled="currentStep === 0 && !reglamentoScrolled"
|
||||
class="btn rounded-none text-sm sm:text-base px-3 sm:px-4"
|
||||
:class="currentStep === 0 && !reglamentoScrolled
|
||||
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
||||
: 'bg-[#22523F] text-white hover:bg-[#1a3d2f]'"
|
||||
>
|
||||
<span v-if="isSubmitting" class="flex items-center gap-2">
|
||||
<span class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
||||
{{ tl("form.sending") }}
|
||||
{{ tl("form.next") }}
|
||||
<Icon icon="ph:arrow-right" class="text-base sm:text-lg" />
|
||||
</button>
|
||||
<div
|
||||
v-if="currentStep === 0 && !reglamentoScrolled"
|
||||
class="absolute bottom-full mb-1 sm:mb-2 left-1/2 -translate-x-1/2 whitespace-nowrap bg-gray-800 text-white text-xs rounded px-3 py-1.5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10 shadow-lg"
|
||||
>
|
||||
{{ tl("form.reglamento_tooltip") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="isLastStep"
|
||||
type="submit"
|
||||
:disabled="isSubmitting"
|
||||
class="btn rounded-none px-3 sm:px-5 transition-colors text-xs sm:text-sm leading-tight min-w-0"
|
||||
:class="isSubmitting
|
||||
? 'bg-gray-400 text-white cursor-not-allowed'
|
||||
: 'bg-[#22523F] text-white hover:bg-[#1a3d2f]'"
|
||||
>
|
||||
<span v-if="isSubmitting" class="flex items-center gap-1 sm:gap-2">
|
||||
<span class="w-3 h-3 sm:w-4 sm:h-4 border-2 border-white border-t-transparent rounded-full animate-spin shrink-0"></span>
|
||||
<span class="truncate">{{ tl("form.sending") }}</span>
|
||||
</span>
|
||||
<span v-else-if="isSuccess" class="flex items-center gap-2">
|
||||
<Icon icon="ph:check-circle" class="text-lg" />
|
||||
{{ tl("form.success") }}
|
||||
</span>
|
||||
<span v-else-if="isError" class="flex items-center gap-2">
|
||||
<Icon icon="ph:x-circle" class="text-lg" />
|
||||
{{ tl("form.error") }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span v-else class="truncate">
|
||||
{{ tl(config.submit_label) }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<div v-else class="bg-[#EBE6D2] p-8 sm:p-12 rounded-none text-center">
|
||||
<img
|
||||
src="/img/logo-metalico.webp"
|
||||
alt="Logo Centro del Reino de Paz y Justicia"
|
||||
class="w-20 h-20 sm:w-28 sm:h-28 mx-auto mb-6"
|
||||
/>
|
||||
<h2 class="text-xl sm:text-2xl font-bold text-[#22523F] font-secondary mb-4">
|
||||
{{ tl("form.success_title") }}
|
||||
</h2>
|
||||
<p class="text-[#1a1a1a]/80 text-sm sm:text-base max-w-lg mx-auto mb-8 leading-relaxed">
|
||||
{{ tl("form.success_info") }}
|
||||
</p>
|
||||
<div class="bg-[#22523F]/5 border border-[#22523F]/10 rounded-xl p-5 sm:p-6 text-left text-sm text-gray-600 max-w-lg mx-auto mb-8">
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon icon="ph:info" class="text-[#22523F] text-lg mt-0.5 shrink-0" />
|
||||
<p>{{ tl("form.success_detail") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<a :href="`/${props.locale}`" class="btn bg-[#22523F] text-white hover:bg-[#1a3d2f] rounded-none px-8">
|
||||
{{ tl("form.back_home") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.reglamento-content h2 {
|
||||
color: #22523F;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.reglamento-content h2:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
.reglamento-content p {
|
||||
color: #374151;
|
||||
line-height: 1.625;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.reglamento-content ul {
|
||||
list-style: disc;
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.reglamento-content li {
|
||||
color: #374151;
|
||||
line-height: 1.625;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.reglamento-content h1 {
|
||||
color: #22523F;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.reglamento-content > p:first-of-type {
|
||||
color: #6B7280;
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,34 +12,60 @@ const tl = createTranslator(Astro.currentLocale);
|
|||
const { locale, section } = Astro.params;
|
||||
const routeKey = getRouteKeyFromSlug(section);
|
||||
|
||||
const items = await getCollection(routeKey, (post)=>{
|
||||
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]
|
||||
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(
|
||||
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(
|
||||
var allYears = routeKey === "news" ? [...new Set(
|
||||
sortedPosts.map(p => new Date(p.data.date).getFullYear())
|
||||
)].sort((a, b) => b - a) : [];
|
||||
}
|
||||
---
|
||||
|
||||
<MainLayout >
|
||||
<MainLayout title={routeKey === "formulario" ? tl("form.page_title") : undefined}>
|
||||
{routeKey === "formulario" && (
|
||||
<div class="pt-16 relative container mx-auto">
|
||||
<Header />
|
||||
</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} />
|
||||
</div>
|
||||
</main>
|
||||
)}
|
||||
|
||||
{routeKey !== "formulario" && (
|
||||
<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>
|
||||
|
|
@ -83,6 +110,8 @@ const allYears = routeKey === "news" ? [...new Set(
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FooterSection />
|
||||
|
||||
</MainLayout>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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 }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue