Por Qué un Módulo Custom y Cuándo Tiene Sentido Crearlo
Drupal.org tiene más de cincuenta mil módulos contribuidos. Pero te toca extender ese módulo contrib que casi hace lo que necesitas y descubres que el patch acabaría más sucio que escribir tu propio módulo desde cero. Ahí entra el desarrollo a medida.
La pregunta no es "¿puedo programarlo?". Es "¿debo programarlo?". Un módulo contribuido con tests, releases de seguridad y mantenedores activos casi siempre gana a código propio que tendrás que arrastrar tú. El custom se justifica cuando la lógica es genuinamente tuya: un algoritmo de pricing con variables que solo existen en tu negocio, una integración con un ERP interno, un flujo de aprobación que ningún Workflow estándar contempla. O cuando necesitas un control de rendimiento que los módulos genéricos no te dan.
Y existe una tercera vía que los que llevamos años en esto explotamos siempre que podemos: usar un contrib como base y extenderlo con hooks, plugins o un EventSubscriber, escribiendo solo la capa fina de personalización. Aprovechas la infraestructura probada y te ahorras reinventar la rueda. Es lo que menos deuda técnica genera, y por eso es lo primero que proponemos en la mayoría de proyectos.
Anatomía de un Módulo Drupal: Ficheros Esenciales
Todo módulo custom en Drupal 10 y 11 vive bajo modules/custom/ con una estructura predecible. El nombre de máquina —minúsculas y guiones bajos, por convención— se repite en la carpeta y en cada fichero que cuelga de ella. Es tedioso al principio. Luego lo agradeces.
El fichero que no puede faltar es mi_modulo.info.yml. Declara el nombre legible, descripción, tipo (module), core compatible, package y dependencias. Sin él, Drupal ni se entera de que existes. A partir de ahí, cada fichero extra desbloquea una capacidad concreta.
mi_modulo.module es el punto de entrada clásico para los hooks procedurales. Drupal lleva años empujando hacia OOP, pero los hooks siguen siendo la vía más directa para alterar comportamiento ajeno sin sobreescribir clases. Aquí defines mi_modulo_form_alter(), mi_modulo_node_presave() o mi_modulo_theme().
Para rutas y páginas usas mi_modulo.routing.yml. Cada entrada empareja un path con un controlador PHP, declara permisos y, si hace falta, pone requisitos sobre los parámetros. El controlador suele vivir en src/Controller/ extendiendo ControllerBase y devuelve render arrays que Drupal convierte en HTML.
La inyección de dependencias se configura en mi_modulo.services.yml. Lo que declares ahí queda registrado en el contenedor de Symfony y disponible para inyección en controladores, plugins o forms. Declarar servicios propios desacopla la lógica de negocio de la capa de presentación y te salva la vida cuando llegan los tests unitarios.
Los formularios extienden FormBase o ConfigFormBase y viven en src/Form/. El esquema de configuración —necesario para que el formulario de ajustes hable bien con el config system— se define en config/schema/mi_modulo.schema.yml.
Hooks, Plugins y Eventos: Tres Mecanismos Complementarios
Saber cuándo usar cada uno es lo que separa al junior del senior. Los hooks son funciones globales que Drupal invoca en momentos específicos del request. Sencillos de escribir, complicados de testear de forma aislada y, hasta hace nada, sin autoloading ni inyección de dependencias nativa. Aun así, para alteraciones puntuales tipo hook_form_alter, hook_entity_presave o hook_preprocess_HOOK, siguen siendo la herramienta correcta.
El sistema de plugins juega en otra liga: OOP puro. Drupal define tipos de plugin —blocks, fields, widgets, formatters, conditions, migrations— y tu módulo aporta implementaciones anotadas con atributos PHP. Crear un Block, por ejemplo, es escribir una clase en src/Plugin/Block/ que implemente BlockPluginInterface y declare su metadata con #[Block]. El descubrimiento es automático: Drupal escanea los namespaces registrados y los encuentra solos. Sin registro manual. Es el patrón que más extensibilidad da con menos acoplamiento.
Los eventos de Symfony llegaron con la 8 y abrieron un tercer canal entre módulos. Despachas un evento personalizado donde te interese, y otro módulo se suscribe implementando EventSubscriberInterface. Sirve para desacoplar módulos que necesitan comunicarse sin conocerse. Caso típico: el módulo de pedidos despacha OrderCompleted, el de notificaciones se suscribe y manda el mail. El de pedidos ni sabe que existe el de notificaciones.
Desde Drupal 11, los hooks también pueden registrarse como métodos de clase con atributos PHP, lo que los acerca al modelo de plugins y eventos. Las fronteras se difuminan, pero la regla mental sigue valiendo: hooks para alterar comportamiento existente, plugins para implementaciones intercambiables de un tipo definido, eventos para notificaciones desacopladas.
Buenas Prácticas de Codificación y Estándares
Drupal impone estándares de codificación derivados de PSR-12 con sus propias rarezas. Cumplirlos no es opcional si quieres que el código aguante el paso del tiempo y la rotación del equipo. PHP_CodeSniffer con los rulesets de Drupal y DrupalPractice metidos en CI cazan las violaciones antes de que aterricen en main.
La inyección de dependencias merece párrafo aparte. Cada vez que tu módulo necesita el entity storage, el mail service, el logger o lo que sea del núcleo, inyectas el servicio por constructor o por el método create() cuando lo instancia el contenedor. Tirar de \Drupal::service() o de helpers estáticos tipo \Drupal::entityTypeManager() funciona, sí. Pero te complica el testing y te acopla a algo que mañana cambiará de sitio.
Separar lógica de negocio de presentación parece de manual, y aun así se viola constantemente. Un controlador no debería tener queries directas ni cálculos pesados; su trabajo es orquestar servicios y devolver una respuesta. Lo gordo va en servicios inyectables que puedan probarse aislados.
La configuración pide cabeza. Los valores por defecto se exportan como YAML en config/install/, y el schema se define para que traducción y formulario de ajustes funcionen. Si necesitas modificar configuración de otro módulo, lo haces vía config/optional/ o desde un hook de instalación. Nunca editando los ficheros del módulo ajeno —eso te lo borra el siguiente composer update.
Testing: PHPUnit, Kernel Tests y Tests Funcionales
Un módulo custom sin tests es una bomba de relojería. Drupal te da varios niveles integrados con PHPUnit, y cada uno tiene su trade-off entre velocidad y cobertura.
Los unitarios (UnitTestCase) corren sin levantar Drupal: rapidísimos, ideales para validar lógica pura de servicios que no tocan el contenedor. Los kernel tests (KernelTestBase) arrancan un Drupal mínimo con SQLite en memoria y prueban interacciones con el entity system o con la config sin overhead de navegador; suelen ser el sweet spot para módulos custom. Los funcionales (BrowserTestBase) levantan Drupal entero y simulan navegador, lo que los hace imprescindibles para forms, permisos y flujos de usuario completos.
Una cobertura razonable: unitarios por cada servicio, kernel tests para la integración con entities y config, y al menos un funcional por ruta o formulario expuesto. La inversión la amortizas en la primera actualización mayor de Drupal que te toque comerte.
Del Entorno Local al Despliegue en Producción
El ciclo arranca en un entorno local reproducible. DDEV o Lando te dan contenedores Docker preconfigurados que se parecen mucho a producción. El código del módulo vive en el repo del proyecto, gestionado con Composer.
El loop habitual: escribes, pasas tests en local, pasas el linter, abres merge request, esperas revisión. El CI corre PHPUnit, PHPStan (nivel 6 como suelo razonable) y el sniffer de estándares. Si algo se rompe, no hay despliegue.
En producción ejecutas drush deploy, que encadena import de configuración, updates pendientes, limpieza de cachés y hooks post-deploy. Las migraciones de datos o cambios de esquema van en hook_update_N(), que Drupal ejecuta secuencialmente y de forma idempotente —cosa que agradeces cuando un deploy se queda a medias y tienes que relanzarlo.
Si tu organización necesita desarrollar módulos personalizados en Drupal para funcionalidades a medida y quiere un equipo con experiencia en arquitectura Drupal empresarial, contacta con nuestro equipo de desarrollo y evaluamos juntos el enfoque técnico.
La Arquitectura como Inversión a Largo Plazo
Un módulo custom no es código que funcione hoy: es código que va a convivir con el proyecto años. Las decisiones de arquitectura —qué mecanismo de extensión usar, cómo cortar los servicios, dónde poner la abstracción— determinan el coste de cada cambio futuro. Un módulo bien planteado absorbe nuevos requisitos con cambios quirúrgicos. Uno mal planteado te obliga a reescribir cada vez que el negocio se mueve.
Invertir en estándares, inyección de dependencias, tests y documentación técnica no es lujo. Es lo que separa un proyecto que escala con cabeza de uno que acumula deuda hasta volverse intratable. Cada hora dedicada a hacerlo decentemente en desarrollo se devuelve multiplicada en mantenimiento y ampliaciones futuras.
Preguntas frecuentes
Las dudas más comunes. Si la tuya no está, pregúntanos
En Tangram no queremos que la información pase por una cadena interminable de intermediarios donde se pierden los detalles importantes.
Contarás con un interlocutor que coordinará la comunicación y trasladará las decisiones al equipo responsable de tu proyecto.
Cuando sea necesario profundizar en cuestiones de estrategia, diseño o tecnología, podremos involucrar a los especialistas correspondientes.
Así tendrás una comunicación organizada, sin necesidad de gestionar personalmente a todos los profesionales que intervienen.
Queremos que tengas acceso al conocimiento de un equipo especializado, sin sufrir la complejidad de coordinarlo.
No tendrás que averiguar a quién escribir cada vez que tengas una duda.
En Tangram contemplamos la figura del Account Manager como interlocutor principal, encargado de acompañarte durante el proyecto, coordinar la comunicación y facilitar el seguimiento de las decisiones.
Nuestra metodología incluye reuniones de revisión, canales de comunicación y herramientas para que puedas participar en el trabajo.
La organización concreta, la frecuencia de las reuniones y los canales disponibles se establecerán según el proyecto contratado.
Lo importante es que sepas con quién hablar, en qué punto está tu proyecto y qué decisiones necesitan tu participación.
Sí, podemos valorar una colaboración con los profesionales que ya trabajan en tu empresa.
No siempre tiene sentido sustituir un equipo que conoce bien tu negocio. En muchas situaciones, lo más útil es complementar sus conocimientos con especialistas en determinadas áreas.
Podemos estudiar qué responsabilidades asumiría Tangram, cuáles mantendría tu equipo y cómo coordinar las decisiones para evitar duplicidades.
La forma concreta de colaboración dependerá de la organización, las herramientas y las necesidades del proyecto.
No se trata de reemplazar lo que ya funciona, sino de aportar el conocimiento que necesitas para llegar más lejos.
No tienes por qué cambiar de proveedor para explorar una colaboración con Tangram.
Durante nuestra trayectoria hemos trabajado con agencias y consultoras, participando en proyectos en los que cada empresa asumía una parte diferente del trabajo.
Esa experiencia nos permite comprender la importancia de definir responsabilidades y coordinar a los profesionales implicados.
Podemos valorar una colaboración para cubrir necesidades concretas que tu proveedor actual no ofrece o para reforzar determinadas áreas.
Si tienes una relación profesional que funciona, no queremos que la rompas sin motivo. Queremos encontrar dónde podemos aportar valor.
La confidencialidad es especialmente importante cuando hablamos de nuevas ideas de negocio, información económica, estrategias comerciales o desarrollos tecnológicos.
En Tangram tenemos experiencia trabajando en proyectos sujetos a acuerdos de confidencialidad, motivo por el que no podemos mostrar públicamente muchos de los trabajos en los que hemos participado.
Si tu proyecto requiere un acuerdo de confidencialidad (NDA) antes de compartir información sensible, podemos revisar sus condiciones y acordar cómo proteger esa información antes de entrar en los detalles.
Entendemos que la confianza empieza por respetar lo que todavía no quieres hacer público.