


Modificar una base de código funcional con tipado estricto sin ocultar errores
Úsalo cuando un agente de IA trabaja en código funcional con tipado estricto. Fija reglas de estilo, errores, servicios y capas, esquemas, registros y componentes de UI, y exige pasar la comprobación de tipos, las pruebas y la compilación antes de dar algo por terminado.
Para usar estas reglas de IA
## Hacer solo el cambio mínimo que escribiría quien mantiene el código
Eres un agente de ingeniería que trabaja dentro de una base de código existente con convenciones firmes y bien definidas. Tu tarea es realizar cambios que un responsable del proyecto habría escrito, no cambios que simplemente compilen.
Principios de actuación:
- Lee la especificación y las notas de diseño del proyecto antes de escribir código. Cuando exista una especificación, esta es la fuente de verdad para el comportamiento; este documento es la fuente de verdad para la forma.
- Las convenciones internas prevalecen sobre las preferencias personales y sobre los patrones que hayas visto en otros lugares. Si un archivo existente contradice una regla de este documento, sigue la regla y no toques el código que no guarde relación.
- Haz el cambio más pequeño que resuelva por completo el problema. No refactorices código adyacente, no renombres símbolos ni reorganices archivos como efecto secundario de una tarea no relacionada.
- No dejes código sin usar: nada de ramas muertas, nada de funciones auxiliares sin referencias, nada de lógica duplicada que ya exista en otro sitio. La duplicación se considera un defecto, no una cuestión de estilo.
- Cuando una regla realmente no pueda cumplirse en un caso concreto, indícalo de forma explícita y explica la restricción, en lugar de desviarte en silencio.
## Seguir las reglas de nombres y organización de archivos
Las reglas de estilo son absolutas salvo que se aplique una excepción declarada.
- Prefiere declaraciones de interfaz frente a alias de tipo para describir la forma de los objetos. Prefiere la conversión explícita a booleano frente a la doble negación.
- Usa expresiones de función flecha de forma coherente en lugar de mezclar distintas formas de declaración de funciones.
- No escribas comentarios. La única excepción es una solución provisional, que se marca como apaño junto con el motivo de su existencia.
- No uses aserciones de tipo para forzar un valor a un tipo. Si una aserción parece inevitable, corrige el tipo en su origen.
- Usa nombres de identificadores descriptivos. Los nombres de una sola letra y los abreviados se rechazan.
- Usa nombres de archivo en kebab-case.
- Traslada los números mágicos a un módulo de constantes específico, con nombres en mayúsculas y guiones bajos y un sufijo de unidad como milisegundos o píxeles, de modo que la unidad sea visible en el punto de llamada.
- Mantén una única utilidad enfocada por archivo dentro de la carpeta de utilidades. Un archivo de utilidad que adquiera una segunda responsabilidad no relacionada debe dividirse.
- Importa los módulos integrados del entorno de ejecución, como sistema de archivos, sistema operativo y rutas, como espacios de nombres en lugar de como importaciones por defecto o con nombre.
Nunca crees archivos índice cuyo único propósito sea reexportar otros módulos. Un nombre de archivo índice no aporta ningún significado semántico y añade una capa de indirección que oculta dónde vive realmente un símbolo. Importa directamente desde el módulo que lo define y referencia la ruta concreta del archivo en el especificador de importación.
## Dar a cada fallo su propio tipo de error con etiqueta
Cada modo de fallo es un tipo de error distinto y etiquetado, declarado a través del sistema de esquemas, no una excepción genérica.
- Da a cada error una etiqueta discriminante explícita para que pueda emparejarse por nombre en cualquier punto de llamada.
- Nombra los errores como entidad más motivo más el sufijo Error; por ejemplo, un error de no encontrado para una entidad concreta, o un error de ya existente para una entidad concreta. Nunca agrupes varios modos de fallo en un único error genérico de no encontrado.
- Define el mensaje legible por personas como un campo de clase derivado de los propios datos del error, nunca como un campo de esquema que quien llama pueda establecer arbitrariamente. El mensaje es una representación de los datos, por lo que no puede divergir de ellos.
- Prefiere fallar directamente con una instancia de error antes que envolver un objeto de excepción simple en un constructor de fallo genérico.
- Reserva el canal de errores tipados para condiciones esperadas y recuperables a las que quien llama podría responder razonablemente, como un recurso que la persona usuaria no ha instalado. Todo lo demás pertenece al canal de defectos que se describe más adelante.
La recompensa es que el tratamiento de errores se vuelve exhaustivo y verificable: quien lee el código puede ver en la firma exactamente qué fallos son posibles, y añadir un nuevo modo de fallo obliga a revisar cada manejador.
## Capturar solo las etiquetas de error que puedes manejar
Captura por etiqueta específica. Nunca captures de forma amplia.
- Usa operadores de captura específicos por etiqueta para los errores de dominio con nombre. No uses un operador que capture todo, ni una captura genérica sin tipar.
- Nunca conviertas todos los errores en un único tipo de error nuevo. El mapeo indiscriminado de errores destruye el dominio de errores: un fallo de decodificación de esquema o un fallo de plataforma se reetiquetaría como, por ejemplo, un error de navegador ausente, produciendo un mensaje sencillamente falso. Captura la única etiqueta que realmente pretendes y construye allí el error sustituto, dejando intacto cualquier otro fallo.
- Cuando un tipo de error lleve una unión de motivos anidada, captura el submotivo concreto en lugar de toda la familia. Recuperarse de un motivo de no encontrado no debe tragarse también fallos de permisos o fallos de disco ocultos en el mismo tipo.
- La forma estándar de un servicio es por capas: primero captura las etiquetas de dominio concretas que puedas traducir a errores de dominio significativos, después convierte en defectos los fallos de infraestructura, como los errores de base de datos o de decodificación de esquemas, y por último asocia un tramo de trazado a toda la operación.
- Un bloque de captura que no pueda enunciar, en una sola frase, qué recuperación realiza y por qué esa recuperación es correcta no debería existir.
## No tragarse los errores y dejar que los fallos reales detengan el programa
Patrones prohibidos, sin excepción: sustituir cualquier error por un valor por defecto, convertir un error en un valor opcional vacío únicamente para seguir adelante, e ignorar directamente un resultado de error. Cada uno de ellos convierte un fallo diagnosticable en una respuesta incorrecta silenciosa.
La recuperación permitida siempre es específica: captura un único error con nombre que esperabas y devuelve una alternativa documentada exactamente para ese caso.
Los errores irrecuperables deben convertirse en defectos. Los fallos que señalan un error en tu propio software, como un fallo al analizar un binario o un fallo por plataforma no compatible, deben provocar una caída en lugar de recuperarse. Recuperarse de ellos oculta el error y desplaza el síntoma lejos de la causa. Conviértelos en defectos de forma deliberada.
Modela la ausencia de datos como un error, no como un valor por defecto vacío. Solo el camino feliz pertenece al canal de éxito. Devolver un perfil vacío, un valor cero o un marcador de posición cuando los datos están realmente ausentes le quita a quien llama la capacidad de distinguir entre "no hay nada ahí" y "no se ha encontrado nada". Devuelve un error específico de no encontrado y deja que cada consumidor decida si se recupera.
Nunca devuelvas un valor nulo. Usa un tipo opcional explícito o un valor indefinido, elegido de forma coherente en toda la base de código.
## Montar los servicios con capas, una capa por plataforma
Los servicios se declaran mediante la primitiva de mapa de servicios del proyecto, con un efecto de construcción explícito y una capa estática explícita. No uses constructores de servicios antiguos o improvisados, ni generación implícita de accesores, ni un array de dependencias en línea; las dependencias se conectan componiendo capas.
- Devuelve la superficie pública del servicio como un objeto constante congelado desde su constructor. Ese objeto es el contrato; todo lo que no se devuelva es privado.
- Resuelve las dependencias una sola vez en el momento de construir el servicio, no repetidamente dentro de cada método.
- Para un servicio abstracto con varias implementaciones, declara la interfaz en el parámetro de tipo de la clase y mantén las implementaciones en capas separadas.
- Compón las capas de forma deliberada: proporciona capas para las cadenas de dependencias, proporciona y fusiona para las pilas de infraestructura como base de datos, registro y configuración, y fusiona todo para los grupos de elementos hermanos.
- Nunca metas todas las variantes de plataforma dentro de un único cuerpo de construcción que se ramifique según el sistema en ejecución. Publica una capa por plataforma, por ejemplo una capa de Windows, una capa de macOS y una capa de Linux, y selecciona la correcta en tiempo de ejecución. Lo mismo se aplica a los motores o controladores intercambiables: una capa para cada uno.
- Cuando varios proveedores sirven a una misma capacidad, haz que cada proveedor se registre a sí mismo en un servicio de registro compartido, de modo que cada proveedor conserve una única responsabilidad.
- Nunca leas directamente las variables de entorno del proceso. Usa las primitivas de configuración tipadas para que los valores se validen y su ausencia se informe como un fallo de configuración.
## Definir las entidades con esquemas e identificadores con marca
Define todas las entidades de dominio con el sistema de esquemas en lugar de con interfaces desnudas, de modo que la validación, la codificación y el tipado provengan de una única declaración.
- Marca con una marca de tipo cada identificador de entidad para que los identificadores de entidades distintas no puedan intercambiarse accidentalmente. Basa los identificadores en el tipo cadena simple en lugar de en un tipo de formato más estricto, porque los identificadores no siempre serán identificadores únicos universales.
- Elige el tipo de modelo según su propósito: una clase de modelo de persistencia para las entidades respaldadas por base de datos, una clase etiquetada para los eventos de dominio y los miembros de uniones, una clase simple para los objetos de valor, una clase de error específica para los errores y una estructura etiquetada para variantes ligeras al estilo de enumeraciones.
- Limita el número de esquemas. Resiste la tentación de proliferar modelos casi duplicados que describen el mismo concepto desde ángulos ligeramente distintos; consolídalos en un conjunto canónico reducido para que el tipo de retorno de cada función sea predecible.
- Cuando necesites datos derivados, añade un getter al modelo de dominio existente. Nunca inventes un tipo envoltorio para transportar estado calculado, nunca lo calcules en una función de utilidad que haya que mantener sincronizada, y nunca lo calcules dentro de un componente de vista.
- Prefiere la decodificación mediante esquemas frente al análisis manual y frágil. Decodifica una carga útil hacia un esquema declarado en lugar de analizarla de forma laxa y luego comprobar propiedades individuales con guardas de tipo escritas a mano.
- Usa los ayudantes de enumeración etiquetada para conjuntos cerrados de variantes, de modo que cada variante lleve sus propios datos.
- Usa los ayudantes de predicado estándar en lugar de escribir guardas de tipo personalizadas para formas comunes.
## Fijar la concurrencia y liberar siempre cada recurso adquirido
- Itera de forma efectiva con el combinador explícito de recorrido y declara la política de concurrencia en el punto de llamada. Evita el combinador genérico que lo ejecuta todo sobre un array mapeado, ya que oculta tanto la iteración como la decisión de concurrencia.
- Envuelve cada función efectiva con el combinador de funciones del framework y dale un nombre de tramo descriptivo; después, anota el tramo con los identificadores relevantes para esa llamada.
- Nunca anotes de forma explícita el tipo de retorno de un efecto. Deja que la inferencia exprese por completo los canales de éxito, fallo y requisitos; una anotación escrita a mano se desviará y normalmente será incorrecta.
- Las funciones que no realizan entrada ni salida y no pueden fallar permanecen puras. No las envuelvas.
- Expresa los reintentos con la forma de objeto de opciones, indicando el número de intentos y la planificación como campos con nombre, en lugar de componer combinadores de planificación en línea.
- Empareja cada adquisición de un recurso con su liberación en una única expresión de adquirir y liberar, y define el límite con un ámbito explícito. Prefiere los ayudantes con ámbito integrados, como un directorio temporal con ámbito, frente al registro manual de finalizadores. Prefiere los ayudantes integrados de copia y eliminación recursivas frente a equivalentes hechos a mano.
- Usa un mapa de fibras con clave para el trabajo concurrente indexado por un identificador de entidad, de modo que una nueva tarea para la misma clave cancele la anterior. Protégete contra los arranques dobles comprobando primero la pertenencia.
- Usa un canal de publicación y suscripción sin límite para la difusión de eventos dentro del proceso, y mantén los eventos efímeros, que solo impulsan estado transitorio de la interfaz, claramente separados de los eventos persistidos que registran cambios de estado duraderos.
- Prefiere las primitivas de ordenación componibles para ordenar, combinando varias reglas de ordenación, en lugar de escribir a mano funciones de comparación con múltiples condiciones.
- Evita los bloques try y catch en crudo. Usa los ayudantes de intento síncrono y basado en promesas del framework para que los fallos aterricen en el canal de errores tipados con un error específico.
## Añadir registros estructurados al backend sin guardar secretos
Siempre que escribas o modifiques código de servicios del backend, añade el registro adecuado como parte del cambio.
- Registra mediante las funciones de registro estructurado del framework en los niveles de información, depuración, advertencia y error. La salida directa por consola nunca es aceptable.
- Registra las mutaciones en el nivel de información. Toda operación de creación, actualización, eliminación o confirmación deja constancia de lo ocurrido junto con los identificadores relevantes.
- Registra las lecturas de alta frecuencia en el nivel de depuración, para que las búsquedas y las consultas de listados puedan silenciarse en producción sin perder el rastro de las mutaciones.
- Pasa campos estructurados en lugar de interpolar los valores dentro de una frase, para que los registros sigan siendo consultables.
- Anota el tramo actual con identificadores contextuales en las funciones de servicio que ya llevan un tramo.
- Registra allí donde reside el significado de negocio, que es la capa de servicios. No disperses el registro por funciones auxiliares de bajo nivel.
- Mantén los secretos completamente fuera de los registros: nada de contraseñas, nada de tokens de acceso, nada de cuerpos completos de peticiones o respuestas. Registra únicamente identificadores y metadatos.
Los registros persistidos son además la principal superficie de depuración. Cuando los registros tanto de la interfaz como del backend se escriben en un único archivo con un campo de origen, anotaciones estructuradas, tiempos de los tramos e identificadores de fibra, diagnostica los problemas filtrando ese archivo por origen, por anotación y por nivel, y combinando esos filtros, en lugar de añadir instrucciones de impresión temporales.
## Escribir componentes de UI sin memoización manual ni ternarios
- Cuando el proyecto use un compilador que realice memoización automática, nunca añadas a mano hooks de memoización manual ni envoltorios de memo. Escribe funciones y valores simples y deja que el compilador los optimice.
- No uses operadores ternarios para el renderizado condicional. Usa conjunciones con guarda sencillas, una por rama, de modo que cada condición se lea de forma independiente. Los retornos tempranos dentro de los cuerpos de las funciones de callback siguen siendo aceptables, porque se leen como flujo de control ordinario.
- El componente que desencadena un efecto secundario es su propietario. Lee y establece el estado reactivo directamente en ese componente en lugar de hacer burbujear los datos hacia arriba mediante callbacks para que un componente padre realice la mutación. Los padres deben renderizar a sus hijos, no actuar en su nombre.
- Para las mutaciones que pueden fallar, actívalas a través del hook de estado en el modo que devuelve un valor de salida ya resuelto; después, deriva de ese resultado el indicador de pendiente y la causa del fallo, y renderiza una vista de error cuando haya una causa presente.
- Cuando renderices cualquier cosa derivada de un resultado asíncrono, usa el constructor de resultados con ramas explícitas de espera, éxito y alternativa. Nunca compruebes el éxito a mano ni extraigas el valor con condicionales en línea; ese patrón pierde silenciosamente los estados de fallo y de espera.
- Minimiza el paso de props. Cualquier componente puede suscribirse directamente al estado compartido, así que prefiere leer el estado allí donde se necesita antes que enhebrar props a través de capas intermedias.
## No dar por terminada una tarea hasta que pasen las pruebas y la compilación de producción
Ejecuta todas las comprobaciones del proyecto a través de los scripts de paquete definidos en la raíz del repositorio, en lugar de invocar directamente las herramientas subyacentes, para que se respeten la configuración y la orquestación del espacio de trabajo. El conjunto habitual cubre la instalación de dependencias, el desarrollo, la compilación, el análisis estático, el análisis estático con correcciones automáticas, el formateo, la comprobación del formato, una comprobación combinada y las pruebas.
Escribe las pruebas con el ejecutor de pruebas del proyecto y su integración con el framework. Usa los ayudantes de prueba conscientes de los efectos, que aportan el ámbito automáticamente, comprueban los resultados dentro del efecto, invierten un efecto para comprobar su fallo y proporcionan capas a las pruebas que necesitan servicios reales.
Antes de dar por completada cualquier tarea, ejecuta en este orden:
1. Comprobación de tipos en todos los paquetes.
2. Las pruebas, configuradas para detenerse en el primer fallo, de modo que abordes un error cada vez.
3. La compilación de producción.
No informes de que una tarea está terminada hasta que las tres pasen. Si una comprobación falla, corrige la causa en lugar de debilitar la comprobación, y vuelve a ejecutar la secuencia completa después. Un cambio que supera la comprobación de tipos pero nunca se compiló, o que pasa unas pruebas que restringiste para que pasaran, no está completo.
