[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"technologies":3,"\u002Fblog\u002Fimpossible-states-dart-sealed-classes":167,"team-members":2206,"blog-topics":2548,"mdc-h2j3xb-key":2589,"glossary":2707},[4,10,15,21,26,32,38,43,48,53,58,63,68,73,77,82,87,92,97,103,108,113,118,123,128,133,138,143,147,152,157,162],{"title":5,"description":6,"slug":7,"category":8,"icon":9},"Android","Todas las apps que entregamos llegan a Android: APIs de plataforma, ejecución en segundo plano, permisos y los requisitos de tienda que traen consigo.","android","framework","logos:android-icon",{"title":11,"description":12,"slug":13,"category":8,"icon":14},"Angular","El framework de frontend en el que trabajamos cuando un producto ya es Angular: una estructura con todo incluido que encaja en apps grandes y de vida larga.","angular","logos:angular-icon",{"title":16,"description":17,"slug":18,"category":19,"icon":20},"Dart","El lenguaje en el que está escrita cada app Flutter que entregamos: null safety robusto, pattern matching de verdad y un compilador que apunta a nativo y a la web.","dart","language","logos:dart",{"title":22,"description":23,"slug":24,"category":8,"icon":25},"Django","El framework de Python que usamos cuando un producto necesita administración, autenticación y un modelo de datos real desde el primer día.","django","logos:django-icon",{"title":27,"description":28,"slug":29,"category":30,"icon":31},"Docker","Cada servicio que construimos se publica como contenedor, así que lo que corre en un portátil, en CI y en producción es un solo artefacto y no tres.","docker","infrastructure","logos:docker-icon",{"title":33,"description":34,"slug":35,"category":36,"icon":37},"Fastlane","La automatización de releases móviles: firma, builds y subidas a las tiendas desde CI, no desde el portátil de un ingeniero el día del lanzamiento.","fastlane","tools","logos:fastlane",{"title":39,"description":40,"slug":41,"category":8,"icon":42},"Flutter","Nuestro stack móvil principal desde 2018: una base de código Dart que llega a iOS, Android, web y escritorio sin un equipo por plataforma.","flutter","logos:flutter",{"title":44,"description":45,"slug":46,"category":36,"icon":47},"Git","Todos los proyectos que tocamos viven en Git: pull requests revisadas, CI en cada rama y un historial que sigue teniendo sentido meses después.","git","logos:git-icon",{"title":49,"description":50,"slug":51,"category":19,"icon":52},"Go","Nuestro lenguaje de backend para APIs en tiempo real y servicios bajo carga: binarios pequeños, compilaciones rápidas y concurrencia que sigue siendo legible.","go","logos:gopher",{"title":54,"description":55,"slug":56,"category":30,"icon":57},"Google Cloud","La nube donde corren nuestras cargas de producción: Kubernetes gestionado, almacenamiento y red, sin servidores hechos a mano que nadie quiere mantener.","gcp","logos:google-cloud",{"title":59,"description":60,"slug":61,"category":36,"icon":62},"Gradle","El sistema de build por el que pasa cada release de Android: product flavours, configuraciones de firma y el cableado de dependencias bajo una app Flutter.","gradle","logos:gradle",{"title":64,"description":65,"slug":66,"category":30,"icon":67},"Helm","Cómo empaquetamos un despliegue de Kubernetes: servicio, configuración, secretos e ingress como una unidad versionada que se puede promover y revertir.","helm","logos:helm",{"title":69,"description":70,"slug":71,"category":19,"icon":72},"Kotlin","A lo que recurrimos cuando una app Flutter necesita Android de verdad por debajo: platform channels, trabajo en segundo plano e integraciones con SDK nativos.","kotlin","logos:kotlin-icon",{"title":74,"description":75,"slug":76,"category":8,"icon":72},"Kotlin Multiplatform","Compartir la lógica de negocio entre iOS y Android mientras cada plataforma conserva su UI nativa: la alternativa cuando Flutter no encaja.","kmp",{"title":78,"description":79,"slug":80,"category":30,"icon":81},"Kubernetes","Cómo ejecutamos servicios en producción: despliegues con Helm, rollouts reversibles y escalado que no necesita a nadie a las 3 de la mañana.","kubernetes","logos:kubernetes",{"title":83,"description":84,"slug":85,"category":30,"icon":86},"NATS","Mensajería ligera entre servicios: publicación\u002Fsuscripción y petición\u002Frespuesta sin el peso operativo de un clúster de broker completo.","nats","logos:nats-icon",{"title":88,"description":89,"slug":90,"category":8,"icon":91},"Nuxt","Vue con renderizado en servidor, enrutado y SEO resueltos: así hacemos sitios y apps web que deben ser rápidas e indexables en la primera carga.","nuxt","logos:nuxt-icon",{"title":93,"description":94,"slug":95,"category":19,"icon":96},"PHP","Donde trabajamos con un backend PHP ya existente: extenderlo, integrarse con él y construir los clientes móviles y web a los que tiene que servir.","php","logos:php",{"title":98,"description":99,"slug":100,"category":101,"icon":102},"PostgreSQL","Nuestra base de datos por defecto, salvo que un producto dé una razón concreta para no usarla: del diseño del esquema al ajuste de índices.","postgres","database","logos:postgresql",{"title":104,"description":105,"slug":106,"category":19,"icon":107},"Python","Nuestro lenguaje para backends, trabajo con datos e integraciones de IA, incluidos los bindings de Python que publicamos para nuestras propias herramientas en Rust.","python","logos:python",{"title":109,"description":110,"slug":111,"category":8,"icon":112},"React","La librería de frontend en la que trabajamos cuando un producto ya es React: componentes, hooks y el ecosistema que ha crecido a su alrededor.","react","logos:react",{"title":114,"description":115,"slug":116,"category":101,"icon":117},"Redis","Donde ponemos los datos que tienen que ser rápidos y se pueden reconstruir: cachés, sesiones, límites de tasa y las colas tras las rutas lentas de un producto.","redis","logos:redis",{"title":119,"description":120,"slug":121,"category":19,"icon":122},"Ruby","El lenguaje de nuestra automatización de releases móviles: lanes de Fastlane, acciones a medida y el pegamento de CI que sube builds a las tiendas.","ruby","logos:ruby",{"title":124,"description":125,"slug":126,"category":19,"icon":127},"Rust","A donde vamos cuando importan tanto el rendimiento como la corrección: renderizado de documentos, herramientas de CLI y servicios que tienen que ser rápidos y predecibles.","rust","simple-icons:rust",{"title":129,"description":130,"slug":131,"category":101,"icon":132},"SQLite","La base de datos que viaja dentro de la app: cachés locales, almacenamiento offline-first y todo lo que tiene que seguir funcionando sin red.","sqlite","logos:sqlite",{"title":134,"description":135,"slug":136,"category":8,"icon":137},"Strapi","Un CMS headless para cuando los editores necesitan ser dueños del contenido: una administración de verdad y una API limpia, sin construirlas desde cero.","strapi","logos:strapi-icon",{"title":139,"description":140,"slug":141,"category":19,"icon":142},"Swift","A lo que recurrimos cuando una app Flutter necesita iOS de verdad por debajo: platform channels, integraciones con SDK nativos, widgets y App Clips.","swift","logos:swift",{"title":144,"description":145,"slug":146,"category":8,"icon":142},"SwiftUI","El framework de UI de Apple, donde construimos las superficies nativas que una app Flutter no puede tener: widgets, App Clips y extensiones.","swiftui",{"title":148,"description":149,"slug":150,"category":8,"icon":151},"Tailwind CSS","Cómo damos estilo a cada frontend que construimos: clases de utilidad y design tokens en lugar de una hoja de estilos que solo crece y nunca se borra.","tailwind","logos:tailwindcss-icon",{"title":153,"description":154,"slug":155,"category":19,"icon":156},"TypeScript","Lo predeterminado para todo lo que escribimos para el navegador y para servicios Node: tipos que detectan errores de integración antes de llegar a una revisión.","typescript","logos:typescript-icon",{"title":158,"description":159,"slug":160,"category":8,"icon":161},"Vue","Nuestro framework de frontend para paneles de administración, cuadros de mando y apps web que deben seguir siendo mantenibles durante años.","vue","logos:vue",{"title":163,"description":164,"slug":165,"category":36,"icon":166},"Whisper","El modelo de reconocimiento de voz de código abierto de OpenAI: el motor de transcripción detrás de la entrada por voz, que funciona como servicio de backend y también en el propio dispositivo.","whisper","simple-icons:openai",{"id":168,"title":169,"author":170,"body":171,"description":177,"extension":2189,"hero":2190,"meta":2193,"metaDescription":2194,"metaTitle":2195,"navigation":816,"ogDescription":2196,"path":2197,"seo":2198,"slug":2199,"stem":2200,"summary":2201,"timestamp":2202,"topic":2203,"updated":2204,"__hash__":2205},"blog_es\u002Fblog\u002Fimpossible-states-dart-sealed-classes.md","Cinco estados que no pueden existir, y tu código permite los cinco","nixan",{"type":172,"value":173,"toc":2168},"minimark",[174,178,181,270,273,281,432,438,443,510,514,517,522,534,537,542,545,552,574,584,587,645,651,657,664,671,675,681,687,698,705,711,715,726,729,745,771,777,781,787,873,884,903,917,932,939,943,946,952,957,1143,1166,1173,1187,1192,1308,1325,1329,1336,1353,1357,1360,1365,1474,1482,1491,1542,1556,1561,1616,1622,1649,1656,1659,1693,1706,1709,1713,1716,1750,1753,1761,1768,1778,1781,1784,1791,1795,1851,1854,1857,1865,1868,1872,1875,1888,1918,1921,2007,2014,2021,2038,2044,2062,2081,2088,2092,2099,2105,2109,2112,2115,2118,2121,2125,2129,2145,2148,2164],[175,176,177],"p",{},"Aquí tienes una tarea de la que habrás escrito veinte versiones: una pantalla con el listado de productos. Cargar desde el servidor, gestionar el error, pintar la lista.",[175,179,180],{},"Se la dimos a un agente —Claude Opus 5— y esto es lo que devolvió.",[182,183,187],"pre",{"className":184,"code":185,"language":18,"meta":186,"style":186},"language-dart shiki shiki-themes material-theme-lighter github-light github-dark","class ProductsState {\n  final List\u003CProduct>? items;\n  final bool isLoading;\n  final Exception? error;\n}\n","",[188,189,190,207,236,249,264],"code",{"__ignoreMap":186},[191,192,195,199,203],"span",{"class":193,"line":194},"line",1,[191,196,198],{"class":197},"smGrS","class",[191,200,202],{"class":201},"sZMiF"," ProductsState",[191,204,206],{"class":205},"su5hD"," {\n",[191,208,210,214,217,220,223,226,229,232],{"class":193,"line":209},2,[191,211,213],{"class":212},"sbsja","  final",[191,215,216],{"class":201}," List",[191,218,219],{"class":205},"\u003C",[191,221,222],{"class":201},"Product",[191,224,225],{"class":205},">",[191,227,228],{"class":197},"?",[191,230,231],{"class":205}," items",[191,233,235],{"class":234},"sP7_E",";\n",[191,237,239,241,244,247],{"class":193,"line":238},3,[191,240,213],{"class":212},[191,242,243],{"class":201}," bool",[191,245,246],{"class":205}," isLoading",[191,248,235],{"class":234},[191,250,252,254,257,259,262],{"class":193,"line":251},4,[191,253,213],{"class":212},[191,255,256],{"class":201}," Exception",[191,258,228],{"class":197},[191,260,261],{"class":205}," error",[191,263,235],{"class":234},[191,265,267],{"class":193,"line":266},5,[191,268,269],{"class":205},"}\n",[175,271,272],{},"Parece algo que habrías escrito tú. Y esa es justo la cuestión: estos modelos se entrenaron con lo que todos llevamos años escribiendo a mano.",[175,274,275,276,280],{},"Ahora echa la cuenta. Tres campos: un bool, una lista nullable y un error nullable. Cada uno con dos estados que importan. Dos por dos por dos: ",[277,278,279],"strong",{},"ocho combinaciones."," ¿Cuántas significan algo?",[282,283,284,309],"table",{},[285,286,287],"thead",{},[288,289,290,296,301,306],"tr",{},[291,292,293],"th",{},[188,294,295],{},"isLoading",[291,297,298],{},[188,299,300],{},"items",[291,302,303],{},[188,304,305],{},"error",[291,307,308],{},"Qué significa",[310,311,312,332,349,364,378,392,404,416],"tbody",{},[288,313,314,320,325,329],{},[315,316,317],"td",{},[188,318,319],{},"true",[315,321,322],{},[188,323,324],{},"null",[315,326,327],{},[188,328,324],{},[315,330,331],{},"Cargando",[288,333,334,339,343,346],{},[315,335,336],{},[188,337,338],{},"false",[315,340,341],{},[188,342,324],{},[315,344,345],{},"con valor",[315,347,348],{},"Ha fallado",[288,350,351,355,357,361],{},[315,352,353],{},[188,354,338],{},[315,356,345],{},[315,358,359],{},[188,360,324],{},[315,362,363],{},"Cargado",[288,365,366,370,372,376],{},[315,367,368],{},[188,369,319],{},[315,371,345],{},[315,373,374],{},[188,375,324],{},[315,377,228],{},[288,379,380,384,388,390],{},[315,381,382],{},[188,383,319],{},[315,385,386],{},[188,387,324],{},[315,389,345],{},[315,391,228],{},[288,393,394,398,400,402],{},[315,395,396],{},[188,397,319],{},[315,399,345],{},[315,401,345],{},[315,403,228],{},[288,405,406,410,412,414],{},[315,407,408],{},[188,409,338],{},[315,411,345],{},[315,413,345],{},[315,415,228],{},[288,417,418,422,426,430],{},[315,419,420],{},[188,421,338],{},[315,423,424],{},[188,425,324],{},[315,427,428],{},[188,429,324],{},[315,431,228],{},[175,433,434,435,437],{},"Tres. Las otras cinco no son errores de compilación. Son código válido. Compila, pasa la revisión, llega a producción y se queda ahí. ",[188,436,295],{}," está a true y al lado hay un error: ¿qué pintas? O está todo vacío: no cargamos, no hay lista, no hay error. ¿Todavía no hemos empezado, o fuimos y volvimos sin nada? El código no lo sabe. Lo adivina, o lo adivina la siguiente persona que abra el fichero.",[439,440,442],"h2",{"id":441},"lo-esencial","Lo esencial",[444,445,446,453,459,469,478,488,494,504],"ul",{},[447,448,449,452],"li",{},[277,450,451],{},"Un estado imposible es aquel que tu tipo permite y tu dominio prohíbe."," Es lo mismo que un invariante roto, visto desde el otro lado.",[447,454,455,458],{},[277,456,457],{},"El coste no son los bugs en tiempo de ejecución."," Es una convención que nadie escribió, ramas que nadie llegó a considerar y un impuesto que pagas en cada lectura, no una vez por incidente.",[447,460,461,464,465,468],{},[277,462,463],{},"El invariante siempre existe."," La única pregunta es si lo sostiene una persona —un comentario, una página del wiki, un ",[188,466,467],{},"assert"," que no se ejecuta en release— o si no hay nada que sostener porque romperlo no compila.",[447,470,471,477],{},[277,472,473,476],{},[188,474,475],{},"enum Status"," más campos nullable lo empeora",", no lo mejora: doce combinaciones en vez de ocho, y siguen siendo tres las que significan algo.",[447,479,480,483,484,487],{},[277,481,482],{},"Una sealed class con campos no nullable deja exactamente los estados con sentido"," y hace que el resto no se pueda construir. Ni booleanos, ni nullables, ni ",[188,485,486],{},"!",".",[447,489,490,493],{},[277,491,492],{},"La exhaustividad es la recompensa."," Añades un cuarto estado y el compilador enumera todos los sitios que no lo tratan, que es retroalimentación sobre la que un agente puede actuar, no un muro contra el que choca.",[447,495,496,503],{},[277,497,498,499,502],{},"Una sola rama ",[188,500,501],{},"_ =>"," desactiva todo eso, en silencio."," Es la única regla de revisión que merece la pena llevarse de este artículo.",[447,505,506,509],{},[277,507,508],{},"Los tipos algebraicos no lo arreglan todo."," Eliminan las combinaciones imposibles de estados, no los valores imposibles, y no hacen nada en la frontera del sistema.",[439,511,513],{"id":512},"estados-imposibles-y-su-nombre-más-antiguo","Estados imposibles y su nombre más antiguo",[175,515,516],{},"Primero el vocabulario, porque hay dos palabras para una misma cosa.",[175,518,519,521],{},[277,520,451],{}," Una pantalla no puede estar cargando y mostrando un error a la vez. El tipo lo permite. Eso es un agujero.",[175,523,524,525,528,529,533],{},"El nombre más antiguo y más preciso es ",[277,526,527],{},"invariante",": una regla que tiene que cumplirse siempre. ",[530,531,532],"em",{},"Si estamos cargando, todavía no hay datos. Si hay un error, tiene un mensaje."," Un estado imposible es simplemente un invariante roto. El mismo hecho desde dos lados: el invariante es cómo debería ser, y el estado imposible es lo que te queda cuando no lo fue.",[175,535,536],{},"Quédate con esa palabra. Todo gira a su alrededor.",[538,539,541],"h3",{"id":540},"por-qué-esto-es-un-problema-y-por-qué-la-explicación-habitual-falla","Por qué esto es un problema, y por qué la explicación habitual falla",[175,543,544],{},"Lo que se suele decir es que los estados imposibles son bugs que no revientan: pintan la pantalla equivocada sin hacer ruido. Suena convincente, y si el problema fuera ese, la respuesta sería barata: tests, QA, una alerta sobre la pantalla vacía. Lo pillas, lo arreglas, sigues.",[175,546,547,548,551],{},"El problema no es ese. No va de tiempo de ejecución en absoluto. Son tres costes, y los tres están en tu código ",[277,549,550],{},"ahora mismo",", aunque la aplicación se comporte perfectamente.",[175,553,554,557,558,560,561,563,564,566,567,569,570,573],{},[277,555,556],{},"Uno: la convención que nadie escribió."," Mira otra vez la clase del agente. ¿Dónde dice que cuando ",[188,559,295],{}," es true no debes leer ",[188,562,300],{},"? En ningún sitio. ¿Dónde dice que ",[188,565,305],{}," e ",[188,568,300],{}," nunca están rellenos a la vez? En ningún sitio. Esas reglas existen y la gente confía en ellas, pero no están en el tipo ni en un comentario. Viven en la cabeza de quien escribió la clase, y todos los demás las reconstruyen a partir del código, leyendo los ",[188,571,572],{},"if"," y deduciendo la intención. Cinco minutos por fichero, para cada persona que lo abra.",[175,575,576,579,580,583],{},[277,577,578],{},"Dos: el número de ramas."," Ocho combinaciones son ocho casos, y con cada uno alguien tiene que hacer algo: tratarlo o descartarlo deliberadamente. Se tratan tres. Las otras cinco ni se tratan ni se descartan: ",[277,581,582],{},"nunca se consideraron."," Esa distinción importa, porque un caso descartado es una decisión y uno no considerado es un agujero.",[175,585,586],{},"Y la cuenta crece multiplicando.",[282,588,589,602],{},[285,590,591],{},[288,592,593,596,599],{},[291,594,595],{},"Campos que viajan juntos",[291,597,598],{},"Combinaciones",[291,600,601],{},"Con sentido",[310,603,604,614,625,635],{},[288,605,606,609,612],{},[315,607,608],{},"3",[315,610,611],{},"8",[315,613,608],{},[288,615,616,619,622],{},[315,617,618],{},"4",[315,620,621],{},"16",[315,623,624],{},"3–4",[288,626,627,630,633],{},[315,628,629],{},"5",[315,631,632],{},"32",[315,634,624],{},[288,636,637,640,643],{},[315,638,639],{},"6",[315,641,642],{},"64",[315,644,624],{},[175,646,647,650],{},[277,648,649],{},"Tres: pagas en cada lectura, no una vez por bug."," Este es el importante. Un bug se arregla una vez. Una convención que no está en el tipo la vuelve a deducir todo el que toque esa pantalla: en la revisión, al añadir una funcionalidad, mientras investiga un incidente. Eso no es un coste puntual, es un impuesto.",[175,652,653,654,656],{},"De ahí sale también el parche habitual. La gente tapa estos agujeros con condiciones: aquí comprobamos que no estamos cargando, allí que el error no es nulo. Cada uno de esos ",[188,655,572],{}," es un parche sobre un agujero que abriste tú al declarar tres campos independientes. Cinco agujeros, normalmente dos parches: los que ya se dispararon.",[175,658,659,660,663],{},"Y de paso, fíjate: nada de esto ",[277,661,662],{},"revienta."," Ni informe de fallo, ni traza de pila, ni nada en tu sistema de errores. Así que lo de que la monitorización lo pillará no sirve aquí. Pero eso es un efecto secundario, no el fondo del asunto.",[175,665,666,667,670],{},"Lo que ha cambiado en los últimos dos años es el ",[277,668,669],{},"volumen."," Antes esta clase aparecía una vez por semana, escrita por una persona que dedicó al menos treinta segundos a pensar en los campos. Ahora aparece en doce segundos y nadie la ha leído: ni el agente ni tú. Y quien revisa es una persona que mira tres campos, no alguien que sostiene ocho combinaciones en la cabeza.",[538,672,674],{"id":673},"quién-sostiene-el-invariante","Quién sostiene el invariante",[175,676,677,678],{},"Así que la pregunta nunca es si tienes un invariante. Siempre lo tienes. La pregunta es ",[277,679,680],{},"quién responde por él.",[175,682,683,684,686],{},"Normalmente una persona. Un comentario encima de la clase. Una página del wiki que nadie abre desde hace dos años. Una convención de revisión que recuerdan dos de cada cinco. En el mejor de los casos, un ",[188,685,467],{}," en el constructor.",[688,689,692],"info-box",{":leading-icon":690,":title":691},"lucide:shield-off","Un assert no es un invariante en Dart",[175,693,694,695,697],{},"Las instrucciones ",[188,696,467],{}," se eliminan de las compilaciones de release. No es que se salten: la condición no llega a evaluarse, y sus argumentos tampoco, por eso una comprobación cara dentro de un assert no cuesta nada en producción. La consecuencia práctica es justo la que se olvida: un invariante que defendiste con un assert no existe delante de tus usuarios. Se cumplía durante el desarrollo, donde ya estabas mirando, y falta exactamente en el único sitio donde no miras. Un assert es una ayuda de depuración, no una restricción.",[175,699,700,701,704],{},"Todo eso es un invariante que alguien ",[277,702,703],{},"sostiene",": a mano, con atención, de memoria. La alternativa es organizar las cosas de forma que no haya nada que sostener, porque romperlo no es expresable en el tipo. No es que comprobemos que esto no puede pasar, sino que esto no compila.",[175,706,707,708],{},"Y por eso dejó de ser una distinción académica. Un invariante sostenido por una persona funciona exactamente mientras el código se escriba a velocidad humana. Un agente no ha leído tu comentario, no ha abierto tu wiki y no ha estado en tu revisión de código. ",[277,709,710],{},"Las convenciones no escalan a la velocidad de generación. Los compiladores sí.",[439,712,714],{"id":713},"tipos-de-datos-algebraicos-de-una-pasada","Tipos de datos algebraicos, de una pasada",[175,716,717,718,721,722,725],{},"Una advertencia antes de la teoría, para desarmar la mitad de las objeciones. El ejemplo es deliberadamente el más obvio posible. Cargando, error, éxito es la ilustración más gastada de los tipos algebraicos que hay en internet: está en todos los tutoriales. La idea tiene un eslogan famoso, ",[530,719,720],{},"make illegal states unrepresentable",", que se suele atribuir a las charlas ",[530,723,724],{},"Effective ML"," de Yaron Minsky, y lleva más de una década por ahí. Aquí no se descubre nada. Lo interesante es de dónde salió esa clase al principio del artículo, y qué haces cuando código así te llega por lotes.",[175,727,728],{},"Ahora la definición. Sin teoría de categorías.",[175,730,731,734,735,738,739,741,742],{},[277,732,733],{},"Un producto es «y»."," Una clase corriente con campos: una cadena ",[277,736,737],{},"y"," un número ",[277,740,737],{}," un booleano. El número de valores posibles es el producto de las cuentas. De ahí salieron los ocho: no es retórica, es aritmética. Cada campo que añades ",[277,743,744],{},"multiplica.",[175,746,747,750,751,754,755,758,759,762,763,766,767,770],{},[277,748,749],{},"Una suma es «o»."," El valor es esto o aquello, y no hay una tercera opción. La suma más sencilla que todo el mundo ya conoce es un ",[188,752,753],{},"enum",". Una sealed class es esa misma suma, solo que cada caso puede llevar sus propios datos. Aquí las cuentas se ",[277,756,757],{},"suman",": ",[530,760,761],{},"cargando"," es un valor, ",[530,764,765],{},"ha fallado"," son tantos como errores haya, ",[530,768,769],{},"cargado"," son tantos como listas haya. Nada se multiplica gratis.",[175,772,773,774],{},"La idea entera en una frase: ",[277,775,776],{},"no puedes construir el tipo de forma que represente un estado que nunca fue válido.",[538,778,780],{"id":779},"y-por-qué-no-un-enum-y-ya-está","\"¿Y por qué no un enum y ya está?\"",[175,782,783,784,786],{},"La objeción obvia es que no hacen falta sealed classes: añade un ",[188,785,475],{}," con tres valores y deja de complicarlo. El instinto es razonable, y no funciona.",[182,788,790],{"className":184,"code":789,"language":18,"meta":186,"style":186},"enum Status { loading, error, success }\n\nclass ProductsState {\n  final Status status;\n  final List\u003CProduct>? items;\n  final Exception? error;\n}\n",[188,791,792,812,818,826,837,855,868],{"__ignoreMap":186},[191,793,794,796,799,802,805,807,809],{"class":193,"line":194},[191,795,753],{"class":197},[191,797,798],{"class":201}," Status",[191,800,801],{"class":205}," { loading",[191,803,804],{"class":234},",",[191,806,261],{"class":205},[191,808,804],{"class":234},[191,810,811],{"class":205}," success }\n",[191,813,814],{"class":193,"line":209},[191,815,817],{"emptyLinePlaceholder":816},true,"\n",[191,819,820,822,824],{"class":193,"line":238},[191,821,198],{"class":197},[191,823,202],{"class":201},[191,825,206],{"class":205},[191,827,828,830,832,835],{"class":193,"line":251},[191,829,213],{"class":212},[191,831,798],{"class":201},[191,833,834],{"class":205}," status",[191,836,235],{"class":234},[191,838,839,841,843,845,847,849,851,853],{"class":193,"line":266},[191,840,213],{"class":212},[191,842,216],{"class":201},[191,844,219],{"class":205},[191,846,222],{"class":201},[191,848,225],{"class":205},[191,850,228],{"class":197},[191,852,231],{"class":205},[191,854,235],{"class":234},[191,856,858,860,862,864,866],{"class":193,"line":857},6,[191,859,213],{"class":212},[191,861,256],{"class":201},[191,863,228],{"class":197},[191,865,261],{"class":205},[191,867,235],{"class":234},[191,869,871],{"class":193,"line":870},7,[191,872,269],{"class":205},[175,874,875,876,879,880,883],{},"Cuenta otra vez: tres valores de estado, por dos estados de lista, por dos estados de error. ",[277,877,878],{},"Doce combinaciones."," Eran ocho. Siguen siendo tres las que significan algo. Aritméticamente ha ",[277,881,882],{},"empeorado",": nueve combinaciones basura en vez de cinco.",[175,885,886,887,890,891,566,894,890,896,898,899,902],{},"Y el problema original sigue intacto. ",[188,888,889],{},"status"," es ",[188,892,893],{},"success",[188,895,300],{},[188,897,324],{},": compila perfectamente. En la interfaz sigues escribiendo ",[188,900,901],{},"items!",", jurándole al compilador que seguro que los datos están ahí. Una promesa no es una garantía. Es una comprobación desactivada con sintaxis agradable.",[175,904,905,906,908,909,912,913,916],{},"La diferencia está en lo que lleva la suma. Un ",[188,907,753],{}," es una suma ",[277,910,911],{},"sin datos",": te dice en qué estado estás, pero los datos quedan al lado, atados al estado solo por tu disciplina. Una sealed class es una suma ",[277,914,915],{},"con datos",": la lista existe exactamente donde significa algo, y en ningún otro sitio.",[175,918,919,920,922,923,925,926],{},"Así que la regla es: si tus estados no llevan datos, usa un ",[188,921,753],{},", es más corto. En cuanto uno solo de ellos lleva carga útil, el ",[188,924,753],{}," deja de ayudar, y ",[277,927,928,929,931],{},"cada ",[188,930,486],{}," de tu interfaz es el recibo.",[175,933,934,935,938],{},"Es una idea vieja. ML, de donde viene, tiene cincuenta años; Haskell, OCaml y F# la tienen desde hace décadas. En los lenguajes mayoritarios ya está prácticamente en todas partes: Dart la incorporó en la versión 3, en 2023. Lo que cambió no es la idea sino el argumento a su favor. Los tipos algebraicos se vendían por elegancia, y los argumentos estéticos pierden contra los sprints. El argumento ahora es que ",[277,936,937],{},"el código se escribe más rápido de lo que se lee",", el único revisor que sigue el ritmo de la generación es el compilador, y todo lo que no expresaste en el tipo lo expresaste como una esperanza.",[439,940,942],{"id":941},"la-reescritura","La reescritura",[175,944,945],{},"Tres pasos, unos dos minutos de trabajo.",[175,947,948,951],{},[277,949,950],{},"Paso uno: escribe los estados con palabras, antes de escribir una sola línea de código."," La pantalla está cargando. La pantalla ha fallado. La pantalla tiene una lista. Ya está: tres. Si en este paso te salen siete, casi con seguridad estás escribiendo combinaciones de flags en vez de estados. Un estado es algo que puedes nombrar con una palabra y señalar en una maqueta.",[175,953,954],{},[277,955,956],{},"Paso dos: cada estado se convierte en su propia clase.",[182,958,960],{"className":184,"code":959,"language":18,"meta":186,"style":186},"sealed class ProductsState {\n  const ProductsState();\n}\n\nfinal class Loading extends ProductsState {\n  const Loading();\n}\n\nfinal class Failed extends ProductsState {\n  const Failed(this.error);\n  final Exception error;\n}\n\nfinal class Loaded extends ProductsState {\n  const Loaded(this.items);\n  final List\u003CProduct> items;\n}\n",[188,961,962,974,986,990,994,1011,1021,1025,1030,1046,1067,1078,1083,1088,1104,1122,1138],{"__ignoreMap":186},[191,963,964,967,970,972],{"class":193,"line":194},[191,965,966],{"class":197},"sealed",[191,968,969],{"class":197}," class",[191,971,202],{"class":201},[191,973,206],{"class":205},[191,975,976,979,981,984],{"class":193,"line":209},[191,977,978],{"class":212},"  const",[191,980,202],{"class":201},[191,982,983],{"class":205},"()",[191,985,235],{"class":234},[191,987,988],{"class":193,"line":238},[191,989,269],{"class":205},[191,991,992],{"class":193,"line":251},[191,993,817],{"emptyLinePlaceholder":816},[191,995,996,999,1001,1004,1007,1009],{"class":193,"line":266},[191,997,998],{"class":212},"final",[191,1000,969],{"class":197},[191,1002,1003],{"class":201}," Loading",[191,1005,1006],{"class":197}," extends",[191,1008,202],{"class":201},[191,1010,206],{"class":205},[191,1012,1013,1015,1017,1019],{"class":193,"line":857},[191,1014,978],{"class":212},[191,1016,1003],{"class":201},[191,1018,983],{"class":205},[191,1020,235],{"class":234},[191,1022,1023],{"class":193,"line":870},[191,1024,269],{"class":205},[191,1026,1028],{"class":193,"line":1027},8,[191,1029,817],{"emptyLinePlaceholder":816},[191,1031,1033,1035,1037,1040,1042,1044],{"class":193,"line":1032},9,[191,1034,998],{"class":212},[191,1036,969],{"class":197},[191,1038,1039],{"class":201}," Failed",[191,1041,1006],{"class":197},[191,1043,202],{"class":201},[191,1045,206],{"class":205},[191,1047,1049,1051,1053,1056,1060,1062,1065],{"class":193,"line":1048},10,[191,1050,978],{"class":212},[191,1052,1039],{"class":201},[191,1054,1055],{"class":205},"(",[191,1057,1059],{"class":1058},"s_hVV","this",[191,1061,487],{"class":234},[191,1063,1064],{"class":205},"error)",[191,1066,235],{"class":234},[191,1068,1070,1072,1074,1076],{"class":193,"line":1069},11,[191,1071,213],{"class":212},[191,1073,256],{"class":201},[191,1075,261],{"class":205},[191,1077,235],{"class":234},[191,1079,1081],{"class":193,"line":1080},12,[191,1082,269],{"class":205},[191,1084,1086],{"class":193,"line":1085},13,[191,1087,817],{"emptyLinePlaceholder":816},[191,1089,1091,1093,1095,1098,1100,1102],{"class":193,"line":1090},14,[191,1092,998],{"class":212},[191,1094,969],{"class":197},[191,1096,1097],{"class":201}," Loaded",[191,1099,1006],{"class":197},[191,1101,202],{"class":201},[191,1103,206],{"class":205},[191,1105,1107,1109,1111,1113,1115,1117,1120],{"class":193,"line":1106},15,[191,1108,978],{"class":212},[191,1110,1097],{"class":201},[191,1112,1055],{"class":205},[191,1114,1059],{"class":1058},[191,1116,487],{"class":234},[191,1118,1119],{"class":205},"items)",[191,1121,235],{"class":234},[191,1123,1125,1127,1129,1131,1133,1136],{"class":193,"line":1124},16,[191,1126,213],{"class":212},[191,1128,216],{"class":201},[191,1130,219],{"class":205},[191,1132,222],{"class":201},[191,1134,1135],{"class":205},"> items",[191,1137,235],{"class":234},[191,1139,1141],{"class":193,"line":1140},17,[191,1142,269],{"class":205},[175,1144,1145,1146,1149,1150,1153,1154,1157,1158,1161,1162,1165],{},"Mira lo que ha pasado. ",[188,1147,1148],{},"Failed"," lleva un error, y ",[277,1151,1152],{},"no es nullable",": el estado «hemos fallado pero no hay error» deja de existir, y físicamente no puedes construirlo. ",[188,1155,1156],{},"Loaded"," lleva una lista, tampoco nullable. ",[188,1159,1160],{},"Loading"," no lleva ",[277,1163,1164],{},"nada",", porque durante la carga no hay datos, y mantener un campo para ellos es precisamente la invitación al estado imposible.",[175,1167,1168,1169,1172],{},"Fíjate en lo que no está. ",[277,1170,1171],{},"Ni un solo booleano. Ni un solo campo nullable."," Ese es todo el refactor; el resto es sintaxis.",[175,1174,1175,1176,1178,1179,1182,1183,1186],{},"Un detalle propio de Dart: ",[188,1177,966],{}," restringe la herencia a la ",[277,1180,1181],{},"misma librería",", que en la práctica es el mismo fichero salvo que la partas a propósito con ",[188,1184,1185],{},"part",". Eso no es pedantería, es el mecanismo: para comprobar la exhaustividad el compilador necesita la lista completa de casos, y solo puede garantizarla dentro de la librería. De ahí la convención de que, haya los casos que haya, vivan juntos en un fichero. La primera vez resulta raro; después, cómodo: todo el espacio de estados de la pantalla se ve de un vistazo.",[175,1188,1189],{},[277,1190,1191],{},"Paso tres: la interfaz.",[182,1193,1195],{"className":184,"code":1194,"language":18,"meta":186,"style":186},"Widget build(BuildContext context) {\n  return switch (state) {\n    Loading() => const AppSpinner(),\n    Failed(:final error) => ErrorView(error),\n    Loaded(:final items) => ItemList(items),\n  };\n}\n",[188,1196,1197,1214,1226,1248,1273,1297,1304],{"__ignoreMap":186},[191,1198,1199,1202,1206,1208,1211],{"class":193,"line":194},[191,1200,1201],{"class":201},"Widget",[191,1203,1205],{"class":1204},"sGLFI"," build",[191,1207,1055],{"class":205},[191,1209,1210],{"class":201},"BuildContext",[191,1212,1213],{"class":205}," context) {\n",[191,1215,1216,1220,1223],{"class":193,"line":209},[191,1217,1219],{"class":1218},"sVHd0","  return",[191,1221,1222],{"class":1218}," switch",[191,1224,1225],{"class":205}," (state) {\n",[191,1227,1228,1231,1234,1237,1240,1243,1245],{"class":193,"line":238},[191,1229,1230],{"class":201},"    Loading",[191,1232,1233],{"class":205},"() ",[191,1235,1236],{"class":197},"=>",[191,1238,1239],{"class":212}," const",[191,1241,1242],{"class":201}," AppSpinner",[191,1244,983],{"class":205},[191,1246,1247],{"class":234},",\n",[191,1249,1250,1253,1255,1258,1260,1263,1265,1268,1271],{"class":193,"line":251},[191,1251,1252],{"class":201},"    Failed",[191,1254,1055],{"class":205},[191,1256,1257],{"class":197},":",[191,1259,998],{"class":212},[191,1261,1262],{"class":205}," error) ",[191,1264,1236],{"class":197},[191,1266,1267],{"class":201}," ErrorView",[191,1269,1270],{"class":205},"(error)",[191,1272,1247],{"class":234},[191,1274,1275,1278,1280,1282,1284,1287,1289,1292,1295],{"class":193,"line":266},[191,1276,1277],{"class":201},"    Loaded",[191,1279,1055],{"class":205},[191,1281,1257],{"class":197},[191,1283,998],{"class":212},[191,1285,1286],{"class":205}," items) ",[191,1288,1236],{"class":197},[191,1290,1291],{"class":201}," ItemList",[191,1293,1294],{"class":205},"(items)",[191,1296,1247],{"class":234},[191,1298,1299,1302],{"class":193,"line":857},[191,1300,1301],{"class":205},"  }",[191,1303,235],{"class":234},[191,1305,1306],{"class":193,"line":870},[191,1307,269],{"class":205},[175,1309,1310,1311,1313,1314,1317,1318,1320,1321,1324],{},"Ni un ",[188,1312,572],{},". Ni una comprobación de null. Los datos se desestructuran en el patrón —",[188,1315,1316],{},":final items","— y llegan ya tipados. Dentro de la rama ",[188,1319,1156],{}," la lista ",[277,1322,1323],{},"existe",", en vez de «podría existir». Compáralo con lo que tendrías con flags: una cadena de comprobaciones cuyo orden importa y cuya razón ya no recuerda nadie.",[538,1326,1328],{"id":1327},"cómo-hacerlo-en-un-proyecto-con-cuarenta-pantallas","Cómo hacerlo en un proyecto con cuarenta pantallas",[175,1330,1331,1332,1335],{},"No reescribas todo. La regla: ",[277,1333,1334],{},"pantalla nueva, sealed desde el principio; pantalla vieja, cuando ya estés dentro arreglando algo."," Una pantalla que nadie ha tocado en dos años, no la toques. Es muy posible que tenga estados imposibles, pero o ya se dispararon o no se dispararán nunca.",[175,1337,1338,1339,1342,1343,1346,1347,1352],{},"Para las que sí toques, la prioridad se reduce a una cosa: ",[277,1340,1341],{},"desde cuántos sitios se lee ese estado."," Un ",[188,1344,1345],{},"switch"," en un widget: reescribirlo casi no compensa, porque de todos modos lo ves todo. Un estado que se lee desde seis sitios, entre interfaz, analítica, logs y un manejador de push, es donde la exhaustividad se gana el sueldo, porque esos son justo los cinco de seis que se olvidan. Si tu capa de ",[1348,1349,1351],"a",{"href":1350},"\u002Fblog\u002Fai-agents-struggle-with-flutter","gestión de estado"," ya canaliza la pantalla por un único flujo, ese es el sitio más barato para empezar.",[439,1354,1356],{"id":1355},"el-mismo-tipo-en-otros-cuatro-lenguajes","El mismo tipo en otros cuatro lenguajes",[175,1358,1359],{},"No porque haga falta un segundo lenguaje, sino para que quede claro que esto no es un dialecto de Dart ni una moda de la comunidad Flutter. El mismo tipo, tres estados, los datos atados al estado.",[175,1361,1362],{},[277,1363,1364],{},"Kotlin.",[182,1366,1369],{"className":1367,"code":1368,"language":71,"meta":186,"style":186},"language-kotlin shiki shiki-themes material-theme-lighter github-light github-dark","sealed interface ProductsState\n\ndata object Loading : ProductsState\n\ndata class Failed(\n    val error: AppError,\n) : ProductsState\n\ndata class Loaded(\n    val items: List\u003CProduct>,\n) : ProductsState\n",[188,1370,1371,1382,1386,1402,1406,1417,1430,1437,1441,1451,1468],{"__ignoreMap":186},[191,1372,1373,1375,1378],{"class":193,"line":194},[191,1374,966],{"class":212},[191,1376,1377],{"class":197}," interface",[191,1379,1381],{"class":1380},"sbgvK"," ProductsState\n",[191,1383,1384],{"class":193,"line":209},[191,1385,817],{"emptyLinePlaceholder":816},[191,1387,1388,1391,1394,1396,1399],{"class":193,"line":238},[191,1389,1390],{"class":212},"data",[191,1392,1393],{"class":197}," object",[191,1395,1003],{"class":1380},[191,1397,1398],{"class":205}," : ",[191,1400,1401],{"class":1380},"ProductsState\n",[191,1403,1404],{"class":193,"line":251},[191,1405,817],{"emptyLinePlaceholder":816},[191,1407,1408,1410,1412,1414],{"class":193,"line":266},[191,1409,1390],{"class":212},[191,1411,969],{"class":197},[191,1413,1039],{"class":1380},[191,1415,1416],{"class":205},"(\n",[191,1418,1419,1422,1425,1428],{"class":193,"line":857},[191,1420,1421],{"class":197},"    val",[191,1423,1424],{"class":205}," error: ",[191,1426,1427],{"class":1380},"AppError",[191,1429,1247],{"class":205},[191,1431,1432,1435],{"class":193,"line":870},[191,1433,1434],{"class":205},") : ",[191,1436,1401],{"class":1380},[191,1438,1439],{"class":193,"line":1027},[191,1440,817],{"emptyLinePlaceholder":816},[191,1442,1443,1445,1447,1449],{"class":193,"line":1032},[191,1444,1390],{"class":212},[191,1446,969],{"class":197},[191,1448,1097],{"class":1380},[191,1450,1416],{"class":205},[191,1452,1453,1455,1458,1461,1463,1465],{"class":193,"line":1048},[191,1454,1421],{"class":197},[191,1456,1457],{"class":205}," items: ",[191,1459,1460],{"class":1380},"List",[191,1462,219],{"class":205},[191,1464,222],{"class":1380},[191,1466,1467],{"class":205},">,\n",[191,1469,1470,1472],{"class":193,"line":1069},[191,1471,1434],{"class":205},[191,1473,1401],{"class":1380},[175,1475,1476,1479,1480,487],{},[188,1477,1478],{},"when"," usado como expresión está obligado a cubrir todas las ramas: exactamente nuestro ",[188,1481,1345],{},[175,1483,1484,1487,1488,1490],{},[277,1485,1486],{},"Rust."," Más interesante, porque es el ",[188,1489,753],{}," incorporado:",[182,1492,1495],{"className":1493,"code":1494,"language":126,"meta":186,"style":186},"language-rust shiki shiki-themes material-theme-lighter github-light github-dark","enum ProductsState {\n    Loading,\n    Failed(AppError),\n    Loaded(Vec\u003CProduct>),\n}\n",[188,1496,1497,1505,1511,1522,1538],{"__ignoreMap":186},[191,1498,1499,1501,1503],{"class":193,"line":194},[191,1500,753],{"class":212},[191,1502,202],{"class":1380},[191,1504,206],{"class":234},[191,1506,1507,1509],{"class":193,"line":209},[191,1508,1230],{"class":1380},[191,1510,1247],{"class":234},[191,1512,1513,1515,1517,1519],{"class":193,"line":238},[191,1514,1252],{"class":1204},[191,1516,1055],{"class":234},[191,1518,1427],{"class":1380},[191,1520,1521],{"class":234},"),\n",[191,1523,1524,1526,1528,1531,1533,1535],{"class":193,"line":251},[191,1525,1277],{"class":1204},[191,1527,1055],{"class":234},[191,1529,1530],{"class":1380},"Vec",[191,1532,219],{"class":234},[191,1534,222],{"class":1380},[191,1536,1537],{"class":234},">),\n",[191,1539,1540],{"class":193,"line":266},[191,1541,269],{"class":234},[175,1543,1544,1545,1547,1548,1551,1552,1555],{},"En Rust un ",[188,1546,753],{}," ",[277,1549,1550],{},"es"," una suma con datos desde el principio, sin ceremonia añadida. Si te dejas un caso en un ",[188,1553,1554],{},"match",", no compila.",[175,1557,1558,1560],{},[277,1559,139],{},", lo mismo con valores asociados:",[182,1562,1565],{"className":1563,"code":1564,"language":141,"meta":186,"style":186},"language-swift shiki shiki-themes material-theme-lighter github-light github-dark","enum ProductsState {\n    case loading\n    case failed(AppError)\n    case loaded([Product])\n}\n",[188,1566,1567,1575,1583,1597,1612],{"__ignoreMap":186},[191,1568,1569,1571,1573],{"class":193,"line":194},[191,1570,753],{"class":212},[191,1572,202],{"class":1380},[191,1574,206],{"class":234},[191,1576,1577,1580],{"class":193,"line":209},[191,1578,1579],{"class":212},"    case",[191,1581,1582],{"class":1058}," loading\n",[191,1584,1585,1587,1590,1592,1594],{"class":193,"line":238},[191,1586,1579],{"class":212},[191,1588,1589],{"class":1058}," failed",[191,1591,1055],{"class":234},[191,1593,1427],{"class":205},[191,1595,1596],{"class":234},")\n",[191,1598,1599,1601,1604,1607,1609],{"class":193,"line":251},[191,1600,1579],{"class":212},[191,1602,1603],{"class":1058}," loaded",[191,1605,1606],{"class":234},"([",[191,1608,222],{"class":205},[191,1610,1611],{"class":234},"])\n",[191,1613,1614],{"class":193,"line":266},[191,1615,269],{"class":234},[175,1617,1618,1621],{},[277,1619,1620],{},"Y el exótico, Idris",": la sintaxis de ML de la que desciende todo lo anterior.",[182,1623,1627],{"className":1624,"code":1625,"language":1626,"meta":186,"style":186},"language-idris shiki shiki-themes material-theme-lighter github-light github-dark","data ProductsState\n  = Loading\n  | Failed AppError\n  | Loaded (List Product)\n","idris",[188,1628,1629,1634,1639,1644],{"__ignoreMap":186},[191,1630,1631],{"class":193,"line":194},[191,1632,1633],{},"data ProductsState\n",[191,1635,1636],{"class":193,"line":209},[191,1637,1638],{},"  = Loading\n",[191,1640,1641],{"class":193,"line":238},[191,1642,1643],{},"  | Failed AppError\n",[191,1645,1646],{"class":193,"line":251},[191,1647,1648],{},"  | Loaded (List Product)\n",[175,1650,1651,1652,1655],{},"Mira la barra vertical. Se lee como «o». ",[277,1653,1654],{},"El tipo suma está escrito literalmente con un símbolo de «o»",": esto es una suma mucho antes de que nadie lo llamara sealed class.",[175,1657,1658],{},"Idris hace además algo que ninguno de los otros cuatro puede:",[182,1660,1662],{"className":1624,"code":1661,"language":1626,"meta":186,"style":186},"import Data.Vect\n\ndata ProductsState : Type where\n  Loading : ProductsState\n  Failed  : AppError -> ProductsState\n  Loaded  : Vect (S n) Product -> ProductsState\n",[188,1663,1664,1669,1673,1678,1683,1688],{"__ignoreMap":186},[191,1665,1666],{"class":193,"line":194},[191,1667,1668],{},"import Data.Vect\n",[191,1670,1671],{"class":193,"line":209},[191,1672,817],{"emptyLinePlaceholder":816},[191,1674,1675],{"class":193,"line":238},[191,1676,1677],{},"data ProductsState : Type where\n",[191,1679,1680],{"class":193,"line":251},[191,1681,1682],{},"  Loading : ProductsState\n",[191,1684,1685],{"class":193,"line":266},[191,1686,1687],{},"  Failed  : AppError -> ProductsState\n",[191,1689,1690],{"class":193,"line":857},[191,1691,1692],{},"  Loaded  : Vect (S n) Product -> ProductsState\n",[175,1694,1695,1698,1699,1702,1703,1705],{},[188,1696,1697],{},"Vect (S n)"," en el tipo significa una lista con ",[277,1700,1701],{},"al menos un elemento garantizado."," No es que lo hayamos comprobado ni que lo hayamos acordado: simplemente no puedes construir ",[188,1704,1156],{}," con una lista vacía. Recuerda este fragmento: volvemos a él al final.",[175,1707,1708],{},"Una idea, cinco lenguajes, exhaustividad en todos. Si no trabajas con Dart, todo lo que sigue funciona igual para ti.",[439,1710,1712],{"id":1711},"la-exhaustividad-que-es-para-lo-que-era-todo-esto","La exhaustividad, que es para lo que era todo esto",[175,1714,1715],{},"Un mes después llega un requisito: cuando la lista llegue vacía, muestra una pantalla de estado vacío con un botón. Eso no es un error ni es éxito con datos. Es un cuarto estado.",[182,1717,1719],{"className":184,"code":1718,"language":18,"meta":186,"style":186},"final class Empty extends ProductsState {\n  const Empty();\n}\n",[188,1720,1721,1736,1746],{"__ignoreMap":186},[191,1722,1723,1725,1727,1730,1732,1734],{"class":193,"line":194},[191,1724,998],{"class":212},[191,1726,969],{"class":197},[191,1728,1729],{"class":201}," Empty",[191,1731,1006],{"class":197},[191,1733,202],{"class":201},[191,1735,206],{"class":205},[191,1737,1738,1740,1742,1744],{"class":193,"line":209},[191,1739,978],{"class":212},[191,1741,1729],{"class":201},[191,1743,983],{"class":205},[191,1745,235],{"class":234},[191,1747,1748],{"class":193,"line":238},[191,1749,269],{"class":205},[175,1751,1752],{},"Una línea. No ha cambiado nada más. Y el proyecto no compila:",[182,1754,1759],{"className":1755,"code":1757,"language":1758},[1756],"language-text","The type 'ProductsState' is not exhaustively matched by the switch\ncases since it doesn't match 'Empty()'.\n","text",[188,1760,1757],{"__ignoreMap":186},[175,1762,1763,1764,1767],{},"El compilador enumera ",[277,1765,1766],{},"todos"," los sitios donde el estado nuevo no se trata. No uno de ellos: todos.",[175,1769,1770,1771,1773,1774,1777],{},"Ahora, por qué esto pertenece a un artículo sobre código escrito por IA. «Añade un estado vacío a la pantalla de listado» es exactamente el tamaño de tarea que la gente le pasa a un agente sin mirar. El agente añadirá la clase, actualizará el ",[188,1772,1345],{}," que tenía en contexto y ",[277,1775,1776],{},"no actualizará los otros dos",": el de la analítica y el que hay detrás del botón de recargar. No estaban en el prompt, así que para él no existen.",[175,1779,1780],{},"Con flags, eso llega a producción. No hay diff que lo pille, porque el diff solo contiene lo que el agente cambió. Los tests están en verde, porque un estado que no existía hace un mes no tiene tests por definición. Te enteras por un usuario, seis semanas después, en forma de «a veces me sale una pantalla en blanco».",[175,1782,1783],{},"Con sealed classes no llega a ninguna parte. No compila.",[175,1785,1786,1787,1790],{},"Y aquí está lo que merece la pena llevarse. ",[277,1788,1789],{},"La exhaustividad no es protección frente a la IA. Es retroalimentación para ella."," Lo que hace malo a un agente en esto es que no tiene acceso a la lista de «y no te olvides tampoco de esto de aquí» que tú llevas en la cabeza. El compilador emite esa lista en formato legible por máquina, con ficheros y números de línea. El agente choca contra el rojo y arregla los tres, que es algo que hace genuinamente bien porque le han dicho exactamente dónde. No le estás impidiendo trabajar: le estás dando lo que le faltaba. El mismo mecanismo ayuda a una persona una vez por semana y a un agente cada veinte minutos.",[538,1792,1794],{"id":1793},"la-única-línea-que-lo-desactiva-todo","La única línea que lo desactiva todo",[182,1796,1798],{"className":184,"code":1797,"language":18,"meta":186,"style":186},"return switch (state) {\n  Loading() => const AppSpinner(),\n  _ => ItemList(state.items),\n};\n",[188,1799,1800,1809,1826,1844],{"__ignoreMap":186},[191,1801,1802,1805,1807],{"class":193,"line":194},[191,1803,1804],{"class":1218},"return",[191,1806,1222],{"class":1218},[191,1808,1225],{"class":205},[191,1810,1811,1814,1816,1818,1820,1822,1824],{"class":193,"line":209},[191,1812,1813],{"class":201},"  Loading",[191,1815,1233],{"class":205},[191,1817,1236],{"class":197},[191,1819,1239],{"class":212},[191,1821,1242],{"class":201},[191,1823,983],{"class":205},[191,1825,1247],{"class":234},[191,1827,1828,1831,1833,1835,1838,1840,1842],{"class":193,"line":238},[191,1829,1830],{"class":205},"  _ ",[191,1832,1236],{"class":197},[191,1834,1291],{"class":201},[191,1836,1837],{"class":205},"(state",[191,1839,487],{"class":234},[191,1841,1119],{"class":205},[191,1843,1247],{"class":234},[191,1845,1846,1849],{"class":193,"line":251},[191,1847,1848],{"class":205},"}",[191,1850,235],{"class":234},[175,1852,1853],{},"Un guion bajo y la exhaustividad ha muerto. En silencio, sin ningún aviso. Una rama que lo captura todo trata todos los casos por definición, así que al compilador no le queda nada que comprobar. Añade un quinto estado, un sexto, un décimo: compila y cae aquí en silencio.",[175,1855,1856],{},"Si te llevas exactamente una regla de revisión de este artículo, llévate esta:",[1858,1859,1860],"blockquote",{},[175,1861,1862],{},[277,1863,1864],{},"Un switch sobre un tipo sealed no debería tener rama por defecto.",[175,1866,1867],{},"Es la única línea de todo esto que hay que comprobar con los ojos, y una regla de lint la detecta para que no tengas ni que hacerlo.",[439,1869,1871],{"id":1870},"dónde-no-ayudan-los-tipos-algebraicos","Dónde no ayudan los tipos algebraicos",[175,1873,1874],{},"Cuatro sitios donde no funcionan o funcionan en tu contra. Sin esta sección lo demás es un sermón.",[175,1876,1877,1880,1881,1883,1884,1887],{},[277,1878,1879],{},"Una suma no cura un producto."," Has partido la pantalla en tres estados, y eso está bien. Pero ",[188,1882,1156],{}," sigue siendo una clase corriente con campos, y si son doce y la mitad son nullable, has movido el pantano un piso más abajo. Los tipos algebraicos van de ",[277,1885,1886],{},"qué estados existen",", no de qué hay dentro de un estado.",[175,1889,1890,1893,1894,1896,1897,1899,1900,1903,1904,1906,1907,1909,1910,1913,1914,1917],{},[277,1891,1892],{},"Estados que se solapan: el error más común al cambiar de enfoque."," Llega un requisito de pull-to-refresh: la lista ya está en pantalla y estás pidiendo una nueva. ¿Eso es ",[188,1895,1160],{}," o ",[188,1898,1156],{},"? La respuesta ingenua es un caso ",[188,1901,1902],{},"Refreshing"," con ",[188,1905,300],{}," dentro. Una semana después tienes ",[188,1908,1902],{},", ",[188,1911,1912],{},"RefreshingAfterError"," y ",[188,1915,1916],{},"FailedButHasCache",": la misma explosión combinatoria, ahora en clases, que es peor que con flags porque es más verbosa.",[175,1919,1920],{},"La respuesta correcta molesta a la gente:",[182,1922,1924],{"className":184,"code":1923,"language":18,"meta":186,"style":186},"final class Loaded extends ProductsState {\n  const Loaded(this.items, {this.isRefreshing = false});\n  final List\u003CProduct> items;\n  final bool isRefreshing;\n}\n",[188,1925,1926,1940,1978,1992,2003],{"__ignoreMap":186},[191,1927,1928,1930,1932,1934,1936,1938],{"class":193,"line":194},[191,1929,998],{"class":212},[191,1931,969],{"class":197},[191,1933,1097],{"class":201},[191,1935,1006],{"class":197},[191,1937,202],{"class":201},[191,1939,206],{"class":205},[191,1941,1942,1944,1946,1948,1950,1952,1954,1956,1959,1961,1963,1966,1969,1973,1976],{"class":193,"line":209},[191,1943,978],{"class":212},[191,1945,1097],{"class":201},[191,1947,1055],{"class":205},[191,1949,1059],{"class":1058},[191,1951,487],{"class":234},[191,1953,300],{"class":205},[191,1955,804],{"class":234},[191,1957,1958],{"class":205}," {",[191,1960,1059],{"class":1058},[191,1962,487],{"class":234},[191,1964,1965],{"class":205},"isRefreshing ",[191,1967,1968],{"class":197},"=",[191,1970,1972],{"class":1971},"s39Yj"," false",[191,1974,1975],{"class":205},"})",[191,1977,235],{"class":234},[191,1979,1980,1982,1984,1986,1988,1990],{"class":193,"line":238},[191,1981,213],{"class":212},[191,1983,216],{"class":201},[191,1985,219],{"class":205},[191,1987,222],{"class":201},[191,1989,1135],{"class":205},[191,1991,235],{"class":234},[191,1993,1994,1996,1998,2001],{"class":193,"line":251},[191,1995,213],{"class":212},[191,1997,243],{"class":201},[191,1999,2000],{"class":205}," isRefreshing",[191,2002,235],{"class":234},[191,2004,2005],{"class":193,"line":266},[191,2006,269],{"class":205},[175,2008,2009,2010,2013],{},"Sí, el booleano ha vuelto, y está ",[277,2011,2012],{},"bien."," Ahora vive dentro de un estado donde significa algo: «tenemos una lista y la estamos refrescando» es una situación real que necesitas poder expresar. Y «refrescando sin lista» ya no existe, porque no hay ningún sitio donde ponerlo.",[688,2015,2018],{":leading-icon":2016,":title":2017},"lucide:git-merge","Sumas para lo mutuamente excluyente, productos para lo que coexiste",[175,2019,2020],{},"Esta es la regla que decide si el refactor ayuda o perjudica. Dos cosas que nunca pueden ser ciertas a la vez piden una suma: casos separados de un tipo sealed. Dos cosas que habitualmente son ciertas a la vez piden un producto: campos uno al lado del otro dentro de un mismo caso. Confundirlos es la principal forma de acabar peor que al empezar, porque estados mutuamente excluyentes modelados como booleanos paralelos te dan el problema de las ocho combinaciones, y hechos que coexisten modelados como casos separados te dan una clase por combinación.",[175,2022,2023,2026,2027,2030,2031,2034,2035,2037],{},[277,2024,2025],{},"Los tipos no sostienen todos los invariantes, y esto importa más que el resto."," Una sealed class expresa maravillosamente un invariante ",[277,2028,2029],{},"estructural",": qué estados existen y qué datos viajan con qué estado. No expresa nada sobre invariantes ",[277,2032,2033],{},"de valor",": esta lista está ordenada, la fecha de inicio precede a la de fin, las líneas suman el total, esta cadena es un correo válido. Todo eso lo sigue sosteniendo una persona, y ningún ",[188,2036,966],{}," ayuda.",[175,2039,2040,2041,2043],{},"Ese fragmento de Idris es justamente el contraejemplo. ",[188,2042,1697],{}," —no vacía, en el tipo— es un invariante de valor empujado dentro del sistema de tipos. Eso se llama tipos dependientes, y podrías expresar «ordenada» o «inicio antes que fin» de la misma forma. El precio es un lenguaje en el que no escribes y, siendo realistas, no vas a escribir. En la práctica se recurre a otra técnica: un constructor privado con validación, para que el tipo no pueda crearse en una forma inválida.",[175,2045,2046,2047,2050,2051,2054,2055,2058,2059],{},"En el mismo saco están las ",[277,2048,2049],{},"fronteras del sistema."," El JSON que llega por la red no está tipado. Los tipos algebraicos empiezan ",[530,2052,2053],{},"después"," del parseo, y una sealed class no te salvará cuando el backend mande ",[188,2056,2057],{},"\"succes\""," con una sola «s»: de eso te salva una deserialización explícita con claves escritas a mano. La frase que merece la pena guardar: ",[277,2060,2061],{},"los tipos algebraicos eliminan las combinaciones imposibles de estados, no los valores imposibles.",[175,2063,2064,2067,2068,2070,2071,2073,2074,2077,2078],{},[277,2065,2066],{},"Y la sobreingeniería."," Una sealed class con dos casos y sin datos es un ",[188,2069,753],{},", y el ",[188,2072,753],{}," se lee más rápido. Una sealed class con un caso es una clase. Un booleano honesto sigue siendo un booleano honesto: ",[188,2075,2076],{},"isSelected"," en una casilla son exactamente dos estados, ambos válidos, y ahí un tipo algebraico no mejora nada. La prueba es simple: cuenta las combinaciones y tacha las que no tienen sentido. ",[277,2079,2080],{},"Si no hay nada que tachar, déjalo en paz.",[175,2082,2083,2084,2087],{},"Por último, el coste honesto: ",[277,2085,2086],{},"es verboso."," Cuatro estados en Dart son unas treinta líneas frente a cuatro campos. La mitad del argumento que dice que se tarda más en escribir no sobrevive a 2026: eso es exactamente el trabajo que un agente hace en segundos y sin errores. La otra mitad, que se tarda más en leer, no ha muerto, y ese es el precio que pagas.",[439,2089,2091],{"id":2090},"encuentra-los-tuyos-en-media-hora","Encuentra los tuyos en media hora",[2093,2094],"how-to-schema",{":description":2095,":name":2096,":steps":2097,":total-time":2098},"Un ejercicio de media hora que convierte un debate abstracto sobre tipos en una cuenta concreta de los estados inválidos que tu código compila hoy.","Cómo encontrar estados imposibles en tu propio código","[{\"name\": \"Elige una clase de estado\", \"text\": \"Busca una clase con tres o más campos que viajen siempre juntos: booleanos, nullables o una mezcla. El estado de una pantalla, el de un formulario, una envoltura de petición. Elige aquella cuyo estado se lea desde más sitios, no la que peor pinta tenga.\"}, {\"name\": \"Escribe todas las combinaciones\", \"text\": \"Para cada campo cuenta los estados que importan: un bool tiene dos, un campo nullable tiene dos, un enum tiene tantos como valores. Multiplícalos. Tres campos son ocho filas, cuatro son dieciséis. Escribe las filas en vez de razonar sobre ellas, porque el sentido del ejercicio es precisamente que nadie sostiene dieciséis filas en la cabeza.\"}, {\"name\": \"Tacha las que nunca ocurren\", \"text\": \"Ve fila por fila y marca cada una como una situación real en la que el producto puede estar o como algo que no pasa nunca. Sé estricto: si tienes que inventarte una historia para justificar que una fila podría darse, es que no se da.\"}, {\"name\": \"Cuenta lo que queda\", \"text\": \"Cada fila que has tachado es un estado imposible que compila hoy. Cinco de ocho es el resultado habitual. Ese número es el tamaño del hueco entre lo que permite tu tipo y lo que admite tu dominio.\"}, {\"name\": \"Busca la rama por defecto\", \"text\": \"Busca en el código los switch sobre ese estado y mira si tienen una rama por defecto o un guion bajo. Cada una es un sitio donde un estado nuevo caerá en silencio en lugar de romper la compilación.\"}, {\"name\": \"Reescribe primero el más leído\", \"text\": \"Convierte esa única clase en una jerarquía sealed con campos no nullable y arregla todos los errores de compilación que produzca. Esos errores son la lista de sitios que dependían de una convención que nadie escribió.\"}]","PT30M",[2100,2101],"checklist",{":groups":2102,":subtitle":2103,":title":2104},"[{\"title\": \"El tipo en sí\", \"items\": [\"Las situaciones mutuamente excluyentes son casos separados de un tipo sealed, no booleanos paralelos\", \"Cada campo dentro de un caso es no nullable, o el null significa algo concreto y está documentado\", \"Los datos viven en el estado al que pertenecen, no al lado de un enum de estado\", \"Los hechos que coexisten son campos dentro de un mismo caso, no un caso por combinación\"]}, {\"title\": \"Cada switch sobre él\", \"items\": [\"Ninguna rama por defecto ni patrón de guion bajo sobre un tipo sealed\", \"Ningún desempaquetado forzado de un campo que el estado ya debería garantizar\", \"Están cubiertos todos los lectores del estado, incluidos la analítica, los logs y los manejadores en segundo plano\"]}, {\"title\": \"Lo que los tipos no harán por ti\", \"items\": [\"Los invariantes de valor —ordenado, no vacío, dentro de rango, bien formado— se imponen en un constructor, no se dan por supuestos\", \"El parseo en la frontera del sistema valida de forma explícita en lugar de fiarse de la forma de los datos\", \"Los assert se tratan como ayuda de depuración, porque no se ejecutan en las compilaciones de release\"]}]","Diez comprobaciones en tres grupos. Todo lo que quede sin marcar es una decisión que alguien todavía no ha tomado.","Revisar una clase de estado",[439,2106,2108],{"id":2107},"la-versión-corta","La versión corta",[175,2110,2111],{},"Un estado imposible es aquel que tu tipo permite y tu dominio prohíbe, que es lo mismo que un invariante roto. Y sale caro no porque rompa algo en tiempo de ejecución, sino porque cada convención que no está en el tipo la vuelve a deducir alguien en cada lectura.",[175,2113,2114],{},"El invariante siempre existe. La única pregunta es si lo sostiene una persona —con un comentario, una página de wiki o un assert que no se ejecuta en release— o si no hay nada que sostener porque romperlo no compila.",[175,2116,2117],{},"Tres campos que viajan juntos, ocho combinaciones, tres con sentido. Una sealed class con campos no nullable deja exactamente esas tres y hace que las otras cinco no se puedan construir. La exhaustividad es la recompensa: convierte «alguien se olvidó de actualizar un caso» en «el proyecto no compila», y funciona igual sea quien sea el que se olvidó. Y una rama por defecto lo desactiva todo, en silencio.",[175,2119,2120],{},"Y el cambio de fondo. Antes los tipos eran la forma de explicar tu razonamiento a un colega que abriría el fichero seis meses después. Ahora son la única forma de explicárselo a algo que escribe código más rápido de lo que tú lees. El oficio no ha desaparecido: ha subido un nivel. Antes escribías la implementación. Ahora escribes las fronteras dentro de las que la implementación tiene que quedarse, y rellenar lo de dentro puede hacerlo cualquiera. Incluida una máquina.",[439,2122,2124],{"id":2123},"preguntas-frecuentes","Preguntas frecuentes",[2126,2127],"questions",{":items":2128},"[{\"title\": \"¿Qué es un estado imposible en programación?\", \"text\": \"Un estado que tu tipo permite y tu dominio prohíbe. Una clase de estado de pantalla con un booleano de carga, una lista nullable y un error nullable tiene ocho combinaciones, pero solo tres describen una situación en la que el producto puede estar realmente. Las otras cinco compilan, pasan la revisión y llegan a producción. La misma idea se llama con más precisión invariante roto: el invariante es la regla que tiene que cumplirse siempre, y el estado imposible es lo que obtienes cuando el tipo no la impone.\"}, {\"title\": \"¿Qué son los tipos de datos algebraicos?\", \"text\": \"Una forma de describir cuántos valores puede tomar un tipo. Un tipo producto es y: una clase corriente con campos, cuyo número de valores es el producto de los de sus campos, de modo que cada campo añadido multiplica. Un tipo suma es o: el valor pertenece a un caso o a otro y no hay una tercera opción, así que las cuentas se suman en vez de multiplicarse. Un enum es una suma sin datos y una sealed class es una suma donde cada caso lleva sus propios datos. El sentido de usar sumas es que no puedes construir un valor que represente un estado que nunca fue válido.\"}, {\"title\": \"¿Por qué no basta con un enum de estado?\", \"text\": \"Porque empeora la aritmética. Un enum de tres valores junto a una lista nullable y un error nullable da doce combinaciones en lugar de ocho, y siguen siendo solo tres las que significan algo. El problema original queda intacto: el estado puede ser success mientras la lista es null, eso compila, y la interfaz acaba desempaquetando el campo a la fuerza. Un enum te dice en qué estado estás pero deja los datos al lado, atados al estado solo por convención. Una sealed class ata los datos al estado, así que la lista existe exactamente donde significa algo.\"}, {\"title\": \"¿Las sealed classes de Dart tienen que estar en un solo fichero?\", \"text\": \"En la misma librería, que en la práctica es el mismo fichero salvo que la partas a propósito con part. Es el mecanismo, no una regla de estilo: para comprobar la exhaustividad el compilador necesita la lista completa de subtipos, y solo puede garantizar que está completa dentro de la librería. La convención que se deriva es mantener juntos en un fichero todos los casos de un estado, y resulta cómoda: todo el espacio de estados de una pantalla se ve de un vistazo.\"}, {\"title\": \"¿Por qué una rama por defecto rompe la comprobación de exhaustividad?\", \"text\": \"Porque una rama que lo captura todo trata cualquier caso por definición, así que al compilador no le queda nada que verificar. En cuanto un switch sobre un tipo sealed tiene una rama por defecto o un guion bajo, añadir un quinto o un décimo estado compila sin problemas y cae en silencio en esa rama en lugar de romper la compilación. Es la regla de revisión más valiosa sobre el tema: un switch sobre un tipo sealed no debería tener rama por defecto, y una regla de lint puede imponerlo.\"}, {\"title\": \"¿Ayudan las sealed classes con el código generado por IA?\", \"text\": \"Sí, pero no como barrera. La exhaustividad es retroalimentación más que protección. Un agente al que le pides añadir un estado nuevo actualizará el switch que tenía en contexto y se dejará los que no, porque no estaban en el prompt. Con flags eso llega a producción en silencio, porque el diff solo muestra lo que cambió y un estado recién creado no tiene tests. Con un tipo sealed la compilación falla y el compilador nombra cada sitio sin tratar con fichero y número de línea, que es exactamente la entrada sobre la que un agente actúa bien.\"}, {\"title\": \"¿Dónde no ayudan los tipos de datos algebraicos?\", \"text\": \"En cuatro sitios. No arreglan un caso que es en sí mismo un tipo producto inflado con doce campos nullable. Perjudican cuando se usan para estados que realmente se solapan, como refrescar mientras los datos ya están en pantalla, que pide un booleano dentro del caso cargado en vez de un caso nuevo. No expresan nada sobre invariantes de valor como ordenado, no vacío o correo válido, que siguen necesitando un constructor que valide, y empiezan después del parseo, así que tampoco hacen nada en la frontera del sistema donde el backend manda una cadena de estado mal escrita. Y se convierten en sobreingeniería allí donde bastaba un enum o un booleano honesto.\"}]",[175,2130,2131,2132,2135,2136,2140,2141,487],{},"Si quieres la lista más amplia de lo que hacen los agentes sin supervisión con el código Flutter, esto es el número tres de nuestro ",[1348,2133,2134],{"href":1350},"desglose de siete puntos",", y el proceso con el que lo evitamos lo contamos en ",[1348,2137,2139],{"href":2138},"\u002Fblog\u002Fbuilding-with-ai","cómo construimos con IA",". Este artículo acompaña a nuestro episodio en vídeo sobre el mismo tema; el anterior fue ",[1348,2142,2144],{"href":2143},"\u002Fblog\u002Fssl-certificate-pinning-flutter","tu SSL pinning probablemente no funciona",[175,2146,2147],{},"Haz el ejercicio y cuéntanos cuántas filas tachaste y en qué pantalla. Nuestra apuesta es que la mayoría acaba en los mismos tres estados que nosotros.",[2149,2150,2151],"post-credits",{},[175,2152,2153,2154,2158,2159,2163],{},"Ilya Nixan es fundador y lead developer en ",[1348,2155,2157],{"href":2156},"\u002F","Nerdy Production",", una agencia Flutter-first que construye y mantiene apps para fintech, salud y retail. También hacemos ",[1348,2160,2162],{"href":2161},"\u002Fservices\u002Fai-code-audit","auditoría de código escrito por IA"," para equipos cuya base de código creció más rápido de lo que nadie podía revisar.",[2165,2166,2167],"style",{},"html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":186,"searchDepth":209,"depth":209,"links":2169},[2170,2171,2175,2178,2181,2182,2185,2186,2187,2188],{"id":441,"depth":209,"text":442},{"id":512,"depth":209,"text":513,"children":2172},[2173,2174],{"id":540,"depth":238,"text":541},{"id":673,"depth":238,"text":674},{"id":713,"depth":209,"text":714,"children":2176},[2177],{"id":779,"depth":238,"text":780},{"id":941,"depth":209,"text":942,"children":2179},[2180],{"id":1327,"depth":238,"text":1328},{"id":1355,"depth":209,"text":1356},{"id":1711,"depth":209,"text":1712,"children":2183},[2184],{"id":1793,"depth":238,"text":1794},{"id":1870,"depth":209,"text":1871},{"id":2090,"depth":209,"text":2091},{"id":2107,"depth":209,"text":2108},{"id":2123,"depth":209,"text":2124},"md",{"type":2191,"url":2192},"youtube","https:\u002F\u002Fyoutu.be\u002FiUI7vWX_22w",{},"Un agente devolvió una clase de estado con ocho combinaciones y tres con sentido. Las sealed classes y la exhaustividad impiden compilar las otras cinco.","Estados imposibles en Dart: sealed classes y ADT","Un bool, dos nullables, ocho combinaciones, tres con sentido. Las otras cinco compilan. Así consigues que no compilen.","\u002Fblog\u002Fimpossible-states-dart-sealed-classes",{"title":169,"description":177},"impossible-states-dart-sealed-classes","blog\u002Fimpossible-states-dart-sealed-classes","Le dimos a un agente una tarea corriente —una pantalla de listado con carga, error y datos— y devolvió una clase con un booleano y dos campos nullable. Eso son ocho combinaciones, de las cuales tres significan algo; las otras cinco compilan, pasan la revisión, llegan a producción y se quedan ahí esperando. Aquí tienes qué es un estado imposible, por qué es lo mismo que un invariante roto, por qué el coste real no son los bugs en tiempo de ejecución sino las convenciones no escritas que cada lector tiene que reconstruir, y cómo las sealed classes junto con la exhaustividad convierten toda una categoría de errores en un fallo de compilación. Además: por qué un enum con campos nullable lo empeora, la única línea que desactiva la comprobación en silencio, y los cuatro sitios donde los tipos de datos algebraicos no ayudan nada.","2026-08-31T12:00:00Z","software-engineering",null,"Dls6t05dx7X9ewW--c8wRUXrdM8EiY438JJ5PBgEdrY",[2207,2264,2311,2361,2450,2502],{"id":2208,"bio":2209,"expertise":2214,"extension":2230,"links":2231,"meta":2235,"metaDescription":2236,"name":2241,"ogDescription":2244,"photo":2249,"role":2250,"seniority":2255,"skills":2256,"slug":2257,"specialization":2258,"stem":2257,"__hash__":2263},"team_members\u002Fdima.yaml",{"en":2210,"ru":2211,"es":2212,"nl":2213},"Dima has been building with [Flutter](\u002Ftechnologies\u002Fflutter) since 2021, and specializes in :term[state management]{slug=\"state-management\"} and app architecture.\n\nHe has hands-on experience with real-time communication protocols — :term{slug=\"webrtc\"} for audio and video, and :term{slug=\"xmpp\"} for messaging — which makes him comfortable with the network-heavy, stateful features many teams struggle to get right.\n","Дима работает с [Flutter](\u002Ftechnologies\u002Fflutter) с 2021 года и специализируется на :term[state management]{slug=\"state-management\"} и архитектуре приложений.\n\nУ него есть практический опыт с протоколами реального времени — :term{slug=\"webrtc\"} для аудио и видео и :term{slug=\"xmpp\"} для обмена сообщениями, — поэтому ему близки сетевые stateful-фичи, которые многим командам даются с трудом.\n","Dima trabaja con [Flutter](\u002Ftechnologies\u002Fflutter) desde 2021 y se especializa en :term[gestión de estado]{slug=\"state-management\"} y arquitectura de aplicaciones.\n\nTiene experiencia práctica con protocolos de comunicación en tiempo real —:term{slug=\"webrtc\"} para audio y vídeo, y :term{slug=\"xmpp\"} para mensajería—, lo que le hace sentirse cómodo con esas funcionalidades con mucho estado y mucha red que a muchos equipos se les atragantan.\n","Dima werkt sinds 2021 met [Flutter](\u002Ftechnologies\u002Fflutter) en is gespecialiseerd in :term[state management]{slug=\"state-management\"} en applicatiearchitectuur.\n\nHij heeft praktijkervaring met protocollen voor realtime communicatie — :term{slug=\"webrtc\"} voor audio en video, en :term{slug=\"xmpp\"} voor berichten — waardoor hij zich thuis voelt bij de netwerkzware functies met veel staat waar veel teams moeite mee hebben.\n",[2215,2218,2223,2225],{"en":2216,"ru":2216,"es":2217,"nl":2216},"State management","Gestión de estado",{"en":2219,"ru":2220,"es":2221,"nl":2222},"Application architecture","Архитектура приложений","Arquitectura de aplicaciones","Applicatiearchitectuur",{"en":2224,"ru":2224,"es":2224,"nl":2224},"WebRTC",{"en":2226,"ru":2227,"es":2228,"nl":2229},"XMPP messaging","Обмен сообщениями по XMPP","Mensajería XMPP","Berichten via XMPP","yaml",[2232],{"type":2233,"address":2234},"email","konopatov@nerdy.pro",{},{"en":2237,"ru":2238,"es":2239,"nl":2240},"Lead Flutter developer at Nerdy Production, building real-time apps since 2021 — state management, app architecture, WebRTC and XMPP.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года — state management, архитектура, WebRTC и XMPP.","Lead de desarrollo Flutter en Nerdy Production, construyendo apps en tiempo real desde 2021: gestión de estado, arquitectura de aplicaciones, WebRTC y XMPP.","Lead Flutter-developer bij Nerdy Production, bouwt sinds 2021 realtime-apps — state management, applicatiearchitectuur, WebRTC en XMPP.",{"en":2242,"ru":2243,"es":2242,"nl":2242},"Dima","Дима",{"en":2245,"ru":2246,"es":2247,"nl":2248},"Lead Flutter developer at Nerdy Production — real-time apps since 2021, and the state management that keeps them from falling over.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года и state management, на котором они держатся.","Lead de desarrollo Flutter en Nerdy Production: apps en tiempo real desde 2021 y la gestión de estado que evita que se caigan.","Lead Flutter-developer bij Nerdy Production — realtime-apps sinds 2021, en het state management dat ze overeind houdt.","\u002Fteam\u002Fdima.webp",{"en":2251,"ru":2252,"es":2253,"nl":2254},"Lead Flutter Developer","Ведущий Flutter-разработчик","Lead de desarrollo Flutter","Lead Flutter-developer","lead",[41,18,155,160,90,111,150,95],"dima",{"en":2259,"ru":2260,"es":2261,"nl":2262},"Real-time apps, state management, and architecture","Приложения реального времени, state management и архитектура","Apps en tiempo real, gestión de estado y arquitectura","Realtime-apps, state management en architectuur","1TuxUt3zjdVkQUvMf9dBITG872C0exrdZyyoKw1beiE",{"id":2265,"bio":2266,"expertise":2271,"extension":2230,"links":2204,"meta":2282,"metaDescription":2283,"name":2288,"ogDescription":2291,"photo":2296,"role":2297,"seniority":2302,"skills":2303,"slug":2304,"specialization":2305,"stem":2304,"__hash__":2310},"team_members\u002Fmasha.yaml",{"en":2267,"ru":2268,"es":2269,"nl":2270},"Masha builds [Flutter](\u002Ftechnologies\u002Fflutter) apps where design and copy are treated as one job.\n\nShe specializes in UI\u002FUX — turning product requirements into clean, usable interfaces — and in the writing inside the app, from onboarding flows to the microcopy that makes a screen make sense. The result is apps that feel considered, not just functional.\n","Маша делает приложения на [Flutter](\u002Ftechnologies\u002Fflutter), где дизайн и текст — одна задача.\n\nОна специализируется на UI\u002FUX, превращая продуктовые требования в чистые и удобные интерфейсы, и на текстах внутри приложения — от онбординга до микрокопирайта, который делает экран понятным. В итоге приложения получаются продуманными, а не просто рабочими.\n","Masha crea apps en [Flutter](\u002Ftechnologies\u002Fflutter) donde el diseño y el texto se tratan como un mismo trabajo.\n\nSe especializa en UI\u002FUX —convertir requisitos de producto en interfaces limpias y usables— y en la escritura dentro de la app, desde los flujos de onboarding hasta el microcopy que hace que una pantalla se entienda. El resultado son apps que se sienten pensadas, no solo funcionales.\n","Masha bouwt [Flutter](\u002Ftechnologies\u002Fflutter)-apps waarin ontwerp en tekst als één taak worden behandeld.\n\nZe is gespecialiseerd in UI\u002FUX — productwensen omzetten in heldere, bruikbare interfaces — en in de teksten binnen de app, van onboarding tot de microteksten die een scherm begrijpelijk maken. Het resultaat zijn apps die doordacht aanvoelen en niet alleen werken.\n",[2272,2277],{"en":2273,"ru":2274,"es":2275,"nl":2276},"UI\u002FUX design","UI\u002FUX-дизайн","Diseño UI\u002FUX","UI\u002FUX-ontwerp",{"en":2278,"ru":2279,"es":2280,"nl":2281},"Product copywriting","Продуктовый копирайтинг","Redacción de producto","Productteksten",{},{"en":2284,"ru":2285,"es":2286,"nl":2287},"Flutter developer at Nerdy Production working where design meets copy — UI\u002FUX, onboarding flows, and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: UI\u002FUX, онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: UI\u002FUX, onboarding y el microcopy que hace que una pantalla se entienda.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — UI\u002FUX, onboarding, en de microteksten die een scherm begrijpelijk maken.",{"en":2289,"ru":2290,"es":2289,"nl":2289},"Masha","Маша",{"en":2292,"ru":2293,"es":2294,"nl":2295},"Flutter developer at Nerdy Production working where design meets copy — onboarding flows and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: onboarding y el microcopy que hace entender una pantalla.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — onboarding en de microteksten die een scherm begrijpelijk maken.","\u002Fteam\u002Fmasha.webp",{"en":2298,"ru":2299,"es":2300,"nl":2301},"Flutter Developer","Flutter-разработчик","Desarrolladora Flutter","Flutter-developer","middle",[41,18],"masha",{"en":2306,"ru":2307,"es":2308,"nl":2309},"UI\u002FUX and copywriting for Flutter apps","UI\u002FUX и копирайтинг для Flutter-приложений","UI\u002FUX y redacción para apps Flutter","UI\u002FUX en teksten voor Flutter-apps","z3c64iWR39RIrn8wE0elSy00IfCITYgNkbgp_G_sslk",{"id":2312,"bio":2313,"expertise":2318,"extension":2230,"links":2329,"meta":2332,"metaDescription":2333,"name":2338,"ogDescription":2341,"photo":2346,"role":2347,"seniority":2352,"skills":2353,"slug":2354,"specialization":2355,"stem":2354,"__hash__":2360},"team_members\u002Fmaxim.yaml",{"en":2314,"ru":2315,"es":2316,"nl":2317},"Maxim is a polyglot engineer who moves comfortably across [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython), and [TypeScript](\u002Ftechnologies\u002Ftypescript), which lets him own a feature from the backend to the screen.\n\nHe came up at Ozon, one of Russia's largest marketplaces, where scale makes reliability non-negotiable — and it shows in his work: testing is a first-class part of how he builds, not an afterthought bolted on at the end.\n","Максим — полиглот-инженер, свободно работающий с [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) и [TypeScript](\u002Ftechnologies\u002Ftypescript), что позволяет ему вести фичу от бэкенда до экрана.\n\nОн вырос в Ozon, одном из крупнейших маркетплейсов России, где масштаб делает надёжность обязательной, — и это видно в его работе: тестирование для него первоклассная часть разработки, а не то, что прикручивают в конце.\n","Maxim es un ingeniero políglota que se mueve con soltura entre [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) y [TypeScript](\u002Ftechnologies\u002Ftypescript), lo que le permite hacerse cargo de una funcionalidad desde el backend hasta la pantalla.\n\nSe formó en Ozon, uno de los mayores marketplaces de Rusia, donde la escala hace que la fiabilidad no sea negociable, y se le nota: el testing es para él una parte de primera clase de cómo construye, no un añadido al final.\n","Maxim is een polyglotte engineer die zich moeiteloos beweegt tussen [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) en [TypeScript](\u002Ftechnologies\u002Ftypescript), waardoor hij een functie van de backend tot het scherm kan dragen.\n\nHij is opgegroeid bij Ozon, een van de grootste marktplaatsen van Rusland, waar schaal betrouwbaarheid onderhandelbaar maakt noch toestaat — en dat zie je terug in zijn werk: testen is bij hem een volwaardig onderdeel van hoe hij bouwt, geen bijzaak die er aan het eind bij komt.\n",[2319,2324],{"en":2320,"ru":2321,"es":2322,"nl":2323},"Automated testing","Автоматизированное тестирование","Testing automatizado","Geautomatiseerd testen",{"en":2325,"ru":2326,"es":2327,"nl":2328},"High-load backend systems","Высоконагруженные бэкенд-системы","Sistemas backend de alta carga","Backendsystemen met hoge belasting",[2330],{"type":2233,"address":2331},"maxim@nerdy.pro",{},{"en":2334,"ru":2335,"es":2336,"nl":2337},"Senior engineer at Nerdy Production across Go, Flutter, Python and TypeScript — from backend to screen, with automated testing built in.","Старший инженер Nerdy Production: Go, Flutter, Python и TypeScript — от бэкенда до экрана, с автотестами как частью разработки.","Ingeniero senior en Nerdy Production con Go, Flutter, Python y TypeScript: del backend a la pantalla, con testing automatizado incorporado.","Senior engineer bij Nerdy Production met Go, Flutter, Python en TypeScript — van backend tot scherm, met geautomatiseerd testen ingebouwd.",{"en":2339,"ru":2340,"es":2339,"nl":2339},"Maxim","Максим",{"en":2342,"ru":2343,"es":2344,"nl":2345},"Senior engineer at Nerdy Production working from backend to screen — Go, Flutter, Python and TypeScript, with the tests written as he goes.","Старший инженер Nerdy Production, работающий от бэкенда до экрана: Go, Flutter, Python и TypeScript — и тесты, которые пишутся по ходу дела.","Ingeniero senior en Nerdy Production, del backend a la pantalla: Go, Flutter, Python y TypeScript, con los tests escritos sobre la marcha.","Senior engineer bij Nerdy Production, van backend tot scherm — Go, Flutter, Python en TypeScript, met de tests die hij onderweg schrijft.","\u002Fteam\u002Fmax.webp",{"en":2348,"ru":2349,"es":2350,"nl":2351},"Senior Software Engineer","Старший инженер-программист","Ingeniero de software senior","Senior software engineer","senior",[41,18,51,106,155,13,150],"maxim",{"en":2356,"ru":2357,"es":2358,"nl":2359},"Backend and Flutter engineering with a testing focus","Бэкенд и Flutter с фокусом на тестирование","Ingeniería backend y Flutter con foco en testing","Backend- en Flutter-engineering met focus op testen","a-8Kx1i-PQXIGU92_Rimqr3bPEur6ZwMfPKQWr2uk4I",{"id":2362,"bio":2363,"expertise":2368,"extension":2230,"links":2410,"meta":2422,"metaDescription":2423,"name":2428,"ogDescription":2431,"photo":2436,"role":2437,"seniority":2442,"skills":2443,"slug":170,"specialization":2444,"stem":170,"__hash__":2449},"team_members\u002Fnixan.yaml",{"en":2364,"ru":2365,"es":2366,"nl":2367},"Ilya founded Nerdy Production and leads its engineering. He has been building software since 2010 and shipping production Flutter since 2018.\n\nBefore that he was CTO of QIWI, one of Russia's largest payment platforms, where he ran roughly 12 engineering teams spanning web products down to card processing, :term{slug=\"pci-dss\"} scope, and contactless payments — including building contactless card payments on Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, with EMV Contactless (Visa PayWave) on top.\n\nHe was also a principal developer at Yandex, where he worked on Yandex.Auto — taking native Android deep into the vehicle, with heavy CAN-bus integration through a custom CAN shield — and a principal at Evotor, whose point-of-sale devices run on a forked :term{slug=\"aosp\"}, giving him a low-level view of Android most app developers never touch.\n\nToday he leads delivery on the agency's flagship apps — from the chart-heavy fintech UI of [ExtraETF](\u002Fportfolio\u002Fextraetf) to the fully custom design system of [Arcana](\u002Fportfolio\u002Farcana). He writes most of the essays on this blog and maintains the agency's open-source work, including the [dxpdf](\u002Fopen-source\u002Fdxpdf) DOCX-to-PDF engine.\n\nHe works across [Flutter](\u002Ftechnologies\u002Fflutter), native iOS and Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes), and [Docker](\u002Ftechnologies\u002Fdocker), with a focus on app architecture, cross-platform delivery, and building teams that ship.\n","Илья основал Nerdy Production и руководит инженерной командой. Он занимается разработкой с 2010 года и выпускает продакшн-приложения на Flutter с 2018-го.\n\nДо этого он был CTO QIWI — одной из крупнейших платёжных платформ России, — где руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга, зоны :term{slug=\"pci-dss\"} и бесконтактных платежей, включая бесконтактную оплату картой на Android через :term[Host Card Emulation]{slug=\"host-card-emulation\"} поверх ISO\u002FIEC 14443, с платёжным протоколом EMV Contactless (Visa PayWave).\n\nОн также был принципал-разработчиком в Яндексе, где работал над Яндекс.Авто, уводя нативный Android глубоко в автомобиль, с серьёзной интеграцией по шине CAN через собственный CAN-шилд, и принципалом в Эвоторе, чьи кассовые устройства работают на форке :term{slug=\"aosp\"}, что дало ему низкоуровневый взгляд на Android, недоступный большинству прикладных разработчиков.\n\nСейчас он ведёт поставку флагманских приложений агентства — от насыщенного графиками финтех-интерфейса [ExtraETF](\u002Fportfolio\u002Fextraetf) до полностью кастомной дизайн-системы [Arcana](\u002Fportfolio\u002Farcana). Он пишет большую часть материалов этого блога и поддерживает open-source агентства, включая движок [dxpdf](\u002Fopen-source\u002Fdxpdf) для конвертации DOCX в PDF.\n\nРаботает с [Flutter](\u002Ftechnologies\u002Fflutter), нативными iOS и Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) и [Docker](\u002Ftechnologies\u002Fdocker); его фокус — архитектура приложений, кросс-платформенная поставка и построение команд, которые доводят продукт до релиза.\n","Ilya fundó Nerdy Production y dirige su ingeniería. Lleva construyendo software desde 2010 y entregando Flutter en producción desde 2018.\n\nAntes fue CTO de QIWI, una de las mayores plataformas de pago de Rusia, donde dirigió alrededor de 12 equipos de ingeniería que abarcaban desde productos web hasta procesamiento de tarjetas, alcance :term{slug=\"pci-dss\"} y pagos contactless, incluida la construcción de pagos contactless con tarjeta en Android mediante :term[Host Card Emulation]{slug=\"host-card-emulation\"} sobre ISO\u002FIEC 14443, con EMV Contactless (Visa PayWave) por encima.\n\nTambién fue principal developer en Yandex, donde trabajó en Yandex.Auto —llevando Android nativo hasta el interior del vehículo, con una integración intensiva por bus CAN a través de un CAN shield propio— y principal en Evotor, cuyos terminales de punto de venta funcionan sobre un fork de :term{slug=\"aosp\"}, lo que le dio una visión de bajo nivel de Android que la mayoría de desarrolladores de apps nunca llega a tocar.\n\nHoy lidera la entrega de las apps insignia de la agencia, desde la interfaz fintech cargada de gráficos de [ExtraETF](\u002Fportfolio\u002Fextraetf) hasta el sistema de diseño totalmente a medida de [Arcana](\u002Fportfolio\u002Farcana). Escribe la mayoría de los artículos de este blog y mantiene el trabajo de código abierto de la agencia, incluido el motor de conversión de DOCX a PDF [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nTrabaja con [Flutter](\u002Ftechnologies\u002Fflutter), iOS y Android nativos, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) y [Docker](\u002Ftechnologies\u002Fdocker), con foco en arquitectura de aplicaciones, entrega multiplataforma y la construcción de equipos que entregan.\n","Ilya richtte Nerdy Production op en leidt de engineering. Hij bouwt software sinds 2010 en levert sinds 2018 Flutter in productie.\n\nDaarvoor was hij CTO van QIWI, een van de grootste betaalplatforms van Rusland, waar hij zo'n 12 engineeringteams aanstuurde die reikten van webproducten tot kaartverwerking, :term{slug=\"pci-dss\"}-scope en contactloos betalen — waaronder het bouwen van contactloze kaartbetalingen op Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, met EMV Contactless (Visa PayWave) daarbovenop.\n\nHij was ook principal developer bij Yandex, waar hij aan Yandex.Auto werkte — native Android diep de auto in brengen, met zware integratie over de CAN-bus via een eigen CAN-shield — en principal bij Evotor, waarvan de kassa-apparaten op een fork van :term{slug=\"aosp\"} draaien, wat hem een blik op Android op laag niveau gaf die de meeste app-ontwikkelaars nooit krijgen.\n\nVandaag leidt hij de oplevering van de vlaggenschipapps van het bureau — van de grafiekzware fintech-UI van [ExtraETF](\u002Fportfolio\u002Fextraetf) tot het volledig eigen designsysteem van [Arcana](\u002Fportfolio\u002Farcana). Hij schrijft de meeste artikelen op deze blog en onderhoudt het opensourcewerk van het bureau, waaronder de DOCX-naar-PDF-motor [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nHij werkt met [Flutter](\u002Ftechnologies\u002Fflutter), native iOS en Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) en [Docker](\u002Ftechnologies\u002Fdocker), met de nadruk op applicatiearchitectuur, cross-platform oplevering en het bouwen van teams die opleveren.\n",[2369,2374,2379,2384,2389,2394,2399,2404,2406,2408],{"en":2370,"ru":2371,"es":2372,"nl":2373},"iOS development","Разработка под iOS","Desarrollo iOS","iOS-ontwikkeling",{"en":2375,"ru":2376,"es":2377,"nl":2378},"Software architecture","Архитектура ПО","Arquitectura de software","Softwarearchitectuur",{"en":2380,"ru":2381,"es":2382,"nl":2383},"Engineering team leadership","Руководство инженерными командами","Liderazgo de equipos de ingeniería","Leidinggeven aan engineeringteams",{"en":2385,"ru":2386,"es":2387,"nl":2388},"Payment systems","Платёжные системы","Sistemas de pago","Betaalsystemen",{"en":2390,"ru":2391,"es":2392,"nl":2393},"Card processing","Карточный процессинг","Procesamiento de tarjetas","Kaartverwerking",{"en":2395,"ru":2396,"es":2397,"nl":2398},"PCI-DSS compliance","Соответствие PCI-DSS","Cumplimiento de PCI-DSS","Naleving van PCI-DSS",{"en":2400,"ru":2401,"es":2402,"nl":2403},"NFC and contactless payments","NFC и бесконтактные платежи","NFC y pagos contactless","NFC en contactloos betalen",{"en":2405,"ru":2405,"es":2405,"nl":2405},"Host Card Emulation",{"en":2407,"ru":2407,"es":2407,"nl":2407},"EMV Contactless",{"en":2409,"ru":2409,"es":2409,"nl":2409},"AOSP",[2411,2414,2417,2420],{"type":2412,"url":2413},"github","https:\u002F\u002Fgithub.com\u002Fthenixan",{"type":2415,"url":2416},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthenixan\u002F",{"type":2418,"url":2419},"telegram","https:\u002F\u002Ft.me\u002Fthenixan",{"type":2233,"address":2421},"nixan@nerdy.pro",{},{"en":2424,"ru":2425,"es":2426,"nl":2427},"Founder and lead developer at Nerdy Production, former CTO of QIWI. Flutter architecture, payments infrastructure, and engineering teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI. Архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI. Arquitectura Flutter, infraestructura de pagos y equipos de ingeniería que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI. Flutter-architectuur, betaalinfrastructuur en teams die opleveren.",{"en":2429,"ru":2430,"es":2429,"nl":2429},"Ilya Nixan","Илья Никсан",{"en":2432,"ru":2433,"es":2434,"nl":2435},"Founder and lead developer at Nerdy Production, former CTO of QIWI — Flutter architecture, payments infrastructure, and teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI: архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI: arquitectura Flutter, infraestructura de pagos y equipos que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI — Flutter-architectuur, betaalinfrastructuur en teams die opleveren.","\u002Fteam\u002Fnixan.webp",{"en":2438,"ru":2439,"es":2440,"nl":2441},"Founder & Lead Developer","Основатель и ведущий разработчик","Fundador y lead developer","Oprichter & lead developer","founder_lead",[41,18,71,141,76,146,51,126,121,155,160,90,13,150,136,7,29,80],{"en":2445,"ru":2446,"es":2447,"nl":2448},"Flutter architecture and leading delivery teams","Архитектура на Flutter и руководство командами поставки","Arquitectura Flutter y liderazgo de equipos de entrega","Flutter-architectuur en het leiden van opleverteams","BmeQZ7JyhRFIx8527WO2t1-WklP8AAvevAxiN9U4TEs",{"id":2451,"bio":2452,"expertise":2457,"extension":2230,"links":2469,"meta":2474,"metaDescription":2475,"name":2480,"ogDescription":2483,"photo":2488,"role":2489,"seniority":2352,"skills":2494,"slug":2495,"specialization":2496,"stem":2495,"__hash__":2501},"team_members\u002Froma.yaml",{"en":2453,"ru":2454,"es":2455,"nl":2456},"Roman works across native iOS and Android and [Flutter](\u002Ftechnologies\u002Fflutter), and pairs that mobile depth with [Python](\u002Ftechnologies\u002Fpython) and applied AI.\n\nMuch of his background is in ERP and CRM development — complex, data-heavy business systems where getting the domain model right matters more than the UI — which gives him a pragmatic eye for how an app fits the process behind it.\n","Роман работает с нативными iOS и Android и с [Flutter](\u002Ftechnologies\u002Fflutter), дополняя мобильную экспертизу [Python](\u002Ftechnologies\u002Fpython) и прикладным AI.\n\nЗначительная часть его опыта — разработка ERP и CRM, сложных систем с большим объёмом данных, где правильная доменная модель важнее интерфейса, — что даёт ему прагматичный взгляд на то, как приложение встраивается в процесс за ним.\n","Roman trabaja con iOS y Android nativos y con [Flutter](\u002Ftechnologies\u002Fflutter), y combina esa profundidad móvil con [Python](\u002Ftechnologies\u002Fpython) e IA aplicada.\n\nBuena parte de su trayectoria está en el desarrollo de ERP y CRM —sistemas de negocio complejos y con mucho dato, donde acertar con el modelo de dominio importa más que la interfaz—, lo que le da una mirada pragmática sobre cómo encaja una app en el proceso que hay detrás.\n","Roman werkt met native iOS en Android en met [Flutter](\u002Ftechnologies\u002Fflutter), en combineert die mobiele diepgang met [Python](\u002Ftechnologies\u002Fpython) en toegepaste AI.\n\nEen groot deel van zijn achtergrond ligt in ERP- en CRM-ontwikkeling — complexe, datazware bedrijfssystemen waarin het domeinmodel goed krijgen zwaarder weegt dan de interface — wat hem een pragmatische blik geeft op hoe een app in het proces erachter past.\n",[2458,2459,2464],{"en":2370,"ru":2371,"es":2372,"nl":2373},{"en":2460,"ru":2461,"es":2462,"nl":2463},"Applied AI","Прикладной AI","IA aplicada","Toegepaste AI",{"en":2465,"ru":2466,"es":2467,"nl":2468},"ERP and CRM systems","ERP- и CRM-системы","Sistemas ERP y CRM","ERP- en CRM-systemen",[2470,2472],{"type":2415,"url":2471},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fromanbatler\u002F",{"type":2233,"address":2473},"roma@nerdy.pro",{},{"en":2476,"ru":2477,"es":2478,"nl":2479},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and a background in data-heavy ERP and CRM systems.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и опыт в ERP- и CRM-системах.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y experiencia en sistemas ERP y CRM con gran volumen de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en een achtergrond in datazware ERP- en CRM-systemen.",{"en":2481,"ru":2482,"es":2481,"nl":2481},"Roman","Рома",{"en":2484,"ru":2485,"es":2486,"nl":2487},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and years of data-heavy ERP and CRM behind him.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и годы работы с нагруженными ERP и CRM.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y años de ERP y CRM con mucha carga de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en jaren datazware ERP en CRM achter zich.","\u002Fteam\u002Froma.webp",{"en":2490,"ru":2491,"es":2492,"nl":2493},"Senior Mobile Developer","Старший мобильный разработчик","Desarrollador móvil senior","Senior mobiele developer",[7,41,18,141,146,106],"roma",{"en":2497,"ru":2498,"es":2499,"nl":2500},"Mobile, AI, and ERP\u002FCRM systems","Мобильная разработка, AI и системы ERP\u002FCRM","Móvil, IA y sistemas ERP\u002FCRM","Mobiel, AI en ERP\u002FCRM-systemen","caPSPO2BX35G9lEjNb6p3U4uF_V7yh8cexCe6l5hm8Y",{"id":2503,"bio":2504,"expertise":2509,"extension":2230,"links":2204,"meta":2520,"metaDescription":2521,"name":2526,"ogDescription":2529,"photo":2534,"role":2535,"seniority":2352,"skills":2540,"slug":2541,"specialization":2542,"stem":2541,"__hash__":2547},"team_members\u002Fxsox.yaml",{"en":2505,"ru":2506,"es":2507,"nl":2508},"Eugene builds the systems that apps depend on. His background is in backend engineering — designing and running the APIs, data models, and services behind a product, primarily in [Python](\u002Ftechnologies\u002Fpython) and [Django](\u002Ftechnologies\u002Fdjango).\n\nHe also works in [Flutter](\u002Ftechnologies\u002Fflutter), so he can reason about a feature from the database to the device and build the backend so the app that consumes it stays simple.\n","Женя строит системы, на которые опираются приложения. Его основной опыт — бэкенд-инженерия: проектирование и эксплуатация API, моделей данных и сервисов за продуктом, в первую очередь на [Python](\u002Ftechnologies\u002Fpython) и [Django](\u002Ftechnologies\u002Fdjango).\n\nОн также работает с [Flutter](\u002Ftechnologies\u002Fflutter), поэтому может рассуждать о фиче от базы данных до устройства и строить бэкенд так, чтобы потребляющее его приложение оставалось простым.\n","Eugene construye los sistemas de los que dependen las apps. Su trayectoria está en la ingeniería backend: diseñar y operar las APIs, los modelos de datos y los servicios que hay detrás de un producto, principalmente en [Python](\u002Ftechnologies\u002Fpython) y [Django](\u002Ftechnologies\u002Fdjango).\n\nTambién trabaja con [Flutter](\u002Ftechnologies\u002Fflutter), así que puede razonar sobre una funcionalidad desde la base de datos hasta el dispositivo y construir el backend de forma que la app que lo consume siga siendo simple.\n","Eugene bouwt de systemen waar apps op leunen. Zijn achtergrond ligt in backend-engineering: de API's, datamodellen en services achter een product ontwerpen en draaien, vooral in [Python](\u002Ftechnologies\u002Fpython) en [Django](\u002Ftechnologies\u002Fdjango).\n\nHij werkt daarnaast met [Flutter](\u002Ftechnologies\u002Fflutter), zodat hij over een functie kan nadenken van de database tot het toestel en de backend zo kan bouwen dat de app die hem verbruikt eenvoudig blijft.\n",[2510,2515],{"en":2511,"ru":2512,"es":2513,"nl":2514},"API design","Проектирование API","Diseño de APIs","API-ontwerp",{"en":2516,"ru":2517,"es":2518,"nl":2519},"Data modelling","Моделирование данных","Modelado de datos","Datamodellering",{},{"en":2522,"ru":2523,"es":2524,"nl":2525},"Senior backend developer at Nerdy Production building the APIs, data models and services behind our apps, primarily in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы за нашими приложениями, в первую очередь на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios detrás de nuestras apps, sobre todo en Python y Django.","Senior backenddeveloper bij Nerdy Production die de API's, datamodellen en services achter onze apps bouwt, vooral in Python en Django.",{"en":2527,"ru":2528,"es":2527,"nl":2527},"Eugene Xsox","Женя Xsox",{"en":2530,"ru":2531,"es":2532,"nl":2533},"Senior backend developer at Nerdy Production — the APIs, data models and services our apps run on, mostly in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы, на которых работают наши приложения, в основном на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios sobre los que corren nuestras apps, en Python y Django.","Senior backenddeveloper bij Nerdy Production — de API's, datamodellen en services waarop onze apps draaien, vooral in Python en Django.","\u002Fteam\u002Fxsox.webp",{"en":2536,"ru":2537,"es":2538,"nl":2539},"Senior Backend Developer","Старший бэкенд-разработчик","Desarrollador backend senior","Senior backenddeveloper",[106,24,41,18,155,160,90,150],"xsox",{"en":2543,"ru":2544,"es":2545,"nl":2546},"Python\u002FDjango backends, plus Flutter","Бэкенды на Python\u002FDjango и Flutter","Backends en Python\u002FDjango, además de Flutter","Backends in Python\u002FDjango, plus Flutter","w_QAKKq_Mh_dtMvAxFpAJBC10PW1I7zgVOxELwRHwO8",[2549,2558,2565,2570,2580],{"id":2550,"extension":2230,"meta":2551,"name":2552,"slug":2556,"stem":2556,"__hash__":2557},"blog_topics\u002Fcybersecurity.yaml",{},{"en":2553,"ru":2554,"es":2555,"nl":2553},"Cybersecurity","Кибербезопасность","Ciberseguridad","cybersecurity","kt1rdsMy5W4b9MvGHm6mZVCr0inHidEuqaosPYmmk4E",{"id":2559,"extension":2230,"meta":2560,"name":2561,"slug":2563,"stem":2563,"__hash__":2564},"blog_topics\u002Fdevops.yaml",{},{"en":2562,"ru":2562,"es":2562,"nl":2562},"DevOps","devops","D3mxlZuFHKCGeszKZuWCiQmHmP5EiCvzg1qEAIQhnyU",{"id":2566,"extension":2230,"meta":2567,"name":2568,"slug":41,"stem":41,"__hash__":2569},"blog_topics\u002Fflutter.yaml",{},{"en":39,"ru":39,"es":39,"nl":39},"OOeALAmwFuEqByuJvTGk4g15FIHENPfYMdpfZh_HTAE",{"id":2571,"extension":2230,"meta":2572,"name":2573,"slug":2578,"stem":2578,"__hash__":2579},"blog_topics\u002Ffor-founders.yaml",{},{"en":2574,"ru":2575,"es":2576,"nl":2577},"For Founders","Для основателей","Para fundadores","Voor oprichters","for-founders","lp2u6Ol2qF4DGxpB83HxbbGzWioT-e40mJhD4lr28-U",{"id":2581,"extension":2230,"meta":2582,"name":2583,"slug":2203,"stem":2203,"__hash__":2588},"blog_topics\u002Fsoftware-engineering.yaml",{},{"en":2584,"ru":2585,"es":2586,"nl":2587},"Software Engineering","Разработка ПО","Ingeniería de software","Software-engineering","aZMb3aUDptoFQk_cEE6l5H4wrYcgZuBYUubmKqOAaaU",{"data":2590,"body":2591},{},{"type":2592,"children":2593},"root",[2594,2600,2619,2630,2658],{"type":2595,"tag":175,"props":2596,"children":2597},"element",{},[2598],{"type":1758,"value":2599},"Ilya fundó Nerdy Production y dirige su ingeniería. Lleva construyendo software desde 2010 y entregando Flutter en producción desde 2018.",{"type":2595,"tag":175,"props":2601,"children":2602},{},[2603,2605,2610,2612,2617],{"type":1758,"value":2604},"Antes fue CTO de QIWI, una de las mayores plataformas de pago de Rusia, donde dirigió alrededor de 12 equipos de ingeniería que abarcaban desde productos web hasta procesamiento de tarjetas, alcance ",{"type":2595,"tag":2606,"props":2607,"children":2609},"term",{"slug":2608},"pci-dss",[],{"type":1758,"value":2611}," y pagos contactless, incluida la construcción de pagos contactless con tarjeta en Android mediante ",{"type":2595,"tag":2606,"props":2613,"children":2615},{"slug":2614},"host-card-emulation",[2616],{"type":1758,"value":2405},{"type":1758,"value":2618}," sobre ISO\u002FIEC 14443, con EMV Contactless (Visa PayWave) por encima.",{"type":2595,"tag":175,"props":2620,"children":2621},{},[2622,2624,2628],{"type":1758,"value":2623},"También fue principal developer en Yandex, donde trabajó en Yandex.Auto —llevando Android nativo hasta el interior del vehículo, con una integración intensiva por bus CAN a través de un CAN shield propio— y principal en Evotor, cuyos terminales de punto de venta funcionan sobre un fork de ",{"type":2595,"tag":2606,"props":2625,"children":2627},{"slug":2626},"aosp",[],{"type":1758,"value":2629},", lo que le dio una visión de bajo nivel de Android que la mayoría de desarrolladores de apps nunca llega a tocar.",{"type":2595,"tag":175,"props":2631,"children":2632},{},[2633,2635,2641,2643,2649,2651,2657],{"type":1758,"value":2634},"Hoy lidera la entrega de las apps insignia de la agencia, desde la interfaz fintech cargada de gráficos de ",{"type":2595,"tag":1348,"props":2636,"children":2638},{"href":2637},"\u002Fportfolio\u002Fextraetf",[2639],{"type":1758,"value":2640},"ExtraETF",{"type":1758,"value":2642}," hasta el sistema de diseño totalmente a medida de ",{"type":2595,"tag":1348,"props":2644,"children":2646},{"href":2645},"\u002Fportfolio\u002Farcana",[2647],{"type":1758,"value":2648},"Arcana",{"type":1758,"value":2650},". Escribe la mayoría de los artículos de este blog y mantiene el trabajo de código abierto de la agencia, incluido el motor de conversión de DOCX a PDF ",{"type":2595,"tag":1348,"props":2652,"children":2654},{"href":2653},"\u002Fopen-source\u002Fdxpdf",[2655],{"type":1758,"value":2656},"dxpdf",{"type":1758,"value":487},{"type":2595,"tag":175,"props":2659,"children":2660},{},[2661,2663,2668,2670,2675,2676,2681,2682,2687,2688,2693,2694,2699,2700,2705],{"type":1758,"value":2662},"Trabaja con ",{"type":2595,"tag":1348,"props":2664,"children":2666},{"href":2665},"\u002Ftechnologies\u002Fflutter",[2667],{"type":1758,"value":39},{"type":1758,"value":2669},", iOS y Android nativos, ",{"type":2595,"tag":1348,"props":2671,"children":2673},{"href":2672},"\u002Ftechnologies\u002Fgo",[2674],{"type":1758,"value":49},{"type":1758,"value":1909},{"type":2595,"tag":1348,"props":2677,"children":2679},{"href":2678},"\u002Ftechnologies\u002Frust",[2680],{"type":1758,"value":124},{"type":1758,"value":1909},{"type":2595,"tag":1348,"props":2683,"children":2685},{"href":2684},"\u002Ftechnologies\u002Ftypescript",[2686],{"type":1758,"value":153},{"type":1758,"value":1909},{"type":2595,"tag":1348,"props":2689,"children":2691},{"href":2690},"\u002Ftechnologies\u002Fkotlin",[2692],{"type":1758,"value":69},{"type":1758,"value":1909},{"type":2595,"tag":1348,"props":2695,"children":2697},{"href":2696},"\u002Ftechnologies\u002Fkubernetes",[2698],{"type":1758,"value":78},{"type":1758,"value":1913},{"type":2595,"tag":1348,"props":2701,"children":2703},{"href":2702},"\u002Ftechnologies\u002Fdocker",[2704],{"type":1758,"value":27},{"type":1758,"value":2706},", con foco en arquitectura de aplicaciones, entrega multiplataforma y la construcción de equipos que entregan.",[2708,2720,2737,2752,2765,2777,2791,2806,2819,2834,2847,2862,2874,2887,2900,2912,2929,2942,2955,2968,2982,2995,3007,3018,3031,3041,3054,3065,3074,3087,3099,3113,3120,3132,3143,3154,3166,3181,3194,3207,3219,3230,3244,3258,3270,3281,3293,3304,3317,3329,3340,3353,3362],{"slug":2626,"term":2409,"definition":2709,"category":2710,"aliases":2711,"links":2713,"related":2717,"readMore":-1,"target":2718,"hasArticle":2719},"El Android Open Source Project: Android sin la capa de Google encima, que cualquiera puede forkear. Los terminales de punto de venta, los kioscos y los sistemas de a bordo funcionan sobre forks suyos, y trabajar a ese nivel expone partes del SO que un desarrollador de apps nunca ve.","platform",[2712],"Android Open Source Project",[2714],{"kind":2715,"url":2716},"website","https:\u002F\u002Fsource.android.com\u002F",[2614],"\u002Fglossary#aosp",false,{"slug":2721,"term":2722,"definition":2723,"category":2724,"aliases":2725,"links":2730,"related":2734,"readMore":-1,"target":2736,"hasArticle":2719},"object-storage","Almacenamiento de objetos","Almacenamiento que guarda un archivo entero bajo una clave en vez de en un árbol de directorios: Amazon S3 y los muchos servicios que hablan su API. Barato, casi ilimitado y el hogar habitual de eventos en bruto, backups y multimedia: se escriben una vez, se leen poco y se guardan para siempre.","architecture",[2726,2727,2728,2729],"S3","almacenamiento compatible con S3","object storage","blob storage",[2731],{"kind":2732,"url":2733},"wikipedia","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject_storage",[2735],"bigquery","\u002Fglossary#object-storage",{"slug":2738,"term":2739,"definition":2740,"category":2741,"aliases":2742,"links":2747,"related":2748,"readMore":-1,"target":2751,"hasArticle":2719},"staff-augmentation","Ampliación de equipo","Un modelo de contratación en el que ingenieros de un socio externo se suman a tu equipo y trabajan bajo tu gestión —en tu repositorio, tus sprints, tu proceso— en vez de entregar un proyecto propio. Compras capacidad; el código y el contexto se quedan contigo.","business",[2743,2744,2745,2746],"staff augmentation","team augmentation","desarrolladores dedicados","outstaffing",[],[2749,2750],"total-cost-of-ownership","time-to-market","\u002Fglossary#staff-augmentation",{"slug":2753,"term":2754,"definition":2755,"category":2710,"aliases":2756,"links":2757,"related":2761,"readMore":-1,"target":2764,"hasArticle":2719},"app-clips","App Clips","Una función de Apple que ejecuta una porción pequeña de una app de iOS —menos de 15 MB— sin instalarla entera. Se invoca desde un código QR, una etiqueta NFC o un enlace, para cuando lo primero que hace un usuario no debería exigir pasar por la tienda.",[],[2758],{"kind":2759,"url":2760},"documentation","https:\u002F\u002Fdeveloper.apple.com\u002Fapp-clips\u002F",[2762,2763],"deep-linking","install-referrer","\u002Fglossary#app-clips",{"slug":2735,"term":2766,"definition":2767,"category":2710,"aliases":2768,"links":2769,"related":2774,"readMore":-1,"target":2776,"hasArticle":2719},"BigQuery","El almacén analítico de Google Cloud. Le lanzas SQL a miles de millones de filas y las recorre en segundos, sobre un almacenamiento separado de las máquinas que consultan, que es lo que mantiene los informes y la exploración fuera de la base de datos que sirve tráfico real.",[],[2770,2772],{"kind":2715,"url":2771},"https:\u002F\u002Fcloud.google.com\u002Fbigquery",{"kind":2732,"url":2773},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBigQuery",[2721,2775],"elasticsearch","\u002Fglossary#bigquery",{"slug":2778,"term":2779,"definition":2780,"category":2781,"aliases":2782,"links":2786,"related":2789,"readMore":-1,"target":2790,"hasArticle":2719},"ci-cd","CI\u002FCD","Automatización que compila, testea y publica cada cambio sin que nadie ejecute comandos a mano. En móvil es lo que convierte una release en pulsar un botón en lugar de en una tarde esperando a que alguien esté disponible.","practice",[2783,2784,2785],"CI","integración continua","entrega continua",[2787],{"kind":2732,"url":2788},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCI\u002FCD",[],"\u002Fglossary#ci-cd",{"slug":2792,"term":2793,"definition":2794,"category":2724,"aliases":2795,"links":2799,"related":2802,"readMore":-1,"target":2805,"hasArticle":2719},"headless-cms","CMS headless","Un sistema de contenidos con editor y API pero sin frontend propio. Los editores publican en un solo sitio y el sitio web o la app renderizan ese contenido por su cuenta, así que la capa de presentación es tuya y no del proveedor del CMS.",[2796,2797,2798],"headless CMS","gestor de contenidos headless","API de contenidos",[2800],{"kind":2732,"url":2801},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHeadless_content_management_system",[2803,2804],"server-side-rendering","rest","\u002Fglossary#headless-cms",{"slug":2807,"term":2808,"definition":2809,"category":2724,"aliases":2810,"links":2813,"related":2816,"readMore":-1,"target":2818,"hasArticle":2719},"crud","CRUD","Create, read, update, delete: las cuatro operaciones detrás de casi todo formulario y pantalla de administración. Nombre corto para la mitad rutinaria de una app, la que gestiona datos, frente a la que lleva la lógica de dominio.",[2811,2812],"Create, Read, Update, Delete","crear, leer, actualizar, borrar",[2814],{"kind":2732,"url":2815},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete",[2804,2817],"graphql","\u002Fglossary#crud",{"slug":2820,"term":2821,"definition":2822,"category":2724,"aliases":2823,"links":2827,"related":2830,"readMore":-1,"target":2833,"hasArticle":2719},"end-to-end-encryption","Cifrado de extremo a extremo","Cifrado aplicado en el dispositivo que envía y deshecho solo en el que recibe, de modo que el servicio que transporta el mensaje no puede leerlo: ni por orden judicial ni tras una brecha. Protege el contenido y nunca los metadatos.",[2824,2825,2826],"E2EE","cifrado extremo a extremo","end-to-end encryption",[2828],{"kind":2732,"url":2829},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEnd-to-end_encryption",[2831,2832],"tls","jwt","\u002Fglossary#end-to-end-encryption",{"slug":2835,"term":2836,"definition":2837,"category":2724,"aliases":2838,"links":2842,"related":2845,"readMore":-1,"target":2846,"hasArticle":2719},"floating-point","Coma flotante","El formato binario IEEE 754 detrás de double y float. No puede representar 0,1 de forma exacta, así que 0,1 + 0,2 da 0,30000000000000004: invisible en gráficos y fatal en dinero, que va en unidades menores enteras o en un tipo decimal.",[2839,2840,2841],"IEEE 754","double","aritmética de coma flotante",[2843],{"kind":2732,"url":2844},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FIEEE_754",[],"\u002Fglossary#floating-point",{"slug":2848,"term":2849,"definition":2850,"category":2741,"aliases":2851,"links":2856,"related":2859,"readMore":-1,"target":2861,"hasArticle":2719},"in-app-purchase","Compra dentro de la app","Vender bienes digitales o una suscripción a través de la facturación de Apple o Google, obligatoria para contenido digital en ambas tiendas y sujeta a comisión. Lo difícil nunca es la compra: es restaurarla en un dispositivo nuevo y mantener honesto el estado de los derechos.",[2852,2853,2854,2855],"IAP","in-app purchase","compras integradas","suscripción in-app",[2857],{"kind":2759,"url":2858},"https:\u002F\u002Fdeveloper.apple.com\u002Fin-app-purchase\u002F",[2860],"product-market-fit","\u002Fglossary#in-app-purchase",{"slug":2749,"term":2863,"definition":2864,"category":2741,"aliases":2865,"links":2869,"related":2872,"readMore":-1,"target":2873,"hasArticle":2719},"Coste total de propiedad","Lo que cuesta un producto a lo largo de toda su vida, no solo construirlo: mantenimiento, actualizaciones, migraciones anuales de SO y de tiendas, y el segundo equipo que contratas para mantener dos bases de código alineadas. Suele superar al presupuesto de desarrollo, y casi nunca va dentro.",[2866,2867,2868],"TCO","total cost of ownership","coste de propiedad",[2870],{"kind":2732,"url":2871},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTotal_cost_of_ownership",[2750],"\u002Fglossary#total-cost-of-ownership",{"slug":2875,"term":2876,"definition":2877,"category":2878,"aliases":2879,"links":2882,"related":2885,"readMore":-1,"target":2886,"hasArticle":2719},"post-quantum-cryptography","Criptografía poscuántica","Algoritmos de cifrado pensados para resistir a un futuro ordenador cuántico, ya estandarizados por el NIST. La migración urge antes que el hardware: el tráfico capturado hoy puede descifrarse cuando esa máquina exista.","protocol",[2880,2881],"PQC","cifrado poscuántico",[2883],{"kind":2732,"url":2884},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPost-quantum_cryptography",[2831,2820],"\u002Fglossary#post-quantum-cryptography",{"slug":2762,"term":2888,"definition":2889,"category":2710,"aliases":2890,"links":2895,"related":2898,"readMore":2899,"target":2899,"hasArticle":816},"Deep linking","Un enlace que abre una pantalla concreta dentro de una app instalada en vez de su pantalla de inicio o una página web. El deep linking diferido sobrevive a la instalación, así que un toque que pasa por la tienda acaba igualmente en la pantalla correcta.",[2891,2892,2893,2894],"deep linking diferido","enlaces profundos","universal links","Android App Links",[2896],{"kind":2732,"url":2897},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMobile_deep_linking",[2753,2763],"\u002Fglossary\u002Fdeep-linking",{"slug":2901,"term":2902,"definition":2903,"category":2781,"aliases":2904,"links":2908,"related":2909,"readMore":-1,"target":2911,"hasArticle":2719},"staged-rollout","Despliegue escalonado","Publicar una build primero a un porcentaje pequeño de usuarios y ampliarlo solo cuando la tasa sin fallos aguanta. Una build mala detectada al diez por ciento es una tarde mala; esa misma build al cien por cien es una semana mala.",[2905,2906,2907],"staged rollout","lanzamiento por fases","release canaria",[],[2910,2778],"feature-flags","\u002Fglossary#staged-rollout",{"slug":2913,"term":2914,"definition":2915,"category":2781,"aliases":2916,"links":2921,"related":2926,"readMore":-1,"target":2928,"hasArticle":2719},"dev-container","Dev Container","Un entorno de desarrollo descrito una sola vez en devcontainer.json —el SO, las herramientas y las versiones de runtime que un proyecto necesita— y abierto de forma idéntica en un contenedor por el editor de cada colaborador, en vez de una guía que cada quien interpreta a su manera.",[2917,2918,2919,2920],"devcontainer.json","Dev Containers","VS Code Dev Containers","contenedor de desarrollo",[2922,2924],{"kind":2715,"url":2923},"https:\u002F\u002Fcontainers.dev\u002F",{"kind":2759,"url":2925},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fdevcontainers\u002Fcontainers",[2927],"container-registry","\u002Fglossary#dev-container",{"slug":2775,"term":2930,"definition":2931,"category":2710,"aliases":2932,"links":2935,"related":2940,"readMore":-1,"target":2941,"hasArticle":2719},"Elasticsearch","Un motor de búsqueda y analítica que indexa registros para poder filtrarlos y buscarlos de forma interactiva en vez de recorrerlos. Lo que usas cuando la pregunta es «enséñame estas sesiones concretas, filtradas de seis maneras» y no «suma esta columna».",[2933,2934],"Elastic","ELK",[2936,2938],{"kind":2715,"url":2937},"https:\u002F\u002Fwww.elastic.co\u002Felasticsearch",{"kind":2732,"url":2939},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FElasticsearch",[2735],"\u002Fglossary#elasticsearch",{"slug":2860,"term":2943,"definition":2944,"category":2741,"aliases":2945,"links":2949,"related":2952,"readMore":-1,"target":2954,"hasArticle":2719},"Encaje producto-mercado","El punto en el que un producto ha encontrado de forma demostrable a gente que lo quiere: lo usa, vuelve y paga. Antes de eso la ingeniería responde a una pregunta; después, responde a la demanda.",[2946,2947,2948],"PMF","product-market fit","product\u002Fmarket fit",[2950],{"kind":2732,"url":2951},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct-market_fit",[2953,2750],"mvp","\u002Fglossary#product-market-fit",{"slug":2956,"term":2957,"definition":2958,"category":2724,"aliases":2959,"links":2962,"related":2963,"readMore":-1,"target":2967,"hasArticle":2719},"fan-out","Fan-out","Leer una fuente una sola vez y entregar cada actualización a todos los clientes suscritos. La versión ingenua escribe a los suscriptores en un bucle y se atasca en cuanto un socket va lento; una real usa un búfer por cliente y descarta a quien no siga el ritmo.",[2960,2961],"fanout","difusión",[],[2964,2965,2966],"websocket","pub-sub","server-sent-events","\u002Fglossary#fan-out",{"slug":2910,"term":2969,"definition":2970,"category":2781,"aliases":2971,"links":2975,"related":2978,"readMore":-1,"target":2981,"hasArticle":2719},"Feature flags","Interruptores que activan o desactivan funcionalidad desde la configuración y no desde una release. Permiten que un mismo binario se comporte distinto por marca, mercado o usuario, y apagar una función arriesgada sin pasar una build nueva por revisión.",[2972,2973,2974],"feature flag","feature toggle","banderas de funcionalidad",[2976],{"kind":2732,"url":2977},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFeature_toggle",[2901,2979,2980],"white-label","multi-tenancy","\u002Fglossary#feature-flags",{"slug":2983,"term":2217,"definition":2984,"category":2724,"aliases":2985,"links":2989,"related":2992,"readMore":-1,"target":2994,"hasArticle":2719},"state-management","Cómo decide una app dónde vive un valor, quién puede cambiarlo y qué partes de la pantalla se redibujan cuando lo hace. En Flutter, elegir entre Riverpod, BLoC y Provider es de las primeras decisiones arquitectónicas y de las más difíciles de revisar.",[2986,2987,2988],"state management","BLoC","Riverpod",[2990],{"kind":2759,"url":2991},"https:\u002F\u002Fdocs.flutter.dev\u002Fdata-and-backend\u002Fstate-mgmt\u002Foptions",[2993],"golden-test","\u002Fglossary#state-management",{"slug":2993,"term":2996,"definition":2997,"category":2781,"aliases":2998,"links":3002,"related":3005,"readMore":-1,"target":3006,"hasArticle":2719},"Golden test","Un test que renderiza un widget y compara el resultado píxel a píxel con una imagen de referencia guardada. En Flutter es la forma más barata de saber si un rediseño rompió el estado vacío a 320 pt, en modo oscuro y con el texto al 200 %.",[2999,3000,3001],"golden tests","test de captura","snapshot test",[3003],{"kind":2759,"url":3004},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FmatchesGoldenFile.html",[2778],"\u002Fglossary#golden-test",{"slug":2817,"term":3008,"definition":3009,"category":2724,"aliases":3010,"links":3011,"related":3016,"readMore":-1,"target":3017,"hasArticle":2719},"GraphQL","Un lenguaje de consulta para APIs donde el cliente nombra exactamente los campos que quiere y recibe una única respuesta con esa forma. Elimina el exceso de datos al que derivan los endpoints REST y añade un fallo propio: una consulta sin límites que recorre todo el modelo.",[],[3012,3014],{"kind":2715,"url":3013},"https:\u002F\u002Fgraphql.org\u002F",{"kind":2732,"url":3015},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGraphQL",[2804],"\u002Fglossary#graphql",{"slug":3019,"term":3020,"definition":3021,"category":2781,"aliases":3022,"links":3024,"related":3027,"readMore":-1,"target":3030,"hasArticle":2719},"hipaa","HIPAA","La ley estadounidense que regula la información sanitaria protegida: cómo puede almacenarse, transmitirse, registrarse y divulgarse. Como PCI-DSS, es una restricción arquitectónica que se elige al principio, no un documento de políticas que se añade antes del lanzamiento.",[3023],"Health Insurance Portability and Accountability Act",[3025],{"kind":2732,"url":3026},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHealth_Insurance_Portability_and_Accountability_Act",[2608,3028,3029],"gdpr","soc-2","\u002Fglossary#hipaa",{"slug":2614,"term":2405,"definition":3032,"category":2710,"aliases":3033,"links":3036,"related":3039,"readMore":-1,"target":3040,"hasArticle":2719},"Permitir que un móvil Android actúe por software como una tarjeta contactless sobre NFC, sin elemento seguro por hardware. Es así como paga una app de wallet en un terminal: el teléfono habla el mismo protocolo EMV contactless que habría hablado la tarjeta de plástico.",[3034,2407,3035],"HCE","pagos contactless",[3037],{"kind":2759,"url":3038},"https:\u002F\u002Fdeveloper.android.com\u002Fdevelop\u002Fconnectivity\u002Fnfc\u002Fhce",[2608,2626],"\u002Fglossary#host-card-emulation",{"slug":3042,"term":3043,"definition":3044,"category":2724,"aliases":3045,"links":3049,"related":3052,"readMore":-1,"target":3053,"hasArticle":2719},"multi-arch-image","Imagen multiarquitectura","Una única etiqueta de imagen que resuelve a binarios distintos según la arquitectura de CPU —linux\u002Famd64 y linux\u002Farm64 son el par habitual—, de modo que el mismo docker pull funciona sin cambios tanto en servidores Intel\u002FAMD como en portátiles con Apple Silicon.",[3046,3047,3048],"build multiarquitectura","multi-arch build","linux\u002Famd64 + linux\u002Farm64",[3050],{"kind":2759,"url":3051},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fmulti-platform\u002F",[2927],"\u002Fglossary#multi-arch-image",{"slug":3055,"term":3056,"definition":3057,"category":2710,"aliases":3058,"links":3059,"related":3062,"readMore":-1,"target":3064,"hasArticle":2719},"impeller","Impeller","El motor de renderizado que usa Flutter hoy, por defecto en iOS desde 2023 y en Android desde 2024. Compila sus shaders por adelantado en lugar de durante la primera animación, lo que eliminó el jank de shaders, el problema más visible de Flutter en producción.",[],[3060],{"kind":2759,"url":3061},"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller",[3063],"skia","\u002Fglossary#impeller",{"slug":2763,"term":3066,"definition":3067,"category":2710,"aliases":3068,"links":3069,"related":3072,"readMore":-1,"target":3073,"hasArticle":2719},"Install Referrer","Una API de Google Play que entrega a una app Android recién instalada los parámetros de campaña del enlace que llevó a la instalación. La mitad Android del deep linking diferido y la forma fiable de atribuir de dónde viene un usuario.",[],[3070],{"kind":2759,"url":3071},"https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Finstallreferrer",[2762,2753],"\u002Fglossary#install-referrer",{"slug":3075,"term":3076,"definition":3077,"category":2781,"aliases":3078,"links":3081,"related":3084,"readMore":-1,"target":3086,"hasArticle":2719},"prompt-injection","Inyección de prompts","Un ataque en el que el texto que aporta un usuario lo lee un modelo de lenguaje como instrucciones y no como datos, desviándolo de sus propias reglas. El hermano de la inyección SQL en la era de los LLM, y aparece donde se concatena entrada de usuario en un prompt.",[3079,3080],"prompt injection","ataque de inyección",[3082],{"kind":2732,"url":3083},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrompt_injection",[3085],"rate-limiting","\u002Fglossary#prompt-injection",{"slug":2832,"term":3088,"definition":3089,"category":2878,"aliases":3090,"links":3092,"related":3097,"readMore":-1,"target":3098,"hasArticle":2719},"JWT","Un token firmado que lleva sus propios claims, de modo que un servidor puede saber a quién pertenece una petición sin consultar una sesión. Estándar para la autenticación móvil. La firma prueba que no se alteró; no oculta lo que hay dentro.",[3091],"JSON Web Token",[3093,3095],{"kind":2715,"url":3094},"https:\u002F\u002Fjwt.io\u002F",{"kind":2732,"url":3096},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FJSON_Web_Token",[2608],"\u002Fglossary#jwt",{"slug":3100,"term":3101,"definition":3102,"category":2781,"aliases":3103,"links":3108,"related":3111,"readMore":-1,"target":3112,"hasArticle":2719},"kyc","KYC","Know Your Customer: las verificaciones de identidad que hace un producto financiero regulado antes de dejar mover dinero —captura de documentos, prueba de vida, listas de sanciones y control antiblanqueo—. Condiciona el onboarding más que cualquier decisión de diseño.",[3104,3105,3106,3107],"Know Your Customer","AML","KYC\u002FAML","conocimiento del cliente",[3109],{"kind":2732,"url":3110},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKnow_your_customer",[2608,3028],"\u002Fglossary#kyc",{"slug":76,"term":74,"definition":3114,"category":2710,"aliases":3115,"links":3117,"related":3118,"readMore":3119,"target":3119,"hasArticle":2719},"Compartir lógica de negocio escrita en Kotlin entre Android, iOS y el servidor mientras cada plataforma conserva su UI nativa. La alternativa a Flutter cuando la interfaz tiene que ser nativa pero las reglas que hay detrás no.",[3116],"KMP",[],[],"\u002Ftechnologies\u002Fkmp",{"slug":3085,"term":3121,"definition":3122,"category":2781,"aliases":3123,"links":3127,"related":3130,"readMore":-1,"target":3131,"hasArticle":2719},"Limitación de tasa","Un tope de cuántas peticiones puede hacer un mismo cliente en una ventana de tiempo. Es lo que impide que un usuario entusiasta, un scraper o un bot se gaste en una tarde un mes de presupuesto de API, y tiene que vivir en tu lado de la integración.",[3124,3125,3126],"rate limiting","rate limit","throttling",[3128],{"kind":2732,"url":3129},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",[3075],"\u002Fglossary#rate-limiting",{"slug":2953,"term":3133,"definition":3134,"category":2741,"aliases":3135,"links":3138,"related":3141,"readMore":-1,"target":3142,"hasArticle":2719},"MVP","La versión más pequeña de un producto que puede ponerse delante de usuarios reales y aun así responder la pregunta por la que lo construiste. Una decisión sobre alcance, no sobre calidad: un MVP también tiene que funcionar.",[3136,3137],"producto mínimo viable","minimum viable product",[3139],{"kind":2732,"url":3140},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMinimum_viable_product",[2979],"\u002Fglossary#mvp",{"slug":2979,"term":3144,"definition":3145,"category":2741,"aliases":3146,"links":3149,"related":3152,"readMore":3153,"target":3153,"hasArticle":816},"Marca blanca","Un mismo producto publicado bajo muchas marcas. Una plataforma móvil de marca blanca construye para cada cliente una app lista para la tienda con su nombre, su diseño y su contenido desde una única base de código compartida, en vez de forkear el proyecto por cliente.",[2979,3147,3148],"white label","app multiinquilino",[3150],{"kind":2732,"url":3151},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhite-label_product",[2953],"\u002Fglossary\u002Fwhite-label",{"slug":2980,"term":3155,"definition":3156,"category":2724,"aliases":3157,"links":3161,"related":3164,"readMore":-1,"target":3165,"hasArticle":2719},"Multi-tenancy","Un solo despliegue que sirve a muchos clientes, cada uno viendo solo sus datos, su configuración y sus funciones activas porque el contexto de tenant se resuelve en cada petición. Es lo que hace que una flota de apps de marca sea un producto y no muchos forks.",[3158,3159,3160],"multi-tenant","multiinquilino","tenant",[3162],{"kind":2732,"url":3163},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMultitenancy",[2979,2910],"\u002Fglossary#multi-tenancy",{"slug":3167,"term":3168,"definition":3169,"category":2878,"aliases":3170,"links":3174,"related":3179,"readMore":-1,"target":3180,"hasArticle":2719},"oauth","OAuth","El estándar detrás de Iniciar sesión con Apple, con Google y demás: el usuario autoriza tu app en un proveedor en el que ya confía y tu app recibe un token en lugar de su contraseña. Nadie inventa una credencial nueva y tú nunca almacenas ninguna.",[3171,3172,3173],"OAuth 2.0","login social","Iniciar sesión con Apple",[3175,3177],{"kind":2715,"url":3176},"https:\u002F\u002Foauth.net\u002F2\u002F",{"kind":2732,"url":3178},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOAuth",[2832],"\u002Fglossary#oauth",{"slug":2608,"term":3182,"definition":3183,"category":2781,"aliases":3184,"links":3187,"related":3192,"readMore":-1,"target":3193,"hasArticle":2719},"PCI-DSS","El estándar de seguridad del sector de tarjetas que obliga a quien almacena, procesa o transmite datos de tarjeta. La mayoría de las apps se mantienen fuera de su alcance a propósito, delegando la introducción de la tarjeta en un proveedor de pagos certificado.",[3185,3186],"PCI DSS","Payment Card Industry Data Security Standard",[3188,3190],{"kind":2715,"url":3189},"https:\u002F\u002Fwww.pcisecuritystandards.org\u002F",{"kind":2732,"url":3191},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPayment_Card_Industry_Data_Security_Standard",[2832],"\u002Fglossary#pci-dss",{"slug":3195,"term":3196,"definition":3197,"category":2710,"aliases":3198,"links":3202,"related":3205,"readMore":-1,"target":3206,"hasArticle":2719},"platform-channels","Platform channels","El puente que usa una app Flutter para llamar a código nativo de iOS y Android: Keychain y Keystore, biometría, hojas de pago, cualquier SDK sin paquete de Dart. Trabajo rutinario pero real, y lo primero donde encalla quien nunca salió de Dart.",[3199,3200,3201],"platform channel","method channel","canales de plataforma",[3203],{"kind":2759,"url":3204},"https:\u002F\u002Fdocs.flutter.dev\u002Fplatform-integration\u002Fplatform-channels",[76],"\u002Fglossary#platform-channels",{"slug":2965,"term":3208,"definition":3209,"category":2724,"aliases":3210,"links":3214,"related":3217,"readMore":-1,"target":3218,"hasArticle":2719},"Pub\u002FSub","Un patrón de mensajería donde un productor publica un evento y cualquier número de consumidores lo lee de forma independiente, con un broker en medio. El productor nunca los espera, y así la ruta de la petición sigue siendo rápida mientras el trabajo lento ocurre detrás.",[3211,3212,3213],"publicación\u002Fsuscripción","publish\u002Fsubscribe","Google Cloud Pub\u002FSub",[3215],{"kind":2732,"url":3216},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPublish%E2%80%93subscribe_pattern",[],"\u002Fglossary#pub-sub",{"slug":2804,"term":3220,"definition":3221,"category":2724,"aliases":3222,"links":3225,"related":3228,"readMore":-1,"target":3229,"hasArticle":2719},"REST","El estilo convencional de las APIs HTTP: una URL nombra un recurso y el verbo HTTP dice qué hacer con él. La forma por defecto en que una app habla con un backend, y lo que esperan encontrar la mayoría de integraciones de terceros.",[3223,3224],"API REST","Representational State Transfer",[3226],{"kind":2732,"url":3227},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FREST",[2964,2966],"\u002Fglossary#rest",{"slug":3028,"term":3231,"definition":3232,"category":2781,"aliases":3233,"links":3237,"related":3242,"readMore":-1,"target":3243,"hasArticle":2719},"RGPD","El reglamento de la UE sobre los datos personales de las personas en la UE: una base legal para recogerlos, consentimiento real para el rastreo y derechos de acceso y supresión. Sigue a tus usuarios, no a tus servidores, así que se aplica esté donde esté la empresa.",[3234,3235,3236],"GDPR","Reglamento General de Protección de Datos","protección de datos",[3238,3240],{"kind":2715,"url":3239},"https:\u002F\u002Fgdpr.eu\u002F",{"kind":2732,"url":3241},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGeneral_Data_Protection_Regulation",[2608,3029,3019],"\u002Fglossary#gdpr",{"slug":2927,"term":3245,"definition":3246,"category":2724,"aliases":3247,"links":3253,"related":3256,"readMore":-1,"target":3257,"hasArticle":2719},"Registro de contenedores","Almacenamiento alojado para imágenes de contenedores, direccionadas por nombre y etiqueta: Docker Hub, GitHub Container Registry o el propio de un proveedor cloud. Publicar una build ahí convierte 'funciona en mi máquina' en una imagen que cualquiera puede descargar y ejecutar sin cambios.",[3248,3249,3250,3251,3252],"registro de imágenes","Docker registry","GHCR","ghcr.io","container registry",[3254],{"kind":2759,"url":3255},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fpackages\u002Fworking-with-a-github-packages-registry\u002Fworking-with-the-container-registry",[2913,3042],"\u002Fglossary#container-registry",{"slug":2803,"term":3259,"definition":3260,"category":2724,"aliases":3261,"links":3265,"related":3268,"readMore":-1,"target":3269,"hasArticle":2719},"Renderizado en servidor","Construir una página como HTML terminado en el servidor, de modo que la primera respuesta ya lleva el contenido, los encabezados, las metaetiquetas y los datos estructurados. Los rastreadores, las vistas previas de enlaces y los dispositivos lentos lo leen sin ejecutar JavaScript.",[3262,3263,3264],"SSR","server-side rendering","renderizado del lado del servidor",[3266],{"kind":2732,"url":3267},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-side_scripting",[2792],"\u002Fglossary#server-side-rendering",{"slug":3029,"term":3271,"definition":3272,"category":2781,"aliases":3273,"links":3276,"related":3279,"readMore":-1,"target":3280,"hasArticle":2719},"SOC 2","Un informe de auditoría externa sobre cómo trata una organización los datos de clientes —seguridad, disponibilidad, confidencialidad— y no un certificado que se compra. Los clientes empresariales lo piden, y condiciona la arquitectura mucho antes que la propia auditoría.",[3274,3275],"SOC2","System and Organization Controls",[3277],{"kind":2732,"url":3278},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSystem_and_Organization_Controls",[2608,3028,3019],"\u002Fglossary#soc-2",{"slug":3282,"term":3283,"definition":3284,"category":2741,"aliases":3285,"links":3288,"related":3291,"readMore":-1,"target":3292,"hasArticle":2719},"saas","SaaS","Software vendido como suscripción continua a un producto alojado, en lugar de como una licencia única que el cliente instala y opera. El proveedor mantiene los servidores, publica actualizaciones sin parar y cobra por usuario o por uso.",[3286,3287],"Software as a Service","software como servicio",[3289],{"kind":2732,"url":3290},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSoftware_as_a_service",[2980,2979],"\u002Fglossary#saas",{"slug":2966,"term":3294,"definition":3295,"category":2878,"aliases":3296,"links":3299,"related":3302,"readMore":-1,"target":3303,"hasArticle":2719},"Server-Sent Events","Un flujo unidireccional del servidor al cliente sobre una conexión HTTP corriente. Más simple que un WebSocket y suficiente allí donde solo el servidor tiene algo que decir: un feed de progreso, o una respuesta de IA que llega token a token.",[3297,3298],"SSE","eventos enviados por el servidor",[3300],{"kind":2732,"url":3301},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-sent_events",[2964,2804],"\u002Fglossary#server-sent-events",{"slug":3063,"term":3305,"definition":3306,"category":2710,"aliases":3307,"links":3310,"related":3315,"readMore":-1,"target":3316,"hasArticle":2719},"Skia","La librería gráfica 2D de código abierto de Google que dibuja Chrome, Android y —hasta Impeller— cada fotograma de Flutter. Renderiza tanto a PDF como a pantalla, que es lo que hace la función de imprimir a PDF de Chrome.",[3308,3309],"Skia Graphics Engine","skia-safe",[3311,3313],{"kind":2715,"url":3312},"https:\u002F\u002Fskia.org\u002F",{"kind":2732,"url":3314},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSkia_Graphics_Engine",[3055],"\u002Fglossary#skia",{"slug":2831,"term":3318,"definition":3319,"category":2878,"aliases":3320,"links":3324,"related":3327,"readMore":-1,"target":3328,"hasArticle":2719},"TLS","La capa de cifrado bajo HTTPS. Demuestra que el servidor es quien dice su certificado, acuerda una clave nueva para la sesión y cifra todo lo demás, de modo que la red intermedia solo ve texto cifrado que no puede alterar sin que se note.",[3321,3322,3323],"SSL","HTTPS","Transport Layer Security",[3325],{"kind":2732,"url":3326},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTransport_Layer_Security",[2820,2832],"\u002Fglossary#tls",{"slug":2750,"term":3330,"definition":3331,"category":2741,"aliases":3332,"links":3335,"related":3338,"readMore":-1,"target":3339,"hasArticle":2719},"Time to market","Cuánto se tarda en llevar un producto desde la decisión hasta usuarios reales. Casi todas las discusiones sobre stack y alcance son en realidad discusiones sobre este número, porque cada semana ahorrada es una semana de ingresos, feedback y posición competitiva.",[3333,3334],"TTM","tiempo de salida al mercado",[3336],{"kind":2732,"url":3337},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTime_to_market",[2953,2749],"\u002Fglossary#time-to-market",{"slug":3341,"term":2224,"definition":3342,"category":2878,"aliases":3343,"links":3345,"related":3350,"readMore":-1,"target":3352,"hasArticle":2719},"webrtc","El estándar de navegador y móvil para enviar audio, vídeo y datos directamente entre dos dispositivos, con los servidores implicados solo en presentarlos entre sí. Es sobre lo que se construye una videollamada dentro de una app cuando no es un SDK alquilado.",[3344],"Web Real-Time Communication",[3346,3348],{"kind":2715,"url":3347},"https:\u002F\u002Fwebrtc.org\u002F",{"kind":2732,"url":3349},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebRTC",[3351,2964],"xmpp","\u002Fglossary#webrtc",{"slug":2964,"term":3354,"definition":3355,"category":2878,"aliases":3356,"links":3357,"related":3360,"readMore":-1,"target":3361,"hasArticle":2719},"WebSocket","Un protocolo que mantiene una conexión abierta entre cliente y servidor para que cualquiera de los dos pueda enviar en cualquier momento, en vez de que el cliente pregunte una y otra vez. Sobre esto funcionan los precios en vivo, el chat y los indicadores de presencia.",[],[3358],{"kind":2732,"url":3359},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebSocket",[2966,2804],"\u002Fglossary#websocket",{"slug":3351,"term":3363,"definition":3364,"category":2878,"aliases":3365,"links":3368,"related":3373,"readMore":-1,"target":3374,"hasArticle":2719},"XMPP","Un protocolo de mensajería abierto y federado, y la alternativa de siempre a escribir un backend de chat o alquilarlo. Se extiende a presencia, indicadores de escritura y transferencia de archivos, y es lo bastante antiguo como para que cada plataforma tenga una librería madura.",[3366,3367],"Jabber","Extensible Messaging and Presence Protocol",[3369,3371],{"kind":2715,"url":3370},"https:\u002F\u002Fxmpp.org\u002F",{"kind":2732,"url":3372},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FXMPP",[3341,2964],"\u002Fglossary#xmpp"]