


Modificar una aplicación de escritorio grande de medios sin congelar la UI ni romper el deshacer
Para una aplicación de escritorio grande que maneja archivos y medios: la IA saca el trabajo pesado del hilo de UI, cubre cancelaciones y casos límite, mantiene exacto el deshacer e informa solo de las pruebas que realmente ejecutó.
Para usar estas reglas de IA
## Seguir toda la ruta de llamadas y dar un único dueño a cada estado
- Comprende la funcionalidad propietaria y las abstracciones existentes antes de editar, y traza la ruta completa de llamadas a través de la interfaz de usuario, la automatización, deshacer, la persistencia y el trabajo en segundo plano.
- Para cambios no triviales, identifica la fuente de verdad, el propietario del estado, el dominio de aislamiento, los invariantes y el comportamiento ante fallos y cancelación antes de escribir código.
- Diseña la arquitectura antes que los detalles de implementación, y prefiere un cambio coherente en lugar de parches dispersos en capas no relacionadas.
- Mantén un único propietario autoritativo para cada fragmento de estado mutable; el estado derivado se calcula o se mantiene mediante una caché con un contrato de invalidación explícito.
- Reutiliza las operaciones de dominio existentes; no añadas una segunda implementación solo porque quien la invoca es una vista previa, una vista, un controlador, una herramienta de automatización o una prueba.
- Ubica el código junto a la funcionalidad que lo posee, reserva las utilidades compartidas para la infraestructura usada por varias funcionalidades independientes, y nombra los tipos y archivos de forma que la propiedad quede clara.
## Escribir comentarios breves y quitar el código muerto
- Mantén los comentarios al mínimo: escribe uno solo cuando el motivo, el invariante, la restricción de seguridad o la solución alternativa no sean obvios, y limítalo a una sola línea breve.
- No narres el código, no repitas los nombres, no describas el parche actual, no dejes rastros de eliminación ni mantengas código comentado.
- Prefiere nombres precisos, tipos pequeños y operaciones extraídas antes que comentarios explicativos.
- Todo cálculo o regla de negocio tiene una única fuente de verdad; nunca la copies a otro archivo o superficie.
- Elimina el código muerto, el estado sin usar, las rutas de compatibilidad obsoletas y los diagnósticos temporales antes de finalizar.
- No añadas código de compatibilidad para plataformas, versiones o configuraciones que el proyecto no admite.
## Sacar el trabajo pesado del hilo de UI y revisar el estado tras cada await
- Trata el hilo de la interfaz de usuario como un recurso escaso: puede contener el estado de la interfaz y una coordinación ligera, pero no debe realizar operaciones de archivos, decodificación, inferencia de modelos, procesamiento de imágenes, indexación, exportación, llamadas bloqueantes ni transformaciones pesadas.
- El aislamiento no es rendimiento: marcar el trabajo como asíncrono o envolverlo en una tarea no lo saca del hilo actual. Sigue al ejecutor real.
- Haz explícitos los saltos entre hilos; toma una instantánea de la entrada inmutable mínima antes de abandonar un hilo, devuelve un valor, y aplícalo después de confirmar que sigue siendo vigente.
- Después de cada punto de suspensión, asume que el estado compartido cambió: revalida la identidad, la generación, la selección y las precondiciones antes de confirmar un resultado.
- Prefiere la concurrencia estructurada; una tarea no estructurada necesita un propietario claro, un identificador almacenado cuando la cancelación importa, y un desmontaje definido.
- La cancelación es cooperativa: los bucles largos la comprueban en puntos útiles y nunca confirman resultados parciales u obsoletos.
- Limita el trabajo paralelo según el recurso escaso real, elimina duplicados de solicitudes idénticas en curso, adquiere y libera las compuertas dentro de los mismos ámbitos, y reanuda cada continuación o callback exactamente una vez.
- Nunca bloquees el hilo de la interfaz de usuario con esperas síncronas, semáforos, bloqueos o sondeo (polling) para trabajo asíncrono.
## Trabajar con archivos fuera del hilo de UI y reemplazarlos de forma atómica
- Toda operación del sistema de archivos se ejecuta fuera del hilo de la interfaz de usuario: lecturas, escrituras, comprobaciones de existencia y metadatos, enumeración, copia, movimiento, reemplazo, eliminación y creación de directorios.
- Asume que cualquier volumen puede ser lento, extraíble, modificado externamente o estar respaldado por red; un tamaño pequeño o un acceso exitoso previo no hacen seguro un acceso síncrono.
- Toma una instantánea de los datos propios del modelo antes de trabajar con archivos, y no mutes el estado observable desde el ejecutor de trabajo de archivos.
- Prepara la salida completa fuera del destino activo, arma el reemplazo en el destino y luego instálalo de forma atómica; dirige las escrituras en vivo a través de un único coordinador en lugar de escribir directamente.
- Serializa las operaciones que apuntan al mismo archivo o destino, de modo que guardar, importar, generar, crear miniaturas, eliminar, exportar y cerrar nunca compitan entre sí (race condition).
- Usa rutas temporales únicas y límpialas tanto en caso de éxito como de fallo o cancelación; nunca elimines ni reemplaces un destino hasta que el reemplazo completo esté listo.
- Muestra los fallos de archivo solicitados por el usuario; no los ocultes detrás de errores ignorados, resultados vacíos o respuestas con apariencia de éxito.
## Medir antes de afirmar una mejora y mantener ligeras las rutas frecuentes
- Identifica las rutas sensibles al rendimiento, como el renderizado, la reproducción, el manejo de entradas, las actualizaciones de vistas, la importación, la indexación, el guardado, la restauración y la exportación, y trátalas como rutas críticas.
- No realices acceso al sistema de archivos, registro (logging), serialización, configuración de modelos o decodificadores, ni otra configuración bloqueante dentro de rutas que se ejecutan por elemento, por fotograma o en interacciones repetidas.
- Mide antes de afirmar una mejora: perfila o realiza pruebas de rendimiento sobre la misma carga de trabajo antes y después del cambio.
- Corrige la complejidad algorítmica y el trabajo innecesario antes de ajustar detalles de bajo nivel; vigila los recorridos anidados, las ordenaciones repetidas, la mutación por copia dentro de bucles, las asignaciones intermedias y los saltos repetidos entre hilos.
- Agrupa y consolida las mutaciones masivas, volcando el estado pendiente antes de instantáneas, deshacer, exportación, cierre y lecturas que deben devolver datos actuales.
- Carga e hidrata los datos de forma diferida (lazy), y almacena en caché el trabajo costoso reutilizable solo con una clave explícita, una capacidad, una regla de invalidación y una política para resultados obsoletos.
- Reutiliza los objetos costosos cuando su ciclo de vida lo permita e invalídalos ante cambios relevantes de configuración; evita que el estado de alta frecuencia, como el progreso y los medidores, invalide vistas completas; limita la memoria retenida.
## Enumerar los casos límite y de fallo antes de implementar
- Trabajar solo en el camino feliz no es suficiente: antes de implementar, enumera los casos límite, de ciclo de vida, de concurrencia y de fallo aplicables, y decide qué capa es responsable de cada uno.
- Valida, según corresponda, las entradas vacías, nulas, cero, negativas, máximas, con desbordamiento, no finitas, malformadas, duplicadas, faltantes, obsoletas y no soportadas.
- Valida antes de realizar aritmética, indexación o conversión numérica; nunca confíes en un `catch` posterior para absorber una trampa aritmética, y define con precisión el redondeo y la limitación (clamping) en lugar de limitar silenciosamente.
- Cubre las operaciones sin efecto (no-op) y repetidas: deben reportar con precisión y no deben generar mutaciones, entradas de historial, trabajo duplicado ni un éxito engañoso.
- Considera la cancelación antes de iniciar, durante cada fase y después de que el trabajo termine pero antes de confirmarlo, y considera la finalización obsoleta tras cambios de selección, destino o configuración.
- Considera las rutas de desmontaje: cierre, salida, suspensión, reactivación, desactivación, cambios de dispositivo y "guardar como" mientras el trabajo está activo.
- Preserva los invariantes en cada ruta de fallo: un éxito parcial debe ser reanudable de forma segura y reportado como tal, o bien revertido.
## Hacer que una acción del usuario se deshaga en un solo paso
- Encauza tanto las acciones del usuario como las automatizadas a través de las mismas operaciones de mutación de dominio y el mismo historial compartido.
- Una intención coherente del usuario produce una única acción deshacible; no expongas los subpasos internos como entradas de historial independientes a menos que cada uno tenga significado por sí mismo.
- Valida los argumentos y las precondiciones antes de abrir un grupo de historial; las operaciones fallidas, canceladas, rechazadas o sin cambios no deben crear entradas vacías.
- El trabajo anidado se fusiona en la acción externa sin cerrar los grupos que pertenecen a la plataforma o a otro subsistema.
- Deshacer restaura el estado exacto, sin redondeos acumulativos, desviaciones en el estado derivado, relaciones huérfanas ni selecciones obsoletas.
- Cuando un cambio afecta al historial, prueba el entrelazado entre ediciones del usuario, ediciones automatizadas, agrupación de eventos de la plataforma y solicitudes concurrentes.
## Diseñar las herramientas de automatización según lo que el usuario quiere lograr
- Diseña las herramientas a partir de la intención del usuario, no de las API internas, las operaciones de almacenamiento o los límites de los servicios; define el resultado, los criterios de éxito, las advertencias, el fallo, la cancelación, la idempotencia y la semántica de deshacer antes que el esquema.
- Una herramienta realiza una acción coherente; una llamada normalmente completa un flujo de trabajo atómico, comprensible y deshacible. No construyas una herramienta amplia con modos no relacionados, y no obligues a quien la invoca a encadenar herramientas de bajo nivel para un trabajo que el propio sistema puede realizar de forma segura.
- Expresa los parámetros en conceptos de dominio orientados al usuario y oculta el diseño del almacenamiento y los detalles de implementación; usa identificadores de entidad estables para la automatización, tratando los índices posicionales y las etiquetas solo como contexto, nunca como identidad duradera.
- Trata todo argumento como no confiable: exige tipos exactos, números finitos, límites explícitos e identificadores válidos; resuelve y valida la solicitud completa antes de mutar, y aplica los cambios que afectan a varias entidades de forma atómica.
- Reutiliza las mismas operaciones de dominio que usa la interfaz de usuario, y no dupliques la lógica de colocación, vinculación o validación.
- Devuelve comprobantes estructurados que describan qué cambió, identificadores estables, el estado sin efecto de forma explícita, advertencias y errores accionables; nunca devuelvas una respuesta con apariencia de éxito cuando el resultado fue ajustado o no se logró, y nunca limites, redirijas o recurras a una alternativa de forma silenciosa a menos que el contrato lo prometa y lo reporte.
- Las herramientas de larga duración exponen un trabajo persistente o un resultado final que quien las invoca puede inspeccionar; mantén los contratos orientados a máquina estables y separados del texto localizado; las descripciones explican cuándo, por qué y cómo interactúa una herramienta con las demás.
## Mantener el estado de la UI en su hilo y respetar el comportamiento nativo de la plataforma
- Mantén el estado observable de la interfaz de usuario en el hilo de la interfaz, y deja que los resultados de segundo plano crucen el límite como valores inmutables.
- Limita la observación a la vista más pequeña que necesita un valor; el estado de alta frecuencia, como el progreso, el hover o la reproducción, no debe invalidar árboles de vistas no relacionados.
- No inicies efectos secundarios persistentes desde el cuerpo de renderizado; usa tareas o controladores conscientes del ciclo de vida con cancelación y desmontaje explícitos, y mantén los cuerpos de renderizado rápidos y libres de efectos secundarios.
- Preserva el comportamiento nativo de la plataforma para el foco, el teclado, la tecla escape, los menús, la restauración de ventanas, deshacer, el estado de arrastre y la confirmación de cierre.
- Los contratos de delegados y controladores de finalización (completion handlers) se completan exactamente una vez en cada ruta de éxito, fallo, cancelación y destino faltante.
- No asumas que un callback de un framework o de finalización llega en el hilo de la interfaz de usuario, salvo que la API lo garantice.
## Procesar medios sin bloquear y conservar exactos los valores de tiempo y fotogramas
- Usa carga asíncrona de propiedades para los recursos externos, y no llames a accesores síncronos obsoletos que puedan bloquear el hilo que los invoca.
- Mantén los valores en su dominio exacto, como enteros precisos de tiempo, fotograma o unidad, durante el mayor tiempo posible, y conviértelos a punto flotante solo en los límites explícitos de la interfaz de usuario o de formatos externos.
- Define y preserva la semántica de origen, como el tiempo, la escala, las transformaciones, el color, el canal alfa, la disposición de canales y los metadatos, a menos que la funcionalidad los modifique explícitamente.
- Reutiliza lectores, contextos de renderizado y pipelines cuando su ciclo de vida lo permita, y no los reconstruyas durante una interacción continua a menos que la invalidación lo requiera.
- Limita los decodificadores, lectores, exportaciones, inferencias y extracciones concurrentes, y propaga la cancelación a través de cada bucle de decodificación, renderizado y exportación, comprobándola entre fragmentos cuando un paso subyacente no se pueda cancelar.
- Realiza pruebas con pistas faltantes, contenedores inusuales, duración cero o indefinida, entradas rotadas o con canal alfa, tasas no estándar y cancelación.
## Usar tokens de diseño en lugar de valores de estilo fijos en el código
- Da estilo a todo a través del sistema compartido de tokens de diseño; nunca codifiques directamente (hardcode) el espaciado, los tamaños, los grosores de fuente, los radios, los anchos de borde, la opacidad, los colores, las sombras o las duraciones de animación.
- Si no existe un token necesario, agrégalo primero al sistema de tokens en lugar de codificar un valor directamente.
- La búsqueda de recursos debe funcionar tanto en la aplicación empaquetada como en las ejecuciones locales y las pruebas; usa la abstracción compartida de búsqueda de recursos en lugar de sondeos específicos de cada funcionalidad, y trata cada estructura de paquete (bundle) como una configuración distinta que debe verificarse.
- No uses indicadores exclusivos de depuración para seleccionar una ruta de recursos o un comportamiento fundamentalmente distinto, a menos que la diferencia sea intencional y esté probada.
- Al añadir un recurso empaquetado, actualiza el manifiesto del paquete, los scripts de compilación y las pruebas, verifica la estructura final empaquetada, y mantén el código opcional controlado por indicadores (feature flags) compilable tanto con el indicador activado como desactivado.
## Dar a cada operación un final visible y registros útiles
- Toda operación iniciada por el usuario o por la automatización debe alcanzar un estado observable de éxito, fallo, rechazo o cancelación.
- No ignores un error en un límite de interacción cuando el fallo cambia el resultado; conviértelo en estado de la interfaz de usuario, en un error de herramienta, en un fallo de trabajo o en un registro accionable.
- El trabajo de tipo "dispara y olvida" (fire-and-forget) solo se permite cuando es explícitamente de mejor esfuerzo; no debe ser responsable de una persistencia obligatoria, y sus fallos deben poder ignorarse de forma segura o quedar registrados.
- Registra los fallos junto con la operación, identificadores estables y la fase del ciclo de vida; nunca escribas secretos, credenciales ni entradas del usuario sin procesar en los registros.
- Conserva suficiente información de diagnóstico para distinguir entre cancelación, fallo de validación, recurso no disponible, fallo del framework y violación de un invariante interno.
- Usa las aserciones (assertions) únicamente para invariantes del programador, no para entradas de usuario recuperables, archivos externos, condiciones de carrera del ciclo de vida o solicitudes de automatización.
## Escribir pruebas pequeñas y deterministas que puedan ejecutarse en paralelo
- Las pruebas unitarias son concisas y se centran en el comportamiento: una prueba establece un solo invariante o una sola regresión, con únicamente la configuración necesaria para dejarlo claro, usando nombres descriptivos y sin comentarios que narren los pasos.
- Usa pruebas parametrizadas para las matrices de casos límite y constructores de fixtures compartidos en lugar de duplicar la configuración entre suites.
- Prueba a través de la interfaz estable más pequeña que demuestre el comportamiento, y evita acoplarte a detalles privados o a recuentos de llamadas incidentales.
- Toda corrección de errores recibe una prueba de regresión que falla ante el defecto original cuando sea posible, y cubre las rutas negativas: validación, operaciones sin efecto, fallo parcial, cancelación, finalización obsoleta y limpieza.
- Las pruebas de concurrencia se coordinan de forma determinista mediante compuertas, continuaciones o puntos de enganche (hooks) inyectados, y no dependen de esperas (sleeps), de la suerte del tiempo o del orden de planificación.
- Las pruebas se ejecutan en paralelo: no compartas estado global mutable, nombres temporales fijos, puertos o directorios; usa directorios temporales únicos y límpialos después, y mantén las credenciales reales, los servicios externos y los archivos reales de usuario fuera de las pruebas unitarias.
## Revisar primero la corrección e informar exactamente qué pruebas se ejecutaron
- Revisa primero la corrección y después el estilo: rastrea el comportamiento modificado a través de quienes lo invocan, el estado compartido, el trabajo en segundo plano, la persistencia, deshacer, la interfaz de usuario y las superficies de automatización.
- Prioriza los hallazgos que causan pérdida de datos, corrupción, caídas, bloqueos, condiciones de carrera, exposición de seguridad, resultados incorrectos, un deshacer roto o un éxito engañoso; después, el rendimiento, la mantenibilidad y el pulido.
- Busca bloqueos del hilo de la interfaz de usuario, errores de concurrencia como estado confirmado tras una suspensión sin revalidación, tareas sin propietario, cancelación faltante, ramificación (fan-out) sin límite, compuertas desequilibradas y callbacks reanudados dos veces, además de lógica de dominio duplicada, cachés inseguras y aritmética de límites sin validar; limita la revisión al cambio y a lo que este afecta directamente.
- Verifica las afirmaciones: ejecuta la compilación y las pruebas apropiadas para el cambio, ejercita las rutas de cancelación y de ciclo de vida en los cambios de concurrencia, y mide los cambios de rendimiento bajo una carga de trabajo representativa.
- Reporta exactamente qué se ejecutó y qué no; no afirmes una verificación completa de compilación, pruebas o tiempo de ejecución cuando solo se completó un subconjunto.
- Mantén los commits enfocados, con un prefijo de categoría preciso y acotado, sin limpieza no relacionada, y describe un cambio por su resumen, su enfoque, las decisiones clave de diseño y la evidencia de pruebas, en lugar de la secuencia de ediciones realizadas.
