top of page

Simon Willison sufrió fallos de CI con Ruff v0.16.0. Los valores predeterminados habían cambiado

26 jul
15 min de lectura

Simon Willison detectó que varios trabajos de CI fallaban después de que Ruff v0.16.0 ampliara sus reglas de linting predeterminadas de 59 a 413. Su dependencia de desarrollo de Ruff sin una versión fijada había incorporado silenciosamente la nueva versión en proyectos de Python ya existentes.

Astral lanzó la versión el 23 de julio de 2026. Dos días después, Willison explicó cómo la actualización llegó a sus compilaciones sin que hubiera realizado una actualización deliberada. Los fallos convirtieron el lanzamiento de un linter en una advertencia práctica sobre la gestión de dependencias.

El conflicto central no es un linting más estricto frente a un código menos sólido. Es la mejora de la seguridad sin configuración de Ruff frente a la estabilidad que los desarrolladores esperan de repositorios sin cambios. Esta tensión importa allí donde la CI instala las herramientas de desarrollo más recientes disponibles en cada ejecución.

Ruff v0.16.0 cambió lo que significa «predeterminado»

El cambio más relevante de Ruff v0.16.0 no es un comando nuevo. Es una definición mucho más amplia de lo que los proyectos sin configuración deberían considerar un error.

Ruff es un linter y formateador de Python escrito en Rust. Un linter analiza el código fuente en busca de errores, patrones sospechosos y determinados problemas de estilo sin ejecutar el programa.

Antes de este lanzamiento, Ruff activaba 59 reglas cuando un proyecto no proporcionaba una selección de lint explícita. La versión 0.16.0 activa 413 reglas bajo la misma condición, según la guía de migración de Astral.

Eso supone un aumento de 354 comprobaciones activas. También significa que una instalación predeterminada de Ruff ahora evalúa casi siete veces más reglas que antes.

El catálogo circundante también ha crecido. Ruff admitía 708 reglas cuando sus valores predeterminados cambiaron por última vez en la versión 0.1.0. Astral afirma que la colección actual contiene 968 reglas.

Los valores predeterminados anteriores seleccionaban principalmente partes de Pyflakes y pycodestyle. Los proyectos podían adoptar Ruff sin tener que tomar de inmediato decisiones detalladas sobre sus numerosas familias de reglas integradas.

Esa línea de base conservadora ayudó a Ruff a encajar en repositorios existentes. También creó una brecha cada vez mayor entre lo que la herramienta sabía y lo que informaba automáticamente.

Astral cerró gran parte de esa brecha en la versión 0.16.0. Los nuevos valores predeterminados recurren a familias adicionales, entre ellas flake8-bugbear, pyupgrade y la propia categoría RUF de Ruff.

Flake8-bugbear se centra en errores probables y patrones de diseño cuestionables. Pyupgrade identifica patrones de sintaxis y de la biblioteca estándar que pueden modernizarse para la versión de Python compatible con un proyecto.

La lista de reglas predeterminadas ahora incluye comprobaciones que pueden revelar problemas de sintaxis y errores de ejecución inmediatos. No son meras preferencias sobre espaciado o nomenclatura.

Esa distinción explica por qué los fallos de Ruff en CI pueden merecer atención. Algunos nuevos informes revelan defectos que antes pasaban solo porque Ruff no activaba automáticamente el detector correspondiente.

Otros informes se referirán a mantenibilidad, modernización o patrones que un equipo acepta de forma intencionada. Un conjunto predeterminado más amplio no puede conocer los requisitos de compatibilidad ni las convenciones de diseño de cada repositorio.

Por tanto, Ruff v0.16.0 cambia dos cosas a la vez. Aumenta la detección automática de defectos y traslada más decisiones de política a la primera actualización posterior al 23 de julio.

La versión también hace que Ruff formatee por defecto bloques de código Python dentro de archivos Markdown. Los bloques delimitados compatibles incluyen python, py, python3, py3, pyi y pycon.

Este comportamiento importa para repositorios que contienen documentación, tutoriales o cuadernos de Quarto. Una comprobación de formato ahora puede identificar cambios fuera de los archivos .py convencionales.

Las notas de la versión de Ruff también describen nuevos comentarios de supresión y una salida de diagnósticos más rica. Estas mejoras ayudan a los desarrolladores a manejar los hallazgos adicionales una vez que aparecen.

El cambio de alcance sigue siendo el principal evento de migración. Un comando que se comportaba de forma predecible la semana pasada puede devolver hoy un código de salida distinto de cero frente a un código fuente idéntico.

Por qué importan los fallos de CI de Simon Willison

La experiencia de Simon Willison muestra cómo una actualización de una herramienta de desarrollo puede cambiar la política efectiva de un repositorio sin cambiar el repositorio en sí.

Willison es un desarrollador y escritor independiente conocido por proyectos relacionados con Python, herramientas de datos e IA generativa. También fue coautor del framework web Django al inicio de su carrera.

El 25 de julio, Willison escribió que sus «diversos trabajos de CI» habían empezado a fallar. Rastreó los fallos hasta los nuevos valores predeterminados de Ruff y una dependencia de desarrollo "ruff" sin una versión fijada.

Su relato sobre Ruff aporta una perspectiva concreta de usuario sobre el lanzamiento. El código del repositorio no necesariamente había empeorado, pero su entorno de validación había cambiado por debajo.

Un trabajo de CI, o integración continua, ejecuta comprobaciones automatizadas cada vez que los desarrolladores proponen o integran cambios. Los equipos dependen de resultados consistentes para decidir si el código es seguro de aceptar.

Si un trabajo instala ruff sin una restricción de versión, el resolvedor de paquetes puede seleccionar la versión disponible más reciente. La siguiente compilación puede entonces imponer un comportamiento que ningún mantenedor revisó explícitamente.

Este modo de fallo es fácil de minimizar porque Ruff suele ser una dependencia de desarrollo. Normalmente no se distribuye dentro de la aplicación que atiende a los usuarios.

Sin embargo, las dependencias de desarrollo controlan si el software puede avanzar por su canal de entrega. Un nuevo código de salida del linter puede bloquear una solicitud de incorporación de cambios, detener un lanzamiento o consumir horas de investigación.

El incidente también deja al descubierto una distinción engañosa entre dependencias de ejecución y herramientas. Los paquetes de ejecución afectan a lo que hace el software desplegado, mientras que las herramientas afectan a si los desarrolladores pueden desplegarlo.

Ambos pueden introducir cambios operativos. Simplemente actúan en distintos puntos del sistema.

La experiencia de Willison es especialmente útil porque las reglas ampliadas de Ruff funcionaban según lo previsto. Los fallos no requirieron un paquete corrupto, un registro comprometido ni un instalador defectuoso.

La herramienta se instaló correctamente. Inspeccionó el proyecto correctamente según su nueva política. La CI falló porque esa política difería de la que el repositorio había asumido implícitamente.

Esto lo convierte en un problema de reproducibilidad. Una compilación o comprobación reproducible debería producir resultados equivalentes a partir del mismo código fuente y las mismas entradas declaradas.

«La versión más reciente de Ruff» no es una entrada estable. Es una solicitud cambiante cuyo significado depende de cuándo un gestor de paquetes la resuelve.

Los archivos de bloqueo y las restricciones exactas pueden hacer explícita esa entrada. Los servicios de actualización pueden proponer después actualizaciones controladas, lo que permite a los mantenedores inspeccionar nuevos diagnósticos antes de integrar el cambio de versión.

La lección se extiende más allá de Ruff. Los formateadores, verificadores de tipos, ejecutores de pruebas, generadores de documentación y escáneres de seguridad pueden revisar sus valores predeterminados entre versiones.

Un repositorio con bibliotecas de aplicación fijadas pero herramientas de desarrollo variables sigue siendo solo parcialmente reproducible. Su comportamiento en producción puede mantenerse fijo mientras su camino hacia producción cambia.

Esta preocupación es especialmente relevante para los sistemas de programación automatizada. Los agentes suelen ejecutar comprobaciones del repositorio, interpretar su salida y modificar código hasta que se superan todos los controles.

Si las herramientas detrás de esos controles cambian inesperadamente, el agente se enfrenta a un objetivo móvil. Puede generar ediciones innecesarias o suprimir hallazgos sin comprender por qué aparecieron.

Los equipos que construyen una base de conocimientos de ingeniería consultable pueden conservar las decisiones de actualización junto con la configuración y el historial de CI. Ese contexto ayuda a futuros mantenedores a distinguir una política intencionada de una desviación accidental.

Simon Willison expuso la nueva disyuntiva de Ruff

Los valores predeterminados más amplios de Ruff mejoran la cobertura en la primera ejecución, pero trasladan trabajo de migración a los proyectos que trataban una configuración omitida como un contrato estable.

La postura de Astral es directa. Ruff acumuló cientos de comprobaciones mientras su selección predeterminada permanecía congelada, dejando inactivos diagnósticos importantes para usuarios sin configuración.

La selección anterior se remontaba a Ruff v0.1.0. Desde entonces, el catálogo de reglas aumentó en 260, de 708 a 968.

Mantener activadas solo 59 comprobaciones significaba que la experiencia de Ruff sin configuración representaba una porción cada vez menor de sus capacidades. Los nuevos usuarios podían asumir que el valor predeterminado era más completo de lo que realmente era.

La versión corrige ese desajuste. Los desarrolladores ahora pueden descubrir errores de sintaxis, riesgos de ejecución, oportunidades de modernización y construcciones sospechosas sin estudiar primero cientos de códigos de regla.

Eso es valioso para proyectos pequeños. También beneficia a repositorios nuevos que buscan una cobertura sensata antes de que los mantenedores desarrollen una política de linting detallada.

La expectativa opuesta es igualmente razonable. Los valores predeterminados suelen tratarse como comportamiento del producto, especialmente cuando la documentación presenta una herramienta como utilizable sin configuración.

Los desarrolladores que omiten lint.select pueden creer que están eligiendo la línea de base mantenida por Ruff. Antes de la versión 0.16.0, también dependían de que esa línea de base se mantuviera estable entre actualizaciones.

Astral cambió la línea de base porque dejarla intacta tenía su propio coste. Los proyectos podían aprobar Ruff mientras contenían errores que el binario instalado ya sabía detectar.

Por tanto, la disyuntiva no es seguridad frente a comodidad. Es una protección automática más amplia frente a la previsibilidad de las actualizaciones.

Un valor predeterminado más limitado reduce las sorpresas durante las actualizaciones, pero oculta más hallazgos a los nuevos usuarios. Un valor predeterminado más amplio revela más defectos, pero puede alterar canales establecidos.

Ruff v0.16.0 elige una protección más sólida para la próxima ejecución. Los proyectos que quieran el contrato anterior ahora deben registrar explícitamente esa preferencia.

Astral proporciona una configuración de compatibilidad directa:

La tabla exacta puede diferir cuando la configuración reside en un ruff.toml independiente. La decisión importante es la selección explícita de reglas, no el nombre del archivo.

Esta configuración restaura las familias predeterminadas anteriores. Da margen de maniobra a los equipos sin obligarlos a fijar indefinidamente la versión 0.15.

Sin embargo, restaurar el comportamiento anterior debería ser un paso de migración, no un rechazo automático de cada nueva comprobación. Algunos fallos pueden identificar errores que merece la pena corregir de inmediato.

Una actualización cuidadosa comienza capturando la salida completa de diagnósticos. Los mantenedores pueden agrupar después los hallazgos por código de regla, gravedad, seguridad de la corrección e impacto en la compatibilidad.

Las reglas que revelan problemas claros de sintaxis o de ejecución merecen prioridad. Los hallazgos de modernización mecánica pueden revisarse por separado, preferiblemente en commits centrados.

Las comprobaciones orientadas a políticas requieren el criterio del equipo. Un patrón puede ser válido para archivos generados, convenciones de frameworks, módulos de compatibilidad o API públicas que no pueden cambiar sin más.

Ruff admite ignorados por archivo y supresiones específicas para esos casos. La versión 0.16.0 añade los comentarios ruff: ignore y ruff: file-ignore junto con el comportamiento existente de noqa.

La supresión específica suele ser más fácil de auditar que una exclusión amplia. Registra dónde una regla no encaja y puede incluir una razón para futuros mantenedores.

Aun así, las supresiones pueden generar desorden cuando aparecen a la vez cientos de infracciones existentes. Una selección de reglas para todo el proyecto puede ser más honesta hasta que los mantenedores programen una limpieza deliberada.

La respuesta correcta depende de la madurez del repositorio. Un proyecto nuevo puede aceptar de inmediato la línea de base más amplia, mientras que una gran base de código heredada puede necesitar una adopción por etapas.

Por eso los cambios en los valores predeterminados tienen un peso inusual. Aplican un juicio de producto a proyectos con historiales y restricciones radicalmente distintos.

Los nuevos valores predeterminados son solo una parte de la migración

Los equipos que resuelven la primera oleada de diagnósticos todavía deben revisar el formato de Markdown, la salida legible por máquinas y el comportamiento de supresión.

Ruff v0.16.0 incorpora los bloques de código Python en Markdown al alcance habitual de su formateador. Esto puede modificar README, páginas de documentación y archivos de publicación similares a notebooks.

El formateador reconoce las cadenas de información habituales de Python asociadas a bloques de código delimitados. Trata pyi como código de stubs y pycon como una sesión interactiva de Python.

Los usuarios de Quarto también pueden formatear bloques marcados con formas como {python}. Los proyectos que usan archivos .qmd pueden necesitar una asignación de extensión antes de que Ruff los incluya.

Esta función alinea los ejemplos de documentación con el formateador del código fuente. Reduce la posibilidad de que los ejemplos copiados usen un formato obsoleto o incoherente.

También puede provocar fallos inesperados en CI cuando ruff format --check antes examinaba únicamente archivos fuente convencionales. Los responsables de la documentación pueden encontrarse por primera vez con las políticas de Ruff.

Los proyectos pueden excluir Markdown con extend-exclude si es necesario. También pueden usar comentarios de supresión de formato alrededor de regiones seleccionadas.

La decisión debe reflejar si los ejemplos de código son guías ejecutables o material explicativo cuidadosamente dispuesto. El formateo automatizado ayuda de forma más consistente a la primera categoría que a la segunda.

La presentación de diagnósticos también ha cambiado. Ruff ahora muestra diferencias sugeridas dentro de la salida normal de check y format --check.

Antes, los desarrolladores podían solicitar una diferencia por separado. La nueva salida completa mantiene juntos los diagnósticos y los cambios propuestos, lo que facilita interpretar una comprobación fallida.

Para los proveedores de CI, format --check ahora admite formatos de salida utilizados para anotaciones de GitHub y GitLab. Un problema de formato puede aparecer directamente en la línea afectada durante una revisión de código.

Los consumidores automatizados requieren más atención. Varios campos de la salida JSON de Ruff ahora pueden ser null en lugar de contener ubicaciones de marcador de posición.

Los campos afectados incluyen filename, location, end_location y las ubicaciones correspondientes dentro de las ediciones de correcciones. Los consumidores que asumen que cada ubicación es un objeto o una cadena pueden fallar.

Este es un pequeño cambio incompatible para la mayoría de los usuarios. Es más importante para los equipos que analizan la salida de Ruff para integrarla en paneles, bots de revisión o sistemas de calidad personalizados.

Por tanto, una canalización puede fallar en tres capas tras la actualización. Ruff puede encontrar una nueva infracción, el formateo puede ampliarse a un nuevo tipo de archivo o un analizador de salida puede rechazar campos anulables.

Tratar cada fallo como «más reglas de lint» puede hacer que se pase por alto la causa real. Los mantenedores deben identificar qué capa cambió antes de editar el código de la aplicación.

El nuevo formato de supresión también merece una revisión de políticas. Un ruff: ignore[F401] al final de una línea funciona como un noqa dirigido para ese diagnóstico.

Un comentario precedente puede suprimir hallazgos en la siguiente línea lógica. Esto resulta útil para cabeceras de funciones multilínea, donde el problema reportado no encaja claramente junto al token relevante.

La supresión para todo el archivo está disponible mediante ruff: file-ignore. Puede incluir una razón, lo que ofrece a los revisores más información que una exclusión general sin explicación.

La nueva opción --add-ignore puede insertar una supresión automáticamente. Esta comodidad no debe sustituir la revisión de si el diagnóstico subyacente representa un defecto real.

La automatización puede hacer que CI se ponga en verde rápidamente añadiendo comentarios. No puede decidir si un proyecto debe mantener esa excepción durante años.

Ruff separa las correcciones consideradas seguras de las que requieren una opción de correcciones no seguras. Incluso una clasificación como segura debe revisarse en el contexto de código generado, API públicas y comportamientos inusuales en tiempo de ejecución.

El comportamiento dinámico de Python limita lo que el análisis estático puede garantizar. La propia guía sobre correcciones de Ruff pide a los usuarios que informen de casos en los que una corrección segura daña el código.

Esta limitación no debilita el argumento a favor del linting. Refuerza la necesidad de distinguir entre detección, modificación automatizada y aprobación humana.

Las herramientas de desarrollo sin versión fijada son ahora el punto de presión

La presión inmediata recae sobre los repositorios que instalan Ruff dinámicamente y dejan implícita la selección de reglas.

Una versión de Ruff totalmente fijada con una lista select explícita dispone de dos controles estables. Uno fija la implementación de la herramienta, mientras que el otro fija la política de lint elegida por el proyecto.

Una versión flotante con reglas explícitas tiene estabilidad parcial. Las nuevas versiones de Ruff todavía pueden alterar el comportamiento de reglas individuales, el análisis, la salida, el formateo o la semántica de configuración.

Una versión fijada sin reglas explícitas también tiene estabilidad parcial. CI se mantiene coherente hasta que los mantenedores actualizan Ruff, momento en el que la migración de valores predeterminados llega de golpe.

Una versión sin fijar y sin reglas explícitas no tiene ninguno de los dos controles. Esa combinación generó las condiciones tras los fallos de Ruff en CI de Simon Willison.

Fijar versiones no significa congelar la herramienta indefinidamente. Separa el descubrimiento de una actualización de su adopción.

Una solicitud de extracción para actualizar dependencias crea un límite de revisión visible. CI puede mostrar los nuevos hallazgos mientras la rama principal existente sigue siendo reproducible.

Los mantenedores pueden elegir entonces entre varias respuestas:

  • Corregir defectos claros identificados por los nuevos valores predeterminados.

  • Aceptar cambios mecánicos seguros en commits aislados.

  • Configurar excepciones intencionales para patrones específicos del repositorio.

  • Restaurar la selección anterior y programar una adopción gradual de reglas.

  • Actualizar analizadores que no pueden manejar ubicaciones JSON anulables.

  • Excluir archivos de documentación que deben conservar un formato manual.

Estas acciones no deben mezclarse a ciegas. Un único gran commit de correcciones automáticas puede ocultar cambios de comportamiento entre miles de ediciones de formato.

Agrupar el trabajo por familia de reglas produce una revisión más clara. También facilita la reversión si una regla entra en conflicto con las versiones de Python compatibles con el proyecto.

La configuración de la versión objetivo importa cuando las reglas de pyupgrade están activas. La sintaxis moderna puede ser correcta para una base de intérpretes e inutilizable para otra.

Los equipos deben verificar que el objetivo de Python configurado en Ruff coincide con la realidad del despliegue. De lo contrario, los consejos de modernización pueden adelantarse al soporte de producción.

El código generado también necesita un tratamiento independiente. Reformatear o aplicar lint a archivos generados suele crear cambios que desaparecen la próxima vez que se ejecuta el generador.

Excluir rutas generadas puede ser más preciso que llenarlas de comentarios de supresión. La fuente o plantilla del generador suele ser el lugar adecuado para exigir calidad.

Los monorepositorios afrontan otra complicación. Distintos paquetes pueden admitir diferentes versiones de Python o mantener políticas de lint diferenciadas.

Un valor predeterminado a nivel raíz puede simplificar las operaciones, pero también puede imponer un calendario de migración a componentes no relacionados. La configuración por paquete puede reflejar mejor la propiedad.

La principal pregunta escéptica es si 413 reglas forman una línea de base ampliamente aceptable. Astral ha documentado la selección, pero la adopción en el mundo real pondrá a prueba su tasa de falsos positivos y la carga de compatibilidad.

Los fallos de Willison proporcionan una señal temprana, no una encuesta representativa. Muestran que la interrupción es posible, no que la mayoría de los usuarios de Ruff la experimentará.

Los proyectos que ya usan un select o extend-select explícito pueden responder de otra manera. Su conjunto efectivo de reglas depende de cómo interactúe esa configuración con la nueva línea de base.

Astral afirma que el cambio todavía puede revelar reglas útiles para usuarios configurados. Cada equipo debe inspeccionar la selección resultante en lugar de asumir que la configuración vuelve irrelevante la versión.

También existe el riesgo de sobrerreaccionar. Fijar Ruff mientras todas las demás herramientas de desarrollo siguen flotando resuelve solo una instancia visible de un problema más amplio.

Los equipos deben inventariar formateadores, comprobadores de tipos, herramientas de pruebas, hooks de pre-commit y generadores de documentación. Cualquiera de ellos puede convertir una compilación limpia en un fallo.

La política duradera es sencilla: versionar el entorno que decide si el código puede enviarse. Esa política incluye las herramientas que los desarrolladores tradicionalmente clasifican como opcionales.

Qué deberían vigilar Simon Willison y los usuarios de Ruff a continuación

Las próximas tres señales mostrarán si la línea de base más amplia de Ruff se convierte en una política aceptada o en una fuente recurrente de fricción en CI.

La primera señal es la actividad de versiones de parche de Astral durante las semanas posteriores a la versión 0.16.0. Ajustes rápidos a las reglas predeterminadas indicarían que repositorios reales descubrieron problemas importantes de compatibilidad.

Las correcciones específicas de reglas no invalidarían la línea de base ampliada. Mostrarían que una selección mucho mayor necesita ajustes bajo cargas de trabajo de producción.

Por el contrario, una actividad limitada de reversión reforzaría el argumento de Astral de que la mayoría de los nuevos diagnósticos permiten actuar. También animaría a más proyectos a aceptar los valores predeterminados en lugar de restaurar el conjunto anterior.

La segunda señal es el comportamiento de la configuración en repositorios públicos de Python. Los mantenedores revelarán su criterio a través de los commits, incluso sin encuestas formales.

Una oleada de selecciones explícitas de los valores predeterminados antiguos sugeriría que los equipos valoran el control de la migración por encima de la cobertura inmediata. Correcciones generalizadas y valores predeterminados mantenidos apuntarían a una adopción exitosa.

Los repositorios más informativos documentarán su razonamiento. Una lista básica de ignorados muestra qué cambió, mientras que una nota de migración explica por qué un equipo aceptó o rechazó cada familia de reglas.

La tercera señal es si las plantillas de paquetes y los ejemplos de CI comienzan a fijar Ruff. Los generadores de nuevos proyectos suelen moldear hábitos con más eficacia que las advertencias retrospectivas.

Si las plantillas adoptan restricciones de versión y flujos de actualización automatizados, la experiencia de Willison habrá influido en las prácticas más allá de esta única versión.

Si los ejemplos siguen instalando un ruff sin restricciones, futuros cambios de valores predeterminados o del formateador pueden repetir la misma sorpresa. El número específico de reglas será distinto, pero el problema de reproducibilidad seguirá existiendo.

Los desarrolladores no necesitan esperar a esas señales antes de actuar. Pueden ejecutar la nueva versión contra una rama, conservar la salida y decidir qué hallazgos mejoran su código.

Un comando de prueba útil es:

Especificar la versión hace que el experimento sea repetible. Ejecutar ruff format --check . por separado ayuda a distinguir los fallos de lint de los cambios de Markdown o de formato del código fuente.

No empiece añadiendo ignorados globales. Primero identifique qué reglas encontraron errores definitivos, cuáles sugieren modernización y cuáles codifican una política discutible.

Después, registre la decisión en la configuración y el control de versiones. Una insignia de CI en verde vale menos cuando nadie sabe qué contrato de calidad la produjo.

Ruff v0.16.0 demuestra la ventaja y el coste de los valores predeterminados activos. La herramienta detecta más sin configuración, pero la ausencia de configuración ya no significa un comportamiento inalterado.

La experiencia de Simon Willison convierte esa disyuntiva abstracta en una cuestión de ingeniería inmediata: ¿declara su repositorio las herramientas y políticas que controlan sus versiones?

Ejecute la versión fijada en una rama, inspeccione cada nueva familia de reglas y haga explícita la línea de base. La próxima compilación limpia debe reflejar una decisión revisada, no la fecha en que CI instaló Ruff por casualidad.

 
 

Empieza gratis

Un asistente de IA local-first con gestión del conocimiento personal

Para ofrecer una mejor experiencia con la IA,

actualmente remio solo es compatible con Windows 10+ (x64) y M-Chip Macs.

Tu aliado de IA para el trabajo
Haz más con remio

Planifica. Crea. Entrega.
Todo en un solo lugar.

bottom of page