


Añadir código de backend y las pruebas adecuadas a una aplicación con varios servicios
Para un agente de IA que cambia una aplicación con varios servicios y trabajadores en segundo plano. Revisa el entorno y los servicios, pone el código de base de datos y de tareas en su sitio, define trabajos y errores según el proyecto, elige un tipo de prueba y planea antes de programar.
Para usar estas reglas de IA
## Preparar el entorno y comprobar que los servicios están en marcha
- Las dependencias del backend viven en un entorno virtual gestionado por lockfile ubicado en la raíz del repositorio. Si aún no existe, créalo a partir del lockfile congelado y actívalo antes de ejecutar nada.
- Las credenciales y claves que necesitan las pruebas viven en el archivo de entorno local del repositorio. Léelas de ahí en lugar de inventar valores o solicitar unos nuevos.
- Da por supuesto que todos los servicios de la aplicación ya están en ejecución, pero verifica en vez de confiar: revisa el directorio de logs y confirma que el servicio correspondiente sigue emitiendo entradas.
- Accede a la base de datos a través del contenedor en ejecución usando la invocación del cliente documentada por el proyecto, no mediante una conexión improvisada propia.
- Cuando ejercites la aplicación web con automatización de navegador, inicia sesión con la cuenta que crea la configuración de extremo a extremo. Si no existe, regístrala desde la página de registro, ya que la primera cuenta registrada se convierte en la administradora.
## Mantener las consultas a la base de datos en una sola capa y enviar las llamadas de prueba a través del frontend
- Coloca toda operación de base de datos dentro de los directorios de acceso a datos designados. No ejecutes consultas desde ningún otro sitio, para que el conocimiento del esquema quede en una sola capa y pueda revisarse en un solo lugar.
- Cuando llames al backend durante pruebas manuales o con scripts, pasa siempre por el origen del frontend en lugar de ir directamente al puerto del backend. Una petición que se salta el frontend también se salta el proxy, la sesión y el comportamiento de autenticación por los que pasa el tráfico real, así que el resultado no es representativo.
## Colocar el trabajo nuevo en el grupo de trabajadores en segundo plano que le corresponde
El trabajo asíncrono se reparte entre grupos de trabajadores especializados en lugar de una única cola general. Coloca el trabajo nuevo en el grupo cuyo perfil le corresponda.
- Un grupo principal coordina las operaciones de todo el sistema: borrado de recursos, sincronización, poda y comprobaciones periódicas.
- Los grupos de obtención descargan datos de fuentes externas y generan una tarea de procesamiento por cada lote, con vigilancia de watchdog para detectar una fuente estancada.
- Los grupos de procesamiento ejecutan la canalización de ingesta: persisten los registros, los dividen en fragmentos, los enriquecen, los convierten en embeddings, los escriben en el índice de búsqueda y actualizan los metadatos.
- Un grupo ligero atiende operaciones cortas con alta concurrencia. Un grupo pesado atiende operaciones intensivas en recursos con baja concurrencia.
- Un grupo de monitorización, de un solo hilo porque la monitorización no necesita paralelismo, recoge la profundidad de la cola, la memoria y las métricas de salud.
- Un grupo planificador es dueño de los disparadores periódicos, desde comprobaciones por intervalos medidas en segundos hasta limpiezas cada hora, y resuelve el inquilino de cada tarea programada.
Propiedades compartidas por todos los grupos: grupos de hilos en lugar de procesos por estabilidad, aislamiento por inquilino, colas de prioridad, latido y comprobación de vitalidad, reintento automático y recuperación ante fallos, un almacén compartido rápido para la coordinación entre procesos, y la base de datos relacional para el estado duradero de las tareas.
## Dar a cada tarea en segundo plano una expiración y su propio límite de tiempo
- Declara las tareas con el decorador del framework independiente de la aplicación, en vez de vincularlas a una instancia concreta de la aplicación, para que importar una tarea nunca arrastre consigo la construcción de la aplicación.
- Mantén los módulos de tareas dentro de los directorios de tareas designados para su edición o nivel.
- Nunca encoles una tarea sin caducidad. Indícala tanto si la tarea se envía desde la programación como si se envía desde otra tarea. Entregar código que encola sin caducidad no es aceptable, porque permite que la cola crezca sin límite.
- Como las tareas se ejecutan en grupos de hilos, las funciones de límite de tiempo propias del framework quedan desactivadas de forma silenciosa. Implementa la lógica de tiempo de espera dentro de la propia tarea.
- Los trabajadores no recargan al cambiar el código. Después de cambiar el código de un trabajador, pide al responsable de mantenimiento que reinicie el trabajador antes de probar, y no des por hecho que el código nuevo está activo.
## Añadir endpoints y lanzar errores con el tipo de error propio del proyecto
- Al añadir un endpoint, tipa directamente la función handler en lugar de declarar un campo de modelo de respuesta aparte.
- Lanza el tipo de error propio del proyecto en lugar de la excepción HTTP genérica del framework, y nunca escribas códigos de estado a mano ni importes constantes de estado del framework.
- Un handler global convierte el error del proyecto en un único cuerpo de respuesta estándar que lleva un código de error y un detalle. Esto elimina código repetitivo y mantiene coherente el manejo de errores en todo el backend.
- Los códigos de error provienen de un registro central. Si hace falta una categoría nueva, añádela primero al registro; nunca inventes un código en el punto de llamada.
- Cuando reenvíes un fallo de un servicio upstream cuyo estado proviene de la respuesta de dicho servicio, pasa ese estado mediante el argumento de sobrescritura de estado del tipo de error, en lugar de abandonar el tipo de error.
## Mantener tipado estricto y escribir solo comentarios que sigan siendo útiles
- Instala y ejecuta los hooks de pre-commit del repositorio sobre todos los archivos antes de proponer un cambio.
- Mantén todo estrictamente tipado tanto en el backend como en el frontend. Las vías de escape sin tipado no son un atajo aceptable.
- Mantén los comentarios breves y limitados a información que siga siendo relevante a largo plazo. No escribas comentarios que narren el cambio en curso, como qué se acaba de añadir, quitar o refactorizar; se convierten en ruido en el momento en que el cambio se integra.
## Escribir a mano las migraciones de base de datos para el esquema predeterminado y el privado
- Genera el archivo de revisión con la herramienta de migración y luego escribe a mano el cuerpo de la migración dentro del archivo que esta crea. No confíes en la autogeneración para expresar la intención.
- Los despliegues multiinquilino necesitan que la migración se aplique contra la configuración de esquema privado además de contra la predeterminada, y la misma distinción se aplica al crear la revisión.
- Trata la migración como parte del cambio sometido a revisión, no como algo secundario que se aplica después.
## Elegir el único tipo de prueba que demuestra el cambio, con pocos mocks
Existen cuatro niveles. Prefiere los niveles de mayor fidelidad antes que el uso intensivo de mocks, y elige el que realmente demuestre el comportamiento.
- Las pruebas unitarias dan por supuesto que ningún servicio es accesible y simulan con mocks toda interacción con el mundo exterior. Escríbelas solo para módulos complejos y aislados cuya lógica merezca fijarse por sí sola.
- Las pruebas unitarias respaldadas por dependencias dan por supuesto que las dependencias externas están en ejecución —base de datos, caché, almacenamiento de objetos, índice de búsqueda, red saliente— mientras que los contenedores de la aplicación no lo están. Las funciones se llaman directamente y algunos componentes seleccionados pueden seguir simulándose con mocks. Úsalas para minimizar el uso de mocks conservando la capacidad de controlar comportamientos inestables o de verificar que una llamada interna ocurrió con argumentos concretos, algo que una prueba de integración pura no puede hacer.
- Las pruebas de integración se ejecutan contra un despliegue real y no simulan nada. Prefiere estas, y recurre a las pruebas unitarias respaldadas por dependencias cuando el uso de mocks o la verificación interna sean realmente necesarios.
- Las pruebas dirigidas por navegador añaden el servidor web encima del despliegue completo. Úsalas para comportamientos que necesiten una coordinación sustancial entre frontend y backend.
No pruebes de más. Un cambio dado suele necesitar un tipo de prueba, no los cuatro.
## Reutilizar los fixtures y helpers existentes al escribir pruebas de integración
- Antes de escribir una prueba de integración, lee la configuración raíz de pruebas para ver los fixtures disponibles y el directorio de utilidades compartidas para ver los helpers.
- Prefiere los helpers de tipo manager de esas utilidades antes que llamar a los endpoints directamente con una biblioteca HTTP en crudo, y prefiere los fixtures antes que llamar a los helpers a mano. Crear un administrador manualmente cuando existe un fixture de administrador duplica la preparación y se aleja del resto de la suite.
- Las pruebas se paralelizan a nivel de directorio, así que los archivos de un mismo directorio no deben depender de estado mutable compartido.
- Cuando una prueba haga una llamada real a un modelo, usa el nivel barato y rápido de cada proveedor en lugar de un modelo insignia. El coste de las pruebas escala con la suite, y lo que se está verificando no depende de la potencia del modelo.
- Copia la forma de una prueba ejemplar existente de la misma categoría en lugar de inventar una estructura nueva.
## Leer los logs de cada servicio antes de suponer por qué algo falló
- Cuando escribas pruebas de integración o manejes a mano el sistema en ejecución, lee los archivos de log de depuración de cada servicio en el directorio de logs. Cada servicio vuelca ahí su salida.
- Lee los logs antes de formarte una teoría sobre un fallo. Una causa no relacionada, como un servicio que no está en ejecución, parece idéntica a un defecto real hasta que se lee el log.
## No subir claves de API al repositorio y proteger los endpoints nuevos
- Nunca subas claves de API ni secretos al repositorio.
- Guarda las credenciales de integración mediante el almacenamiento cifrado de credenciales, nunca en configuración en texto plano.
- Sigue los patrones establecidos de control de acceso basado en roles al añadir una funcionalidad. Un endpoint nuevo no hereda ninguna protección por defecto.
- Valida la entrada con modelos de esquema en la frontera.
- Usa consultas parametrizadas para que la inyección sea imposible por construcción, y nunca montes una consulta mediante concatenación de cadenas.
## Etiquetar cada llamada a un modelo de IA con un nombre de operación registrado para el trazado
Toda llamada a un modelo de lenguaje, modelo de embeddings, reranker, generador de imágenes, componente de voz o clasificador de intención debe abrir un span de generación etiquetado con un valor del registro central de flujos. Usa el wrapper que corresponda a la ruta de llamada: uno para las llamadas hechas a través de la abstracción de modelos, y otro para las llamadas directas al proveedor o por HTTP que la eluden.
- Añade un valor nuevo al registro antes de instrumentar una operación nueva. Nunca pases una cadena en crudo como etiqueta.
- Las etiquetas nombran la operación, como el reranking o la edición de imágenes, no el proveedor. El proveedor pertenece a los metadatos de configuración del modelo.
- El mecanismo de reserva automático emite valores centinela sin etiquetar para las llamadas que llegan a la abstracción sin un span explícito. Esos centinelas se ven en los paneles y significan que falta instrumentación. Corrige el punto de llamada en lugar de apoyarte en el mecanismo de reserva.
## Investigar el código y luego escribir un plan sin calendario ni código
Investiga antes de escribir. Explora primero las partes relevantes de la base de código; un plan escrito a partir de suposiciones es peor que no tener plan.
Un plan incluye al menos estos elementos:
- Problemas a abordar: qué se supone que debe lograr el cambio.
- Notas importantes: cosas encontradas durante la investigación que importan para la implementación.
- Estrategia de implementación: el enfoque de alto nivel para llevar a cabo el cambio.
- Pruebas: qué pruebas verificarán el comportamiento correcto, elegidas deliberadamente y mantenidas al mínimo.
No incluyas una sección de cronograma, y no incluyas un tratamiento de los pasos de rollback. No escribas código como parte del plan; mantenlo a alto nivel, aunque referirse a archivos o funciones concretos resulta útil. Esta es una lista mínima, así que añade más cuando ayude.
## Leer las reglas propias de un subproyecto antes de trabajar en él
- La guía de la raíz cubre las cuestiones compartidas. Cada subproyecto mantiene su propio documento de estándares junto a su código, y ese documento rige su propio árbol.
- Lee el documento acotado antes de trabajar en esa área. No des por hecho que las reglas de un cliente se aplican a otro: un cliente móvil sin modelo de objetos del documento, con primitivas de estilo cuyos valores de espaciado son absolutos en lugar de una escala relativa de pasos, y con sus propias primitivas de enrutado y de vistas, sigue reglas distintas a las de un cliente web.
- Las prácticas de ingeniería más amplias viven en la guía de contribución del repositorio. Léela y síguela además de este documento.
