Symfony, evaluado en serio

Y la tensión que aparece con el offline · Orchestra, tarea 003 · 2026-08-24 · panorámica · modo de trabajo · stack anterior

Por qué no lo propuse. Sesgo mío: partí de «un solo lenguaje de punta a punta» y eso me llevó recto a TypeScript en el servidor, sin evaluar alternativas. El argumento del lenguaje único es real, pero no es el único que cuenta — y hay uno que pesa más, en el apartado siguiente.

Tu pregunta primero: assets sin Node

VíaNecesitaQué hace
AssetMapper (Symfony 6.3+)Nada Sirve JS y CSS con importmap, sin empaquetador. En producción compila, versiona por huella y comprime. Es la vía oficial actual y no toca ningún runtime JavaScript.
Tailwind bundleNada Descarga el binario autónomo de Tailwind: un ejecutable, sin runtime JS. Compila y minifica el CSS. Esto responde tu pregunta: sí, se puede.
EncoreNode Es Webpack por dentro. Potente y veterano, pero arrastra el ecosistema que quieres evitar.
Vite via bundleNode o Deno Deno puede correr Vite y sustituir a Node aquí. Pero meter un runtime entero solo para el CSS es peor que no meter ninguno: AssetMapper ya lo resuelve.
Conclusión. Con Twig + AssetMapper + el binario de Tailwind, la aplicación web se construye y se sirve con cero runtime JavaScript en el proyecto. Ni Node ni Deno.

El argumento que pesa más que los técnicos

Tú eres quien aprueba el trabajo de los agentes. Ese es el eslabón que sostiene todo el método: los agentes escriben, y alguien con criterio decide si eso entra o no.

Un stack teóricamente superior que no puedes revisar a fondo convierte tu aprobación en un trámite. Un stack que dominas convierte cada diff en una revisión real. En un proyecto llevado por agentes, la familiaridad del humano que aprueba no es una comodidad: es un control de calidad.

Si dominas Symfony, ese argumento solo pierde frente a un impedimento técnico duro. Y hay uno que hay que mirar de frente: el offline.

Dónde Symfony encaja mejor que TypeScript

Autorización

Los voters de Symfony son exactamente el patrón de la «puerta única»: una pregunta —¿puede este usuario hacer esto sobre este objeto?— resuelta en un solo lugar, con acceso al contexto completo.

Y encajan de lujo con los permisos que caducan: el mandato vencido se evalúa en el voter, no en cada pantalla. Aquí Symfony gana claro.

Actas y documentos

Twig generando el HTML del acta, y de ahí a PDF. Es exactamente para lo que sirve una plantilla de servidor, y es una parte grande del producto.

Páginas públicas

El tablero de la junta y el verificador público de actas son páginas que deben cargar en un teléfono viejo sin JavaScript. Twig las sirve mejor que cualquier framework de cliente.

Reglas de dominio

PHP moderno tipado sostiene bien el dominio: quórum, mandatos, validez de acuerdo. No es tan expresivo como otros lenguajes, pero es más que suficiente y se prueba rápido.

Trabajos en cola

Messenger para enviar citaciones, generar actas y rendiciones. Pieza madura y ya integrada, sin añadir infraestructura.

Convenciones para agentes

Symfony es muy opinado: hay una forma canónica de hacer cada cosa y muchísimo material público. Los agentes rinden mejor copiando un patrón fuerte que inventando.

Dónde Symfony lo pone más difícil

Offline — el problema real

Twig renderiza en el servidor. Sin servidor no hay pantalla. Tomar asistencia en una sede sin señal exige lógica en el teléfono: guardar datos localmente y encolar acciones.

Eso se escribe a mano, con o sin Symfony. La pregunta no es si se puede —se puede— sino cuánta aplicación tiene que funcionar así.

PostGIS con Doctrine

Funciona vía bundles espaciales, pero es menos pulido que en otros ecosistemas. Parte de las consultas geográficas se escribirán en SQL nativo. Es una molestia, no un impedimento.

El catálogo de pantallas

Storybook no existe para Twig. Pero el catálogo se puede hacer como una ruta más de la aplicación que renderiza cada componente en todos sus estados — y eso es incluso más fiel: es la app enseñándose a sí misma, sin herramienta intermedia.

Los snapshots se capturan con Chrome headless, que no necesita Node.

La tensión de fondo, y los tres caminos

Dijiste que el offline es requisito. Con Symfony eso empuja en una dirección concreta, y conviene elegirla a ojos abiertos en vez de descubrirla a medio camino.

Recomendado

Camino 1 · Symfony entero, con islas offline

Toda la aplicación es Symfony con Twig: pantallas, formularios, actas, páginas públicas. Donde hace falta interactividad, Symfony UX. Y las dos o tres pantallas que deben funcionar sin señal —tomar asistencia, registrar acuerdos en la sede— se construyen como islas: una porción de JavaScript autónomo dentro de una página Twig, con su almacenamiento local y su cola de envío.

Sin Node ni Deno en el proyecto. Una sola aplicación, un solo despliegue, un solo lenguaje que dominas.

El offline queda acotado a donde de verdad importa, en vez de contaminar toda la arquitectura.

El catálogo de pantallas es una ruta de la propia app: imposible que se desincronice del producto.

Las islas hay que escribirlas a mano y mantenerlas con disciplina, o se convierten en una aplicación de cliente mal hecha por acumulación.

Si dentro de un año media aplicación tiene que ser offline, este camino se queda corto.

Más capaz, más caro

Camino 2 · Symfony como API + aplicación de cliente aparte

Symfony expone la API y sirve las páginas públicas y los documentos. Una PWA aparte —TypeScript— se encarga de todo lo que usa el dirigente, con offline pleno.

Cumple todos los requisitos sin concesiones, y el offline es de primera clase en todas las pantallas.

Deja el camino abierto a una app de tienda el día que se quiera.

Dos ecosistemas, dos despliegues, y Node vuelve por la puerta del frontend.

Es el doble de superficie para que un agente se equivoque, y el doble de revisión para ti.

Punto medio

Camino 3 · Symfony con cliente ligero sobre importmap

Como el camino 1, pero el JavaScript de las islas se escribe con una biblioteca pequeña traída por importmap de AssetMapper — sin empaquetador y sin node_modules.

Más orden que escribir JavaScript suelto, sin traer el ecosistema completo.

Herramientas de desarrollo más pobres para esa parte: sin tipos y sin las comodidades del ecosistema.

Qué cambia respecto al stack anterior

CapaAntesAhoraNota
Base de datosPostgres + PostGISIgualNo cambia nada
Acceso a datosDrizzleDoctrineMigraciones versionadas; SQL nativo para lo geográfico
DominioTypeScript puroPHP tipado puroMisma idea: sin base de datos, sin HTTP, probado al milisegundo
API y pantallasFastify + ReactSymfony + TwigUna sola aplicación en vez de dos
ContratosZod → OpenAPIDTOs → OpenAPILa regla «contrato antes que pantalla» se mantiene intacta
EstilosTailwind con NodeTailwind binarioMismo Tailwind, sin runtime JS
CatálogoStorybookRuta propia de la appMás fiel, y una dependencia menos
SnapshotsPlaywrightChrome headlessCapturas sin Node
PruebasVitestPHPUnitEl dominio sigue probándose en milisegundos
Puerta únicaPaquete propioVoters de SymfonyMejora: es el patrón nativo del framework
OfflineToda la appIslas acotadasEl cambio de fondo. Es lo que hay que decidir
Lo que no cambia, y no debe cambiar. Contrato antes que pantalla. Dominio puro separado. Puerta única a los datos. Módulos verticales. Datos falsos buenos. Esas cinco decisiones eran de arquitectura, no de stack, y siguen valiendo igual en PHP.

Lo que hace falta decidir

  1. Symfony sí o no. Si lo dominas, mi recomendación es que sí.
  2. Cuánto offline. Islas acotadas (camino 1) o offline pleno con cliente aparte (camino 2). Es la decisión que no se corrige gratis.