Componentes Vue personalizados en interfaces generadas, seguros ante la regeneración
Aprende a añadir componentes Vue personalizados en una UI generada sin romper la regeneración, usando patrones de aislamiento, fronteras claras y reglas simples de entrega.

Qué se rompe cuando editas una UI generada
Las UIs generadas están pensadas para reconstruirse. En plataformas como AppMaster, el código de aplicaciones Vue3 se produce desde constructores visuales. La regeneración es la forma en que los cambios se mantienen coherentes entre pantallas, lógica y modelos de datos.
El problema es simple: si editas archivos generados directamente, la siguiente regeneración puede sobrescribir tus cambios.
Por eso los equipos recurren al código personalizado. Los bloques de UI integrados suelen cubrir formularios y tablas estándar, pero las aplicaciones reales normalmente necesitan algunos elementos especiales: gráficas complejas, selectores en mapas, editores de texto enriquecido, firmas o planificadores drag-and-drop. Esos son buenos motivos para añadir componentes Vue personalizados, siempre que los trates como complementos, no como ediciones.
Cuando el código personalizado se mezcla con el generado, los fallos pueden retrasarse y ser confusos. Puede que no los notes hasta que un cambio en la UI obliga a regenerar, o hasta que un compañero ajusta una pantalla en el editor visual. Los problemas comunes incluyen:
- Tu marcado personalizado desaparece porque la plantilla fue regenerada.
- Los imports o el registro fallan porque cambiaron nombres de archivos o la estructura.
- Un “arreglo rápido” se convierte en un conflicto en cada despliegue.
- La lógica generada y la lógica personalizada divergen y empiezan a fallar casos límite.
- Las actualizaciones se sienten arriesgadas porque no sabes qué se reemplazará.
El objetivo no es evitar la personalización. Es hacer que las reconstrucciones sean previsibles. Si mantienes un límite claro entre pantallas generadas y widgets personalizados, la regeneración seguirá siendo rutinaria en vez de estresante.
Una regla de frontera que mantiene la regeneración segura
Si quieres personalizar sin perder trabajo, sigue una regla: nunca edites archivos generados. Trátalos como salida de solo lectura, como un artefacto compilado.
Piensa en tu UI como dos zonas:
- Zona generada: páginas, layouts y pantallas producidas por el generador.
- Zona personalizada: tus componentes Vue escritos a mano en una carpeta separada.
La UI generada debe consumir tus componentes personalizados. No debe ser el lugar donde se construyen esos componentes.
Para que esto funcione a largo plazo, mantén la “frontera” pequeña y clara. Un widget personalizado debe comportarse como un pequeño producto con un contrato:
- Props de entrada: solo lo que necesita para renderizar.
- Eventos de salida: solo lo que la página necesita reaccionar.
Evita acceder al estado global o llamar APIs no relacionadas desde dentro del widget, a menos que eso sea explícitamente parte del contrato.
Con pantallas Vue3 generadas al estilo AppMaster, esto normalmente significa que haces un cableado mínimo en la pantalla generada para pasar props y manejar eventos. Ese cableado puede cambiar entre regeneraciones, pero se mantiene pequeño y fácil de rehacer. El trabajo real permanece seguro en la zona personalizada.
Patrones de aislamiento que funcionan bien con Vue3
El objetivo es claro: la regeneración debe poder reemplazar libremente los archivos generados, mientras tu código de widget permanece intacto.
Un enfoque práctico es mantener los widgets a medida como un pequeño módulo interno: componentes, estilos y utilidades en un solo lugar. En apps Vue3 generadas, eso normalmente significa que el código personalizado vive fuera de las páginas generadas y se importa como dependencia.
Un componente wrapper ayuda mucho. Deja que el wrapper hable con la app generada: lee la forma de datos existente de la página, normalízala y pasa props limpias al widget. Si la forma de datos generada cambia después, a menudo actualizas el wrapper en lugar de reescribir el widget.
Algunos patrones que aguantan bien:
- Trata el widget como una caja negra: props entrantes, eventos salientes.
- Usa un wrapper para mapear respuestas de API, fechas e IDs a formatos amigables para el widget.
- Mantén los estilos scoped para que las páginas regeneradas no sobrescriban tu widget por accidente.
- No dependas de la estructura DOM del padre ni de nombres de clases específicos de la página.
En cuanto a estilos, prefiere CSS scoped (o CSS Modules) y namespace en las clases compartidas. Si el widget necesita coincidir con el tema de la app, pasa tokens de tema como props (colores, espaciado, tamaño de fuente) en lugar de importar estilos de la página.
Los slots pueden ser seguros cuando son pequeños y opcionales, como un mensaje de “estado vacío”. Si los slots empiezan a controlar el layout o el comportamiento central, habrás movido el widget de nuevo a la capa generada, y ahí comienza el dolor de la regeneración.
Diseñar un contrato de componente estable (props y eventos)
La forma más segura de mantener la regeneración indolora es tratar cada widget como una interfaz estable. Las pantallas generadas pueden cambiar. Tu componente no debería hacerlo.
Empieza por las entradas (props). Mantén pocas, previsibles y fáciles de validar. Prefiere primitivas simples y objetos planos que controles. Añade valores por defecto para que el widget se comporte bien incluso cuando la página todavía no pase nada. Si algo puede llegar malformado (IDs, cadenas de fecha, valores tipo enum), validalo y falla con suavidad: muestra un estado vacío en vez de romper.
Para las salidas, estandariza eventos para que los widgets se sientan consistentes en la app. Un conjunto fiable es:
update:modelValueparav-modelchangepara cambios confirmados por el usuario (no cada pulsación)errorcuando el componente no puede completar su trabajoreadycuando el trabajo asíncrono termina y el widget es usable
Si hay trabajo asíncrono, hazlo parte del contrato. Expón props como loading y disabled, y considera errorMessage para fallos del servidor. Si el componente hace fetch por su cuenta, aún así emite error y ready para que el padre pueda reaccionar (toast, logging, UI de fallback).
Expectativas de accesibilidad
Incluye la accesibilidad en el contrato. Acepta una prop label (o ariaLabel), documenta el comportamiento por teclado y mantén el foco predecible después de acciones.
Por ejemplo, un widget de timeline en un dashboard debería soportar flechas para moverse entre ítems, Enter para abrir detalles y devolver el foco al control que abrió un diálogo cuando este se cierre. Esto hace que el widget sea reutilizable en pantallas regeneradas sin trabajo extra.
Paso a paso: añade un widget a medida sin tocar archivos generados
Empieza pequeño: una pantalla que importa a los usuarios, un widget que la haga destacar. Mantener el primer cambio estrecho facilita ver lo que la regeneración afecta y lo que no.
-
Crea el componente fuera del área generada. Ponlo en una carpeta que controles y versionas (por ejemplo
customoextensions). -
Mantén la superficie pública pequeña. Pocas props entrantes, pocos eventos salientes. No pases todo el estado de la página.
-
Añade un wrapper delgado que también controles. Su trabajo es traducir “datos de página generados” al contrato del widget.
-
Integra a través de un punto de extensión soportado. Referencia el wrapper de una manera que no requiera editar archivos generados.
-
Regenera y verifica. Tu carpeta custom, el wrapper y el componente deberían permanecer sin cambios y compilar.
Mantén las fronteras nítidas. El widget se centra en la presentación y la interacción. El wrapper mapea datos y reenvía acciones. Las reglas de negocio permanecen en la capa lógica de la app (backend o procesos compartidos), no escondidas dentro del widget.
Un chequeo útil: si regeneración ocurriera ahora mismo, ¿podría un compañero reconstruir la app y obtener el mismo resultado sin rehacer ediciones manuales? Si la respuesta es sí, tu patrón es sólido.
Dónde poner la lógica para que la UI siga siendo mantenible
Un widget personalizado debería preocuparse sobre todo por cómo se ve y cómo responde a la interacción. Cuanta más lógica de negocio metas en el widget, más difícil será reutilizarlo, probarlo y cambiarlo.
Un buen valor por defecto: mantén la lógica de negocio en la página o en la capa de feature, y deja el widget “tonto”. La página decide qué datos recibe el widget y qué ocurre cuando el widget emite un evento. El widget renderiza y reporta la intención del usuario.
Cuando necesites lógica cerca del widget (formateo, estado pequeño, validación cliente), escóndela detrás de una pequeña capa de servicios. En Vue3 eso puede ser un módulo, un composable o una store con una API clara. El widget importa esa API, no internas aleatorias de la app.
Una división práctica:
- Widget (componente): estado UI, manejo de entradas, visuales, emite eventos como
select,change,retry. - Servicio/composable: moldeado de datos, caching, mapear errores de API a mensajes de usuario.
- Página/contenedor: reglas de negocio, permisos, qué datos cargar y cuándo guardar.
- Partes generadas de la app: sin tocar; pasan datos y escuchan eventos.
Evita llamadas API directas dentro del widget a menos que eso sea explícitamente el contrato del widget. Si el widget es responsable de obtener datos, hazlo evidente (nombralo por ejemplo CustomerSearchWidget y coloca el código de llamadas en un servicio). Si no, pasa items, loading y error como props.
Los mensajes de error deben ser orientados al usuario y consistentes. En lugar de mostrar texto bruto del servidor, mapea errores a un pequeño conjunto de mensajes usados en toda la app, como “No se pudieron cargar los datos. Inténtalo de nuevo.” Incluye una acción de reintento cuando sea posible y registra errores detallados fuera del widget.
Ejemplo: un widget ApprovalBadge no debería decidir si una factura es aprobable. Deja que la página calcule status y canApprove. El badge emite approve y la página aplica la regla real y llama al backend (por ejemplo la API modelada en AppMaster), luego pasa el estado limpio de éxito o error de vuelta a la UI.
Errores comunes que causan dolor tras la siguiente regeneración
La mayoría de problemas no vienen de Vue. Vienen de mezclar trabajo personalizado en lugares que el generador posee, o de depender de detalles que probablemente cambien.
Los errores que suelen convertir un pequeño widget en limpieza recurrente:
- Editar archivos Vue generados directamente y perder la pista de qué cambiaste.
- Usar CSS global o selectores amplios que afectan otras pantallas cuando cambia el marcado.
- Leer o mutar formas de estado generadas directamente, de modo que un renombrado rompa el widget.
- Meter demasiadas suposiciones específicas de la página en un componente.
- Cambiar la API del componente (props/eventos) sin un plan de migración.
Un escenario común: añades un widget de tabla personalizado y funciona. Un mes después, un cambio en el layout generado hace que tu regla global .btn ahora afecte login y páginas admin. O un objeto cambia de user.name a user.profile.name y el widget falla silenciosamente. El problema no era el widget: era la dependencia de detalles inestables.
Dos hábitos previenen la mayoría de esto:
Primero, trata el código generado como solo lectura y mantiene los archivos personalizados por separado con un límite de import claro.
Segundo, mantén el contrato del componente pequeño y explícito. Si necesitas evolucionarlo, añade una prop de versión simple (por ejemplo apiVersion) o soporta ambas formas de prop, vieja y nueva, por una release.
Lista de comprobación rápida antes de lanzar un componente personalizado
Antes de fusionar un widget a medida en una app Vue3 generada, haz un chequeo rápido. Debe sobrevivir a la próxima regeneración sin heroísmos y alguien más debería poder reutilizarlo.
- Prueba de regeneración: ejecuta una regeneración completa y recompila. Si tuviste que re-editar un archivo generado, la frontera está mal.
- Entradas y salidas claras: props entrantes, events salientes. Evita dependencias mágicas como tomar el DOM externo o asumir una store de página concreta.
- Contención de estilos: scopa estilos y usa un prefijo de clase claro (por ejemplo
timeline-). - Todos los estados se ven bien: loading, error y empty deben estar presentes y con sentido.
- Reutilizable sin clonar: confirma que puedes usarlo en una segunda página cambiando props y handlers, no copiando internos.
Una forma rápida de validarlo: imagina añadir el widget a una pantalla admin y luego a un portal de clientes. Si ambos funcionan con solo cambios de props y handlers, estás en un lugar seguro.
Un ejemplo realista: añadir un widget de timeline a un dashboard
Un equipo de soporte a menudo quiere una pantalla que cuente la historia de un ticket: cambios de estado, notas internas, respuestas de cliente y eventos de pago o entrega. Un widget de timeline encaja bien, pero no quieres editar archivos generados y perder el trabajo en la siguiente regeneración.
El enfoque seguro es mantener el widget aislado fuera de la UI generada y colocarlo en la página mediante un wrapper delgado.
El contrato del widget
Mantenlo simple y predecible. Por ejemplo, el wrapper pasa:
ticketId(string)range(últimos 7 días, últimos 30 días, personalizado)mode(compacto vs detallado)
El widget emite:
selectcuando el usuario hace clic en un eventochangeFilterscuando el usuario ajusta rango o modo
Así, el widget no sabe nada del dashboard, modelos de datos o cómo se hacen las peticiones. Renderiza la timeline e informa acciones de usuario.
Cómo conecta el wrapper con la página
El wrapper se ubica junto al dashboard y traduce los datos de la página al contrato. Lee el ID del ticket desde el estado de la página, convierte filtros de UI en un range y mapea registros del backend al formato de evento que espera el widget.
Cuando el widget emite select, el wrapper puede abrir un panel de detalles o disparar una acción de página. Al emitir changeFilters, el wrapper actualiza filtros de la página y refresca datos.
Cuando la UI del dashboard se regenera, el widget permanece intacto porque vive fuera de los archivos generados. Normalmente solo vuelves al wrapper si la página renombró un campo o cambió cómo almacena filtros.
Hábitos de testing y release que evitan sorpresas
Los componentes personalizados suelen fallar de forma mundana: cambia la forma de una prop, deja de lanzarse un evento o una página regenerada renderiza más a menudo de lo que el widget espera. Unos pocos hábitos detectan estos problemas pronto.
Test local: detecta roturas en la frontera temprano
Trata la frontera entre la UI generada y tu widget como una API. Testea el widget sin la app completa primero, usando props hardcodeadas que respeten el contrato.
Renderízalo con props del “camino feliz” y con valores faltantes. Simula eventos clave (guardar, cancelar, seleccionar) y confirma que el padre los maneja. Prueba datos lentos y pantallas pequeñas. Verifica que no escriba en estado global a menos que eso sea parte del contrato.
Si construyes sobre una app AppMaster Vue3, corre estas pruebas antes de regenerar nada. Es más fácil diagnosticar un problema en la frontera cuando no has cambiado dos cosas a la vez.
Regresión tras regenerar: qué revisar primero
Tras cada regeneración, revisa los puntos de contacto: ¿se pasan las mismas props y se manejan los mismos eventos? Ahí suele aparecer la rotura primero.
Mantén la inclusión predecible. Evita imports frágiles que dependan de rutas de archivo que puedan moverse. Usa un punto de entrada estable para tus componentes personalizados.
En producción, añade logging ligero y captura de errores dentro del widget:
- Montado con props clave (sanitizadas)
- Violaciones del contrato (prop requerida ausente, tipo incorrecto)
- Llamadas API fallidas con un código de error corto
- Estados vacíos inesperados
Cuando algo falla, debes poder responder rápido: ¿la regeneración cambió las entradas o cambió el widget?
Siguientes pasos: hacer el patrón repetible en tu app
Una vez que el primer widget funcione, la verdadera ganancia es hacerlo repetible para que el siguiente no sea un apaño.
Crea un pequeño estándar interno para contratos de widgets y documéntalo donde tu equipo guarde notas de la app. Manténlo simple: nombres, props requeridas vs opcionales, un conjunto pequeño de eventos, comportamiento ante errores y propiedad clara (qué vive en la UI generada vs tu carpeta custom).
También escribe las reglas de frontera en lenguaje llano: no editar archivos generados, mantener el código personalizado aislado y pasar datos solo por props y eventos. Evita el “arreglo rápido” que se convierte en impuesto de mantenimiento.
Antes de construir un segundo widget, haz una pequeña prueba de regeneración. Lanza el primer widget y luego regenera al menos dos veces durante cambios normales (un cambio de etiqueta, un ajuste de layout, un campo nuevo) y confirma que nada se rompe.
Si usas AppMaster, suele funcionar mejor mantener la mayor parte de UI y lógica en los editores visuales (UI builders, Business Process Editor y Data Designer). Reserva componentes Vue personalizados para widgets verdaderamente a medida que los editores no pueden expresar, como timelines especializados, interacciones de gráficas o controles inusuales. Si buscas un punto de partida limpio para este enfoque, AppMaster en appmaster.io está diseñado alrededor de la regeneración, así que mantener widgets aislados encaja de forma natural en el flujo de trabajo.
FAQ
Editar archivos Vue generados es arriesgado porque la regeneración puede sobrescribirlos por completo. Aunque tu cambio parezca sobrevivir una vez, una pequeña edición visual en el generador puede recrear las plantillas y borrar tus ajustes manuales.
Coloca todo el código Vue escrito a mano en una carpeta separada que controles (por ejemplo custom o extensions) e impórtalo como dependencia. Trata las páginas generadas como salida de solo lectura y conéctalas a tus componentes mediante una interfaz pequeña y estable.
Un wrapper es un componente delgado que posees y que se sitúa entre la página generada y tu widget. Traduce la forma de datos de la página a props limpias y convierte los eventos del widget en acciones de página; así, si los datos generados cambian después, normalmente solo actualizas el wrapper.
Mantén el contrato pequeño: pocas props con los datos que el widget necesita y pocos eventos para informar la intención del usuario. Prefiere valores simples y objetos planos que controles, añade valores por defecto, valida las entradas y falla con suavidad mostrando un estado vacío en vez de lanzar errores.
Usa update:modelValue cuando el widget actúe como control de formulario y deba soportar v-model. Usa change para acciones “confirmadas” —por ejemplo cuando el usuario pulsa Guardar o termina una selección— así el padre no procesa cada pulsación.
Escopa los estilos de tu widget y usa un prefijo claro de clases para que las páginas regeneradas no anulen tu CSS accidentalmente. Si necesitas coincidir con el tema de la app, pasa tokens de tema como props (colores, espaciados, tamaño de fuente) en lugar de importar o depender de estilos de la página.
Por defecto, deja las reglas de negocio fuera del widget. Que la página o el backend decidan permisos, validaciones y comportamiento de guardado. El widget debe centrarse en renderizar e interactuar y emitir eventos como select, retry o approve.
Evita depender de detalles inestables como rutas de archivos generados, estructura DOM del padre o formas internas de objetos de estado. Si los necesitas, enmascáralos en un wrapper para que un renombrado como user.name a user.profile.name no te obligue a reescribir el widget.
Prueba el widget en aislamiento con props fijas que cumplan el contrato, incluyendo valores faltantes y malformados. Luego regenera y verifica primero dos cosas: ¿se siguen pasando las mismas props? ¿Se siguen manejando los mismos eventos? Ahí suele aparecer la rotura.
No todo requiere código personalizado; reserva componentes a medida para lo que los editores visuales no expresan bien: gráficas complejas, selectores de mapa, firmas, o interfaces drag-and-drop. Si un requerimiento se puede cubrir ajustando la UI y procesos generados, será más fácil de mantener a largo plazo.


