Buscar/Trabajar en un monorepo frontend de TypeScript sin saltarse los límites entre paquetes

Trabajar en un monorepo frontend de TypeScript sin saltarse los límites entre paquetes

Para cuando una IA cambia un frontend modular en TypeScript. Cubre los límites entre paquetes y un núcleo sin framework, cambiar el estado solo por su acción dueña, dónde van archivos y pruebas, los checks antes del commit, el orden de release y las capas de UI.

Para usar estas reglas de IA
## Cruza los límites entre paquetes solo por sus exports públicos - En un workspace con múltiples paquetes, asigna a cada paquete una única responsabilidad y cruza los límites solo a través de las exportaciones públicas del paquete; nunca importes elementos internos de otro paquete desde el código de la aplicación, ya que las importaciones internas acoplan a quien las usa con la estructura privada y se rompen cuando esta cambia. Aplica los límites mediante lint automatizado y verificaciones de arquitectura en lugar de depender de la revisión, y cuando no exista una ubicación correcta, créala deliberadamente y actualiza las convenciones documentadas. - Mantén el núcleo compartido agnóstico de framework y libre de DOM del navegador: coloca los bindings de framework de UI y la proyección de DOM/CSS en capas dedicadas, y carga las dependencias pesadas en tiempo de ejecución, como módulos WASM o SDKs grandes, desde un único módulo central que entregue a quien las usa una instancia inyectada, en lugar de importarlas de forma improvisada en cada punto de uso. - No crees archivos shim de reexportación cuyo único propósito sea reenviar la API de otro paquete; importa directamente el paquete propietario en los puntos de uso. Un barrel de compatibilidad puede reexportar a un propietario únicamente para preservar una API pública ya establecida. - Prefiere exportaciones de subruta específicas y acotadas antes que un único barrel general, cuando importaciones más pequeñas dejan más clara la intención de la dependencia. ## Cambia el estado compartido solo mediante la acción que lo posee - Construye los subsistemas grandes a partir de un contexto compartido más módulos de acción específicos de cada dominio (viewport, selección, historial, estructura, etc.) en lugar de un único archivo que crece sin parar. Antes de añadir comportamiento, inspecciona la carpeta existente y los patrones cercanos, y coloca el cambio en el módulo de dominio más afín; no amontones lógica no relacionada en el archivo que esté abierto en ese momento. - Encamina toda mutación de estado a través del setter o la acción propietaria para que los efectos secundarios —eventos, agrupación de deshacer, actualizaciones derivadas— se disparen de forma consistente. Nunca asignes directamente a campos de estado compartido, o esos efectos dejarán de ejecutarse silenciosamente, y no mantengas estado mutable a nivel de módulo dentro de los componentes; guarda el estado compartido en el store. - Expón los cambios a través de un sistema de eventos tipado y haz que los suscriptores reaccionen a los eventos en lugar de hacer polling. Haz que los módulos de dominio compartan estado a través del objeto de contexto, no importando código de la aplicación o del framework. - Los wrappers de la aplicación pueden añadir estado reactivo, persistencia y E/S de plataforma alrededor del núcleo, pero el núcleo se mantiene agnóstico de framework; protege cualquier API del navegador en código compartido con una verificación explícita en tiempo de ejecución antes de usarla. ## Prohíbe any y las aserciones non-null en TypeScript - Prohíbe `any`: usa tipos precisos, genéricos y declaration merging. Prohíbe las aserciones non-null: usa guards, optional chaining y nullish coalescing. Ambas prácticas mantienen al verificador de tipos capaz de detectar errores reales en lugar de ocultarlos. - Prefiere tipos union precisos antes que primitivos laxos —una pequeña union de literales de cadena en lugar de `number | string | null`— y no definas un tipo en línea cuando ya exista un tipo compartido con nombre; impórtalo y reutilízalo. - Nombra las constantes repetidas o que se usan entre varias features en un módulo de constantes compartido, en lugar de duplicar literales por todo el código base. - Nunca uses una fuente aleatoria no criptográfica para identificadores ni para nada relevante en materia de seguridad; usa siempre un RNG criptográfico. Al mutar datos anidados, haz una copia profunda de los objetos anidados con un structured clone, nunca con un spread superficial. ## Agrupa carpetas por dominio y pon cada tipo de prueba en su sitio - Organiza por espacio de nombres de dominio en lugar de una ceremonia estructural excesiva, y mantén los servicios de la aplicación, las vistas de rutas o layout, y la UI en sus propias áreas de nivel superior. - Usa una subcarpeta para un dominio de varios archivos en lugar de archivos hermanos que comparten un prefijo repetido. Cuando un dominio gana su segundo archivo, crea la carpeta de inmediato en lugar de prefijar los nombres; prefiere `selection/hit-test` antes que `selection-hit-test`. - Mantén en minúsculas los nombres de archivo de punto de entrada convencionales como `index`, `types` y `context`, y sigue el casing propio del ecosistema para los archivos de componentes. No coloques código ni pruebas de forma improvisada; si no existe una ubicación adecuada, créala deliberadamente y documéntala. - Mantén la ubicación de las pruebas estricta y basada en la ubicación: coloca cada tipo de prueba en su directorio establecido (end-to-end, integración, unitarias, helpers compartidos, por paquete). El comportamiento visible para el usuario pertenece a las pruebas end-to-end; las aserciones de estado interno y de datos pertenecen a las pruebas unitarias. Nunca hagas commit de archivos de prueba temporales o de profiling. ## Usa una librería existente en vez de escribir el mismo código a mano - No reimplementes a mano lo que una dependencia ya hace. Revisa primero las dependencias existentes; si ninguna encaja, adopta una librería de calidad en lugar de escribir la implementación en línea —por ejemplo, usa una librería de diff para diffs unificados y una librería de color para cálculos de color, no un bucle escrito a mano. - Antes de construir UI, controles o composables personalizados, lee la documentación oficial de la dependencia en lugar de adivinar a partir del uso local, dando preferencia a los puntos de entrada de documentación legibles por máquina cuando estén disponibles. Si la documentación oficial contradice los patrones locales, sigue las APIs oficiales actuales y actualiza los wrappers locales de forma deliberada. - Coloca los programas y helpers mantenibles en un área de herramientas dedicada, organizada por dominio, cada uno con pruebas específicas, en lugar de como scripts sueltos en la raíz. Reserva un área de scripts únicamente para pequeños shims de punto de entrada de compatibilidad que importen desde el área de herramientas, manteniendo la lógica de implementación fuera de los shims. - Incluye las pruebas de las herramientas, además de la cobertura de lint y formato, en las verificaciones estándar, de modo que el área de herramientas se someta al mismo nivel de exigencia que el código de producto. ## Pasa todos los checks de calidad antes del commit y usa Conventional Commits - Ejecuta la puerta de calidad completa antes de hacer commit y de nuevo antes de abrir un cambio a revisión: lint y typecheck conscientes de tipos con cero errores, el formateador con ordenamiento de imports, detección de copy-paste y duplicación con cero clones, y las suites relevantes de pruebas unitarias y end-to-end. - Autorrevisa antes de enviar: si la duplicación aumenta, extrae helpers compartidos o reutiliza tipos existentes; elimina las definiciones de tipo en línea que dupliquen tipos con nombre y cualquier lógica copiada y pegada; mantén los archivos por debajo de unos pocos cientos de líneas y divídelos por dominio cuando crezcan. - Escribe los mensajes de commit siguiendo Conventional Commits (`feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `build`, `ci`, `chore`): una primera línea corta, imperativa, con scope opcional y tipo en minúsculas, y la justificación en el cuerpo, con cada línea del cuerpo comenzando con una palabra en mayúscula. - Conserva el casing de nombres de producto y siglas en los asuntos y cuerpos en lugar de aplanar las siglas a texto en minúsculas, y usa el scope más estrecho y honesto posible —u omite el scope cuando un cambio abarca áreas no relacionadas. ## Publica releases en un orden fijo y mantén al día el changelog - Sigue un procedimiento de release fijo y ordenado: incrementa la versión en cada archivo que la contenga, mueve las entradas del changelog de la sección "Unreleased" a un nuevo encabezado de versión con fecha, haz commit con un mensaje de release y luego crea y sube el tag. Confirma que existan los secretos de firma o de release necesarios antes de crear el tag. - Trata el workflow de build y release como la única fuente de verdad para el empaquetado y la publicación, y mantén sincronizada con él cualquier lista de paquetes duplicada. Condiciona los despliegues a producción a tags de release o a un dispatch manual explícito, nunca a pushes normales a la rama por defecto, para que el lanzamiento siga siendo intencional. - Mantén la documentación al día: actualiza la sección "Unreleased" del changelog y la documentación orientada al usuario cada vez que cambie el comportamiento, y actualiza la referencia para colaboradores o agentes cuando cambien la arquitectura o las convenciones. - Mantén la documentación publicada libre de planes de implementación especulativos o internos —coloca los planes temporales en un área de borrador ignorada y destila la dirección duradera en la documentación canónica— y no crees copias localizadas de relleno; enlaza la navegación localizada a la página canónica hasta que exista una traducción real. ## Impide que los componentes base de UI compartidos importen código de la app - Separa una capa de primitivos de UI compartida y reutilizable de las capas de features y de shell. Los primitivos compartidos y los controles con estilo de bajo nivel no deben importar servicios de la aplicación, stores ni paneles de features; los componentes de cada feature son dueños de sus propios controles específicos. - Resuelve los estilos mediante variantes de tema estáticas y tipadas, definidas en un solo lugar y combinadas por instancia, en lugar de dispersar ramas condicionales de clases utilitarias o props de clase puntuales por slot. Vincula el estado visual a través de atributos de datos semánticos y resuelve las variantes tipadas en el script. - Para los localizadores de pruebas, prefiere el role, la label y el texto orientados al usuario; expón atributos semánticos con alcance acotado para la identidad, y reserva los test ids dedicados para los límites de integración poco frecuentes, en lugar de fabricar ids globalmente únicos dentro de componentes compartidos. - No pases setters ni acciones imperativas a través de slots, a menos que un componente sea explícitamente un primitivo renderless; prefiere los bindings de modelo, los eventos emitidos y las props normales. Usa una librería de íconos en lugar de SVG inline crudo o símbolos Unicode, y mantén las labels semánticas renderizando los atajos de teclado a partir de los metadatos del comando en lugar de incrustar el texto del atajo directamente en las labels.