Cómo Implementar Formularios Dinámicos con Lógica Condicional y Validación Avanzada usando Webform en Drupal
Llevo ocho años montando sitios en Drupal y Webform es el módulo que más veces ha salvado un proyecto a contrarreloj. Recuerdo un cliente del sector formación que pidió un formulario de inscripción a curso con becas, descuentos por colectivo y un CIF que solo aparecía si pagaba la empresa. Tres semanas hasta producción. Lo monté entero con Webform, sin una línea de PHP custom, y sigue en pie cuatro años después. Esta guía recoge lo aprendido en clientes reales: cómo construir un sistema dinámico con condiciones y validación seria sin acabar con un módulo personalizado por cada caso.
Instalación con Composer y Submódulos que Sí Necesitas
Webform se instala como cualquier otro módulo contribuido, pero hay detalles que importan. Lo primero, usar Composer. Si lo bajas a mano a modules/contrib, te quedas sin las librerías que necesita para campos como firma o selectores de fecha.
En la raíz del proyecto ejecuto siempre lo mismo:
composer require drupal/webform
drush en webform webform_ui -y
drush cr
Una vez activado, en /admin/structure/webform aparece el panel central. Antes de crear nada, paso por /admin/config/content/webform y reviso tres cosas: que el almacenamiento de envíos en base de datos esté activo (por defecto lo está, pero alguna instalación heredada lo trae apagado), las plantillas globales de correo y los mensajes de confirmación predeterminados.
Los submódulos que activo casi siempre son webform_ui para la interfaz visual y webform_templates cuando el cliente tiene varios formularios parecidos. En un colegio mayor con siete formularios de admisión casi idénticos, los templates ahorraron días. El YAML lo vas a tocar igual, pero para el 80% de los casos la UI de arrastrar y soltar es más rápida y evita errores de indentación.
Diseñar la Estructura Antes de Pensar en Condiciones
El error más típico que veo en proyectos heredados es montar las condiciones sin pensar antes la jerarquía. Webform tiene más de cuarenta tipos de elementos, pero lo que marca la diferencia son los contenedores: fieldsets, details, wizards y containers. Agrupan visualmente y pueden mostrarse u ocultarse en bloque, ahorrando escribir la misma condición en diez campos.
En un alta de proveedor para una empresa de logística, el formulario preguntaba el tipo de proveedor (autónomo, pyme, multinacional) y mostraba bloques distintos según la respuesta. La estructura en el constructor visual quedó así:
- Campo
tipo_proveedor(select) - Fieldset
datos_autonomonombre_completo(text)dni(text)iae(select)
- Fieldset
datos_pymerazon_social(text)cif(text)numero_empleados(number)
- Fieldset
datos_multinacionalrazon_social(text)vat_internacional(text)pais_matriz(select)
La condición se aplica una sola vez sobre el fieldset, no sobre cada campo hijo. Si más adelante añades un campo, cae bajo la condición del padre sin tocar nada. Parece obvio, pero lo veo mal hecho continuamente en auditorías.
Cómo Funcionan los States por Dentro
La lógica condicional se construye encima de la API de States de Drupal core, la misma que usa cualquier formulario nativo. Webform expone esta API en la pestaña Condiciones de cada elemento con un constructor visual donde eliges acción y condición.
Las acciones son mostrar, ocultar, requerir, deshabilitar, marcar checked o expandir. Las condiciones se evalúan contra otros campos: igual a, distinto, vacío, lleno, contiene, mayor que. Se combinan con AND y OR, así que "muestra este campo si cliente=empresa Y sector=tecnología O consultoría" se configura sin tocar JavaScript.
Por debajo, Webform genera un atributo data-drupal-states en el markup y Drupal core se encarga del cliente. Si exportas el formulario a YAML, la condición aparece bajo #states:
'#states':
visible:
':input[name="tipo_proveedor"]':
value: pyme
Conocer esta sintaxis es importante porque hay casos en los que la UI no llega: condicionar sobre un campo dentro de una tabla repetible, por ejemplo. Ahí toca editar el YAML directamente, y saber que el selector es :input[name="..."] ahorra un par de horas.
Cuando la condición depende de un cálculo (suma de dos campos numéricos, fecha calculada), uso el elemento Computed Twig. Evalúa una expresión Twig contra los valores del formulario y deja el resultado en un campo computado al que después aplicas condiciones normales. Más limpio que un hook PHP para algo que es presentación.
Validación de Verdad, no Solo Required
Webform trae validación básica: requerido, formato email, longitud mínima y máxima, tipo numérico. Para proyectos serios no llega, y aquí se separa un formulario que aguanta producción del que se cae el primer día.
Patrones regex en el campo. Cada elemento de texto admite #pattern que se evalúa en cliente y servidor. Para CIF español uso ^[A-HJNP-SUVW][0-9]{7}[0-9A-J]$. Para IBAN, un patrón más largo con código de país. Si el usuario manipula el HTML para saltarse la validación cliente, Drupal vuelve a evaluar el patrón al recibir el POST.
Módulo Webform Validation. Añade reglas que la API base no cubre: comprobar que un valor no se enviase antes (evita duplicados por email en inscripciones), validar que un grupo de checkboxes sume al menos N marcados, o contraseñas con criterios de complejidad.
Validación condicional. Las reglas de obligatoriedad y patrón también se activan bajo condición. En el formulario de inscripción al curso del principio, el IBAN solo era requerido si la persona pedía pago fraccionado. Se configura desde la misma pestaña de condiciones eligiendo "requerir" en vez de "mostrar".
Hooks para lógica de negocio. Cuando la validación va a una base de datos externa o consulta un REST (verificar un código de descuento, comprobar stock), implemento hook_webform_element_validate() en un módulo custom. Tiene acceso al FormStateInterface completo, así que añado errores al campo concreto con $form_state->setErrorByName() y el usuario los ve junto al input, no en un mensaje genérico.
Para validar varios campos como unidad (fecha inicio anterior a fecha fin, total que no exceda un máximo), está Webform Validation Group: configuras los campos del grupo y la regla.
Handlers: lo que Pasa Después del Submit
El submit no es el final, es el principio del flujo de datos. Webform usa handlers para encadenar acciones tras un envío válido, configurables desde la pestaña Emails / Handlers de cada formulario.
El Email handler casi siempre va primero. Soporta tokens para meter cualquier valor del formulario en cuerpo o asunto, y permite varias instancias con condiciones distintas. En el alta de proveedor de logística teníamos tres: uno notificaba a compras siempre, otro a calidad solo si era multinacional, y un tercero al propio proveedor con el resumen enviado.
El Remote post handler envía los datos a un endpoint HTTP en JSON o form-encoded. Lo uso para integrar con HubSpot, Pipedrive o un endpoint interno del cliente, mapeando cada campo del formulario al campo destino sin módulo de integración específico.
El Save to database handler está activo por defecto y guarda cada envío en /admin/structure/webform/manage/[id]/results/submissions. Desde ahí exportas a CSV o JSON, y Webform Views Integration permite construir vistas personalizadas para paneles de seguimiento dentro del propio Drupal.
Para wizards multipaso, Webform guarda el progreso entre páginas automáticamente. Activando el guardado de borradores, un usuario autenticado puede dejar el formulario a medias y retomarlo días después. En una solicitud de subvención para una administración pública, esto fue diferencial: los expedientes se rellenaban en varias sesiones y nadie quería perder media hora de tecleo.
Rendimiento, Accesibilidad y Anti-spam en Webform de Producción
Un Webform bien montado funciona sin tocar nada. Uno mal montado en una página con tráfico tira los Core Web Vitals al suelo. Tres frentes que reviso antes de producción.
Accesibilidad. Webform genera markup con ARIA correcto por defecto, pero al usar condiciones que muestran y ocultan bloques verifico con un lector de pantalla que los cambios se anuncian. Para mensajes de validación dinámica añado aria-live="polite" en el contenedor de errores. En un formulario de una entidad pública nos lo exigieron por auditoría WCAG: dos líneas en el webform.theme.html.twig del subtema.
Rendimiento. Activo agregación de CSS y JS en /admin/config/development/performance, y para formularios incrustados en páginas cacheadas verifico que no rompe el caching. Webform Ajax hace el submit sin recargar página; en móvil la diferencia se nota.
Anti-spam. En cualquier formulario sin login, Honeypot o Antibot son obligatorios. Para los de alto valor (contacto comercial, alta de proveedor) añado reCAPTCHA v3, sin casillas que marcar. He visto formularios sin protección recibir cientos de envíos basura al día saturando bandejas y disparando falsos leads en el CRM.
Antes del lanzamiento, ronda de pruebas con todos los caminos condicionales, campos vacíos, valores extremos en numéricos, intentos de inyección en textos libres y envío desde móvil. Si el cliente lo aprueba sin pasar por ahí, lo paso yo.
YAML, Exportación y el Día a Día Real con Webform
El último consejo es el que más rentabilidad da: trabajar con la exportación YAML del formulario como código versionado. Cada Webform se exporta entero desde la pestaña Origen (Source), o se sincroniza con la configuración de Drupal usando drush cex si está marcado como configuración.
En proyectos donde el formulario evoluciona (campos nuevos, condiciones, handlers ajustados), tener el YAML en Git permite ver el diff de cada cambio, revertir si algo falla y replicar el formulario entre desarrollo, staging y producción sin reconstruirlo a mano. Cuando llegué a un cliente con catorce formularios productivos sin versionar, lo primero fue exportarlos al repositorio y montar despliegue con drush cim. Desde entonces ningún cambio en producción se ha perdido.
Si tu proyecto Drupal necesita formularios serios (procesos de alta, inscripciones, captación de leads, expedientes multipaso) y quieres apoyo de alguien que ha estado dentro del módulo en clientes reales, hablemos de tu caso.