feat: integrar campos WhatsForm en formulario de visa

Reemplaza los 4 campos básicos del formulario de visa con 40+ campos
del DS-160 organizados en 7 secciones (datos personales, patrocinador,
historial migratorio, dirección, datos familiares, empleo, estudios).
Agrega soporte para tipos heading (divisor de sección) y radio buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Gestoría LP
2026-03-02 05:25:58 +00:00
parent eb030a64f8
commit dda0cc4ba0
4 changed files with 113 additions and 6 deletions

View File

@@ -1104,6 +1104,39 @@ select.form-control {
padding-right: 2.5rem; padding-right: 2.5rem;
} }
.form-heading {
font-size: 1.1rem;
font-weight: 600;
color: var(--color-accent);
margin-top: var(--space-lg);
margin-bottom: var(--space-md);
padding-bottom: var(--space-xs);
border-bottom: 1px solid rgba(192, 192, 192, 0.2);
}
.form-radio-group {
display: flex;
flex-wrap: wrap;
gap: var(--space-sm) var(--space-md);
padding-top: var(--space-xs);
}
.form-radio-label {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: var(--font-size-sm);
color: var(--color-gray-700);
cursor: pointer;
}
.form-radio-label input[type="radio"] {
accent-color: var(--color-accent);
width: 16px;
height: 16px;
cursor: pointer;
}
.form-error { .form-error {
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
color: var(--color-danger); color: var(--color-danger);

View File

@@ -30,8 +30,9 @@ if (!$nombre || !$telefono || !isset($SERVICIOS[$servicio])) {
// Collect service-specific fields // Collect service-specific fields
$datosExtra = []; $datosExtra = [];
foreach ($SERVICIOS[$servicio]['campos_formulario'] as $campo) { foreach ($SERVICIOS[$servicio]['campos_formulario'] as $campo) {
if (empty($campo['name'])) continue; // Skip headings
$val = trim($_POST[$campo['name']] ?? ''); $val = trim($_POST[$campo['name']] ?? '');
if ($campo['required'] && !$val) { if (!empty($campo['required']) && !$val) {
die('Por favor llene el campo: ' . htmlspecialchars($campo['label'])); die('Por favor llene el campo: ' . htmlspecialchars($campo['label']));
} }
$datosExtra[$campo['label']] = $val; $datosExtra[$campo['label']] = $val;

View File

@@ -20,9 +20,69 @@ $SERVICIOS = [
'Acompañamiento el día de la cita', 'Acompañamiento el día de la cita',
], ],
'campos_formulario' => [ 'campos_formulario' => [
// Tipo de trámite
['name' => 'tipo_visa', 'label' => 'Tipo de visa', 'type' => 'select', 'options' => ['Turista (B1/B2)', 'Estudiante (F1)', 'Trabajo (H1B)', 'Otro'], 'required' => true], ['name' => 'tipo_visa', 'label' => 'Tipo de visa', 'type' => 'select', 'options' => ['Turista (B1/B2)', 'Estudiante (F1)', 'Trabajo (H1B)', 'Otro'], 'required' => true],
['name' => 'primera_vez', 'label' => '¿Es primera vez o renovación?', 'type' => 'select', 'options' => ['Primera vez', 'Renovación'], 'required' => true], ['name' => 'primera_vez', 'label' => '¿Es primera vez o renovación?', 'type' => 'select', 'options' => ['Primera vez', 'Renovación'], 'required' => true],
['name' => 'pasaporte_vigente', 'label' => '¿Tiene pasaporte vigente?', 'type' => 'select', 'options' => ['Sí', 'No'], 'required' => true], ['name' => 'pasaporte_vigente', 'label' => '¿Tiene pasaporte vigente?', 'type' => 'select', 'options' => ['Sí', 'No'], 'required' => true],
// Datos personales
['type' => 'heading', 'label' => 'Datos personales'],
['name' => 'fecha_nacimiento', 'label' => 'Fecha de nacimiento', 'type' => 'date', 'required' => true],
['name' => 'lugar_nacimiento', 'label' => 'Lugar de nacimiento', 'type' => 'text', 'required' => true],
['name' => 'curp', 'label' => 'CURP', 'type' => 'text', 'required' => true],
['name' => 'estado_civil', 'label' => 'Estado civil', 'type' => 'radio', 'options' => ['Casado', 'Soltero', 'Viudo', 'Unión libre'], 'required' => false],
['name' => 'nombre_pareja', 'label' => 'Nombre completo de la pareja', 'type' => 'text', 'required' => false],
['name' => 'lugar_fecha_nacimiento_pareja', 'label' => 'Lugar y fecha de nacimiento de la pareja', 'type' => 'text', 'required' => false],
// Patrocinador del viaje
['type' => 'heading', 'label' => 'Patrocinador del viaje'],
['name' => 'quien_paga_viaje', 'label' => '¿Quién paga el viaje?', 'type' => 'text', 'required' => false],
['name' => 'nombre_patrocinador', 'label' => 'Nombre del patrocinador', 'type' => 'text', 'required' => false],
['name' => 'parentesco_patrocinador', 'label' => 'Parentesco con el patrocinador', 'type' => 'text', 'required' => false],
['name' => 'telefono_patrocinador', 'label' => 'Teléfono del patrocinador', 'type' => 'text', 'required' => false],
['name' => 'email_patrocinador', 'label' => 'Email del patrocinador', 'type' => 'text', 'required' => false],
// Historial migratorio
['type' => 'heading', 'label' => 'Historial migratorio'],
['name' => 'ultimos_cruces', 'label' => 'Liste sus últimos 5 cruces a EE.UU. (fechas y motivo)', 'type' => 'textarea', 'required' => false],
['name' => 'visa_perdida_robada', 'label' => '¿Ha perdido o le han robado una visa?', 'type' => 'select', 'options' => ['Sí', 'No'], 'required' => false],
['name' => 'anio_perdida', 'label' => 'Año de la pérdida/robo', 'type' => 'text', 'required' => false],
['name' => 'como_fue_perdida', 'label' => '¿Cómo fue la pérdida/robo?', 'type' => 'textarea', 'required' => false],
['name' => 'visa_negada', 'label' => '¿Le han negado una visa anteriormente?', 'type' => 'select', 'options' => ['Sí', 'No'], 'required' => false],
['name' => 'peticion_migracion', 'label' => '¿Tiene alguna petición de migración en proceso?', 'type' => 'select', 'options' => ['Sí', 'No'], 'required' => false],
// Dirección personal
['type' => 'heading', 'label' => 'Dirección personal'],
['name' => 'direccion', 'label' => 'Dirección completa', 'type' => 'text', 'required' => true],
// Datos familiares
['type' => 'heading', 'label' => 'Datos familiares'],
['name' => 'nombre_padre', 'label' => 'Nombre completo del padre', 'type' => 'text', 'required' => false],
['name' => 'fecha_nacimiento_padre', 'label' => 'Fecha de nacimiento del padre', 'type' => 'date', 'required' => false],
['name' => 'nombre_madre', 'label' => 'Nombre completo de la madre', 'type' => 'text', 'required' => false],
['name' => 'fecha_nacimiento_madre', 'label' => 'Fecha de nacimiento de la madre', 'type' => 'date', 'required' => false],
['name' => 'familiares_eeuu', 'label' => '¿Tiene familiares en EE.UU.?', 'type' => 'select', 'options' => ['Sí', 'No'], 'required' => false],
['name' => 'relacion_familiar', 'label' => 'Relación con el familiar', 'type' => 'select', 'options' => ['Padres', 'Hijos', 'Hermanos', 'Pareja'], 'required' => false],
['name' => 'status_migratorio_familiar', 'label' => 'Status migratorio del familiar', 'type' => 'select', 'options' => ['Residente legal', 'Ciudadano'], 'required' => false],
// Empleo (últimos 5 años)
['type' => 'heading', 'label' => 'Empleo (últimos 5 años)'],
['name' => 'nombre_empresa', 'label' => 'Nombre de la empresa', 'type' => 'text', 'required' => false],
['name' => 'direccion_empresa', 'label' => 'Dirección de la empresa', 'type' => 'text', 'required' => false],
['name' => 'telefono_empresa', 'label' => 'Teléfono de la empresa', 'type' => 'text', 'required' => false],
['name' => 'puesto', 'label' => 'Puesto', 'type' => 'text', 'required' => false],
['name' => 'fechas_trabajo', 'label' => 'Fechas de trabajo (inicio - fin)', 'type' => 'text', 'required' => false],
// Estudios
['type' => 'heading', 'label' => 'Estudios'],
['name' => 'nivel_estudios', 'label' => 'Nivel de estudios', 'type' => 'text', 'required' => false],
['name' => 'nombre_escuela', 'label' => 'Nombre de la escuela', 'type' => 'text', 'required' => false],
['name' => 'direccion_escuela', 'label' => 'Dirección de la escuela', 'type' => 'text', 'required' => false],
['name' => 'telefono_escuela', 'label' => 'Teléfono de la escuela', 'type' => 'text', 'required' => false],
['name' => 'curso_estudios', 'label' => 'Curso o carrera', 'type' => 'text', 'required' => false],
['name' => 'fechas_estudios', 'label' => 'Fechas de estudio (inicio - fin)', 'type' => 'text', 'required' => false],
// Final
['name' => 'comentarios', 'label' => 'Comentarios adicionales', 'type' => 'textarea', 'required' => false], ['name' => 'comentarios', 'label' => 'Comentarios adicionales', 'type' => 'textarea', 'required' => false],
], ],
], ],

View File

@@ -80,26 +80,39 @@ require_once __DIR__ . '/includes/header.php';
</div> </div>
<?php foreach ($servicio['campos_formulario'] as $campo): ?> <?php foreach ($servicio['campos_formulario'] as $campo): ?>
<?php if ($campo['type'] === 'heading'): ?>
<h3 class="form-heading"><?= htmlspecialchars($campo['label']) ?></h3>
<?php else: ?>
<div class="form-group"> <div class="form-group">
<label for="<?= htmlspecialchars($campo['name']) ?>"> <label for="<?= htmlspecialchars($campo['name']) ?>">
<?= htmlspecialchars($campo['label']) ?> <?= htmlspecialchars($campo['label']) ?>
<?= $campo['required'] ? ' *' : '' ?> <?= !empty($campo['required']) ? ' *' : '' ?>
</label> </label>
<?php if ($campo['type'] === 'select'): ?> <?php if ($campo['type'] === 'select'): ?>
<select id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" <?= $campo['required'] ? 'required' : '' ?>> <select id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" <?= !empty($campo['required']) ? 'required' : '' ?>>
<option value="">Seleccione...</option> <option value="">Seleccione...</option>
<?php foreach ($campo['options'] as $opt): ?> <?php foreach ($campo['options'] as $opt): ?>
<option value="<?= htmlspecialchars($opt) ?>"><?= htmlspecialchars($opt) ?></option> <option value="<?= htmlspecialchars($opt) ?>"><?= htmlspecialchars($opt) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<?php elseif ($campo['type'] === 'radio'): ?>
<div class="form-radio-group">
<?php foreach ($campo['options'] as $opt): ?>
<label class="form-radio-label">
<input type="radio" name="<?= htmlspecialchars($campo['name']) ?>" value="<?= htmlspecialchars($opt) ?>" <?= !empty($campo['required']) ? 'required' : '' ?>>
<?= htmlspecialchars($opt) ?>
</label>
<?php endforeach; ?>
</div>
<?php elseif ($campo['type'] === 'textarea'): ?> <?php elseif ($campo['type'] === 'textarea'): ?>
<textarea id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" rows="4" <?= $campo['required'] ? 'required' : '' ?> placeholder="Escribe aqui..."></textarea> <textarea id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" rows="4" <?= !empty($campo['required']) ? 'required' : '' ?> placeholder="Escribe aqui..."></textarea>
<?php elseif ($campo['type'] === 'date'): ?> <?php elseif ($campo['type'] === 'date'): ?>
<input type="date" id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" <?= $campo['required'] ? 'required' : '' ?>> <input type="date" id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" <?= !empty($campo['required']) ? 'required' : '' ?>>
<?php else: ?> <?php else: ?>
<input type="text" id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" <?= $campo['required'] ? 'required' : '' ?>> <input type="text" id="<?= htmlspecialchars($campo['name']) ?>" name="<?= htmlspecialchars($campo['name']) ?>" class="form-control" <?= !empty($campo['required']) ? 'required' : '' ?>>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
<button type="submit" class="btn btn--primary btn--block"> <button type="submit" class="btn btn--primary btn--block">