<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Multita</title>
    <description>The latest articles on DEV Community by Multita (@multita).</description>
    <link>https://dev.to/multita</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3989684%2Fea00cd9a-dece-4032-834f-5cd6d8dd6741.png</url>
      <title>DEV Community: Multita</title>
      <link>https://dev.to/multita</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/multita"/>
    <language>en</language>
    <item>
      <title>Entity SEO: how sameAs, Wikidata and Crunchbase tell Google your brand is real</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Mon, 10 Aug 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/multita/entity-seo-how-sameas-wikidata-and-crunchbase-tell-google-your-brand-is-real-3h4e</link>
      <guid>https://dev.to/multita/entity-seo-how-sameas-wikidata-and-crunchbase-tell-google-your-brand-is-real-3h4e</guid>
      <description>&lt;p&gt;Most SEO advice is about links and keywords. There is a quieter layer that matters more for a young brand: entity SEO, the job of convincing Google (and the AI models reading the same graph) that your brand is one real, identifiable thing. Here is what actually moved the needle for &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  sameAs is not a backlink
&lt;/h2&gt;

&lt;p&gt;The common confusion: people think linking out to their own profiles drains authority. &lt;code&gt;sameAs&lt;/code&gt; is not that. It lives in your structured data and it is an identity statement, not a vote. It says "these profiles and this domain are the same entity". It passes no PageRank and it costs you nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/company/multita/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://www.crunchbase.com/organization/multita"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://www.wikidata.org/wiki/Q..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wikidata and Crunchbase: the graph the models read
&lt;/h2&gt;

&lt;p&gt;A Wikidata item and a Crunchbase profile are not big traffic sources. Their value is being structured, trusted nodes about your entity, the kind that get pulled into the Knowledge Graph that Google and AI assistants consult. For a new company with no Wikipedia page yet, they are the most credible "this exists" signal you can plant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters for AI answers
&lt;/h2&gt;

&lt;p&gt;When someone asks an assistant about your category, the model names you correctly only if your entity is well defined across these sources. Vague entity, vague or wrong citation. A consistent name, URL and description repeated across your structured data, your Wikidata item and your profiles is what makes the answer land on you.&lt;/p&gt;

&lt;p&gt;Entity SEO will not rank a page by itself. It makes everything else you do attach to a brand the machines actually recognize, and for a new site that recognition is worth more than another handful of links.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>marketing</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Getting a brand-new site read by Google and AI: IndexNow, llms.txt and structured data</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Mon, 03 Aug 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/multita/getting-a-brand-new-site-read-by-google-and-ai-indexnow-llmstxt-and-structured-data-1dp0</link>
      <guid>https://dev.to/multita/getting-a-brand-new-site-read-by-google-and-ai-indexnow-llmstxt-and-structured-data-1dp0</guid>
      <description>&lt;p&gt;A new domain has no authority and no crawl history, so the default is to wait weeks for Google to notice you. You can skip a lot of that wait. When we launched &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt;, three cheap and boring pieces did most of the work of getting indexed and getting quoted by AI assistants.&lt;/p&gt;

&lt;h2&gt;
  
  
  IndexNow: stop waiting for the crawler
&lt;/h2&gt;

&lt;p&gt;IndexNow lets you ping search engines the moment a page changes, instead of waiting to be crawled. You drop a key file at your root and POST the changed URLs. On Cloudflare it is a few lines in a Worker. New pages got picked up in hours, not days.&lt;/p&gt;

&lt;h2&gt;
  
  
  llms.txt: a map for the models
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a plain text file at your root that points AI crawlers at your most useful pages, in clean markdown. It is young as conventions go, but it costs nothing and it tells a model "this is what the site is about" without making it fight your nav and footer to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured data: say what you are in a language machines trust
&lt;/h2&gt;

&lt;p&gt;JSON-LD is the part both Google and the models lean on. An &lt;code&gt;Organization&lt;/code&gt; and a &lt;code&gt;WebSite&lt;/code&gt; block state your name, your URL and what you do, as data instead of guesswork.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Multita&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://multita.com.ar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payoff is clearer entity recognition, a real shot at a brand panel, and a much higher chance that an AI answer cites you correctly instead of inventing something.&lt;/p&gt;

&lt;p&gt;None of this buys authority. It buys legibility, which for a new site is the thing standing between you and being found. Ship all three on launch day.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>cloudflare</category>
      <category>ai</category>
    </item>
    <item>
      <title>API design: raw data, or totals with the business logic baked in?</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Mon, 27 Jul 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/multita/api-design-raw-data-or-totals-with-the-business-logic-baked-in-1och</link>
      <guid>https://dev.to/multita/api-design-raw-data-or-totals-with-the-business-logic-baked-in-1och</guid>
      <description>&lt;p&gt;When you expose a data API you hit this fork fast: do you return raw, normalized records and let the client do the math, or do you return totals with your business logic already applied? We faced it building the API for &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt;, which returns the traffic fines on a vehicle, and the answer that held up was "both, on purpose".&lt;/p&gt;

&lt;h2&gt;
  
  
  The case for raw
&lt;/h2&gt;

&lt;p&gt;Raw, normalized records are honest. Each fine comes back with its amount, status and jurisdiction, and the integrator decides what to do with them. A developer building a dashboard wants exactly this. They have their own rules, and a pre-chewed total just gets in the way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"debt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;209250&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voluntary_payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The case for baked-in totals
&lt;/h2&gt;

&lt;p&gt;Now picture a different integrator: a WhatsApp bot that quotes a client in a single message. It does not want to reimplement your discount rules, your "which fines actually count" logic, or your rounding. It wants a number it can trust. If every integrator re-derives that number, they will all get it a little wrong, each in their own way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we ship both
&lt;/h2&gt;

&lt;p&gt;So the &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;Multita API&lt;/a&gt; returns the normalized items plus an optional summary with the totals already computed. Raw for the people who want control, totals for the people who want an answer. The rule we follow is narrow: never force the client to reimplement business logic to get a correct number, and never hide the raw data either.&lt;/p&gt;

&lt;p&gt;If you are designing an API, start from who integrates it. A team with its own logic and a bot that just wants the result are different customers, and you can serve both without picking a side.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Construir software para un mercado que los devs ignoran</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Mon, 20 Jul 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/multita/construir-software-para-un-mercado-que-los-devs-ignoran-1gmj</link>
      <guid>https://dev.to/multita/construir-software-para-un-mercado-que-los-devs-ignoran-1gmj</guid>
      <description>&lt;p&gt;Los mejores nichos suelen ser los aburridos. Mientras todos hacen otra app de productividad, hay industrias enteras moviéndose con planillas, PDFs y trámites a mano, esperando que alguien se tome el laburo de entenderlas. Nosotros caímos en una de esas: los trámites del automotor en Argentina.&lt;/p&gt;

&lt;h2&gt;
  
  
  El nicho que nadie quiere
&lt;/h2&gt;

&lt;p&gt;Acá hay una profesión entera, las gestorías, que vive de hacer transferencias, informes y libre deudas para sus clientes. Parte de su día es revisar las multas de cada auto, porque una multa impaga frena una transferencia y la deuda viaja con el vehículo, no con el dueño anterior. El detalle: esas multas están repartidas en más de 33 sistemas oficiales que no se hablan entre sí.&lt;/p&gt;

&lt;p&gt;Ningún dev mira eso. No es sexy, no sale en Hacker News. Y justo por eso la competencia es floja y la demanda es real y constante.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué un mercado ignorado juega a favor
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;La gente ya tiene el problema y ya paga por resolverlo, hoy con horas de trabajo manual.&lt;/li&gt;
&lt;li&gt;La competencia rara vez es técnica, así que un producto que de verdad funcione gana casi solo.&lt;/li&gt;
&lt;li&gt;Entender el dominio es la barrera. Una vez que sabés qué es una "denuncia de venta" o por qué un acta que arranca con "01-" no tiene descuento de gestoría, tenés algo que un extraño no copia en un fin de semana.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso último es lo importante. En &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt; la parte difícil no fue el código, fue aprender las reglas raras de cada jurisdicción y meterlas en el producto. Ese conocimiento es el foso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo entrar
&lt;/h2&gt;

&lt;p&gt;Hablá con la gente que sufre el problema antes de escribir una línea. Las reglas que te cuenten, las que no están documentadas en ningún lado, son las que hacen que tu producto sea el que funciona y no el que casi funciona.&lt;/p&gt;

&lt;p&gt;Si estás buscando qué construir, mirá los rubros que todos evitan por aburridos. Ahí está la demanda sin atender.&lt;/p&gt;

</description>
      <category>indiehackers</category>
      <category>startup</category>
      <category>saas</category>
      <category>career</category>
    </item>
    <item>
      <title>Rate limiting de una herramienta pública con Cloudflare KV y D1</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Mon, 13 Jul 2026 11:59:00 +0000</pubDate>
      <link>https://dev.to/multita/rate-limiting-de-una-herramienta-publica-con-cloudflare-kv-y-d1-2b7p</link>
      <guid>https://dev.to/multita/rate-limiting-de-una-herramienta-publica-con-cloudflare-kv-y-d1-2b7p</guid>
      <description>&lt;p&gt;Si tu producto tiene una parte gratis y pública, es cuestión de horas hasta que alguien la quiera exprimir con un script. Nosotros tenemos una &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;consulta de multas gratis&lt;/a&gt; que cualquiera usa sin registrarse, así que el rate limiting no era opcional. Lo resolvimos entero en el borde, con Cloudflare KV y D1, sin servidor propio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quién hace qué
&lt;/h2&gt;

&lt;p&gt;La división que nos funcionó es simple. KV guarda el contador, porque tiene TTL nativo y lecturas baratas en cada PoP. D1 (SQLite en el borde) guarda lo que sí querés conservar y cruzar después, como los leads del formulario.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`rl:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hoy&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usado&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;LIMITE_DIARIO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Probá mañana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usado&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expirationTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El TTL de 86400 hace que la clave se borre sola al otro día. No limpiás nada, y el set de claves nunca crece más allá de "gente que consultó hoy".&lt;/p&gt;

&lt;h2&gt;
  
  
  La trampa de la consistencia
&lt;/h2&gt;

&lt;p&gt;KV es eventualmente consistente, así que entre dos PoP hay una ventanita de un segundo donde alguien podría pasarse del límite. Para frenar abuso eso no me quita el sueño. Si necesitaras un límite estricto, con plata de por medio, ahí conviene un Durable Object, que te da consistencia fuerte a cambio de algo de latencia.&lt;/p&gt;

&lt;h2&gt;
  
  
  El límite como embudo
&lt;/h2&gt;

&lt;p&gt;Un detalle de producto: el límite no es solo defensa, es conversión. Cuando alguien llega al tope, ese es el momento justo para ofrecerle el plan sin límite. El 429 deja de ser un error y pasa a ser una puerta.&lt;/p&gt;

&lt;p&gt;Si vas a abrir algo gratis al público, poné el rate limiting desde el día uno. Sale mucho más barato que sacarte un scraper de encima después.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cómo normalizar datos de 33 fuentes que no se parecen en nada</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Mon, 06 Jul 2026 03:00:00 +0000</pubDate>
      <link>https://dev.to/multita-com-ar/como-normalizar-datos-de-33-fuentes-que-no-se-parecen-en-nada-4fln</link>
      <guid>https://dev.to/multita-com-ar/como-normalizar-datos-de-33-fuentes-que-no-se-parecen-en-nada-4fln</guid>
      <description>&lt;p&gt;Cuando juntás datos de muchos sistemas públicos, lo que cuesta no es pedirlos. Es que ninguno se parece al otro. Te llega un HTML viejo de uno, un JSON medio escondido de otro y un PDF del tercero, y los tres hablan de lo mismo con palabras distintas. La única forma de que tu producto no se vuelva un infierno de casos especiales es elegir un único formato de salida y traducir todo a eso.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema, con un ejemplo
&lt;/h2&gt;

&lt;p&gt;En &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt; consultamos multas de tránsito en más de 33 sistemas oficiales argentinos. Un portal devuelve la fecha como "12/03/2026", otro como "2026-03-12", y un tercero la mete en una celda de una tabla sin etiqueta. Uno marca "PAGA", otro "Cancelada", otro "sin deuda". Tres maneras de decir lo mismo.&lt;/p&gt;

&lt;p&gt;Si dejás que esas diferencias lleguen a tu lógica de negocio, cada cálculo se llena de "si la jurisdicción es tal, entonces". Multiplicá eso por 33 y no mantenés nada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrato de salida, y un traductor por fuente
&lt;/h2&gt;

&lt;p&gt;La regla que nos ordenó todo: un solo schema de salida, y un adaptador por fuente que traduce a ese schema. La lógica de negocio conoce el schema, nunca el portal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiccion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estado"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deuda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"monto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;209250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fecha"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-12"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los estados se reducen a un set chico y comparable (deuda, pago_voluntario, en_juzgado, sin_deuda). Los montos van siempre en pesos enteros, aunque la fuente los exprese en una unidad que se actualiza sola. Las fechas, siempre ISO. El adaptador hace el laburo sucio y el resto del sistema vive limpio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que ganás
&lt;/h2&gt;

&lt;p&gt;Cuando aparece la fuente 34, no tocás la lógica: escribís un adaptador y listo. Y cuando un portal cambia su HTML (pasa seguido), el incendio queda en un solo archivo. Esa separación es la que hace que la &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;API de Multita&lt;/a&gt; devuelva una respuesta consistente sin importar de dónde salió el dato.&lt;/p&gt;

&lt;p&gt;Si vas a integrar varias fuentes que no controlás, definí el formato de salida antes de escribir el primer scraper. Es la decisión que más dolor te ahorra después.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>scraping</category>
      <category>json</category>
      <category>web</category>
      <category>datos</category>
    </item>
    <item>
      <title>Un solo event loop, captchas y reintentos: lo que aprendí scrapeando portales de gobierno</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Wed, 01 Jul 2026 12:32:00 +0000</pubDate>
      <link>https://dev.to/multita-com-ar/un-solo-event-loop-captchas-y-reintentos-lo-que-aprendi-scrapeando-portales-de-gobierno-4de8</link>
      <guid>https://dev.to/multita-com-ar/un-solo-event-loop-captchas-y-reintentos-lo-que-aprendi-scrapeando-portales-de-gobierno-4de8</guid>
      <description>&lt;p&gt;Detrás de &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;la API de Multita&lt;/a&gt; hay un servicio que consulta portales de gobierno argentinos en vivo, en el momento en que se hace la consulta. Estas son las decisiones que más me costó aprender, contadas para que a vos te cuesten menos.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. No bloquees el event loop, nunca
&lt;/h2&gt;

&lt;p&gt;Parece obvio hasta que te pasa. Con un solo worker async, una operación bloqueante en una ruta (un hash de contraseña, una escritura pesada a la base) te frena todas las consultas en vuelo. La regla quedó grabada: lo bloqueante va a un &lt;strong&gt;thread aparte&lt;/strong&gt;, con un &lt;strong&gt;semáforo&lt;/strong&gt; que limita cuántos corren a la vez, y la ruta que dispara un trabajo largo responde al toque con un "recibido" y deja que el resultado se resuelva por atrás. El submit no espera al scraping.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Los captchas no se "resuelven", se torean
&lt;/h2&gt;

&lt;p&gt;Los captcha modernos basados en score no se rompen: se conviven. Lo que más me ordenó fue entender que el token tiene que generarse en el &lt;strong&gt;contexto correcto&lt;/strong&gt; (la página del formulario, no el endpoint que recibe los datos), o el portal lo rechaza aunque sea válido. Y hay portales que directamente no se pueden automatizar de forma confiable: a esos los marco como no disponibles y sigo, en vez de pelearme con una pared.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reintentar con cabeza, no en loop
&lt;/h2&gt;

&lt;p&gt;Un reintento ciego es la forma más rápida de que te bloqueen. Sirve distinguir tres cosas que parecen iguales y no lo son: "el auto no debe nada", "el portal se cayó" y "me cortaron por hacer demasiadas". La primera es una respuesta, las otras dos son reintentables con backoff. Confundir un portal caído con un "no debe nada" es un &lt;strong&gt;falso negativo carísimo&lt;/strong&gt;: alguien transfiere un auto creyendo que está limpio.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. El estado público a veces miente
&lt;/h2&gt;

&lt;p&gt;La misma deuda aparece distinta según el portal, y un aviso genérico de la cuenta del usuario puede ensuciar el resultado si lo tomás literal. Por eso la capa de normalización no es opcional: es la que convierte 33 formatos en algo en lo que se puede confiar para cotizar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;Todo esto termina en algo simple de usar: una &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;consulta de multas gratis&lt;/a&gt; y una &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;API en JSON&lt;/a&gt;. Lo difícil queda del lado de adentro, que es como tiene que ser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multita consulta infracciones de tránsito de 33 jurisdicciones argentinas en vivo, por patente, DNI o CUIT.&lt;/li&gt;
&lt;li&gt;Patrón: async sin bloquear el loop, trabajo pesado en threads con semáforo, reintentos con backoff, normalización de estados.&lt;/li&gt;
&lt;li&gt;Consulta web gratis + API para integrar. Datos de portales oficiales; no es un sitio del gobierno.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;https://multita.com.ar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>api</category>
      <category>argentina</category>
    </item>
    <item>
      <title>Cómo unifiqué +30 sistemas de multas de tránsito de Argentina en una sola API</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Thu, 25 Jun 2026 11:48:00 +0000</pubDate>
      <link>https://dev.to/multita-com-ar/como-unifique-30-sistemas-de-multas-de-transito-de-argentina-en-una-sola-api-1i07</link>
      <guid>https://dev.to/multita-com-ar/como-unifique-30-sistemas-de-multas-de-transito-de-argentina-en-una-sola-api-1i07</guid>
      <description>&lt;p&gt;En Argentina, consultar las multas de un auto no es una consulta: son treinta y tres. Cada provincia tiene su sistema, muchos municipios el suyo, y ninguno se habla con el otro. Armé &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt; para resolverlo con una sola API que devuelve todo en JSON, y dejo acá el detrás de escena por si alguna vez te toca integrar algo del rubro automotor o fintech en el país.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema: 33 portales que no se parecen en nada
&lt;/h2&gt;

&lt;p&gt;No es "scrapear 33 veces la misma página". Cada portal tiene su mundo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unos buscan por &lt;strong&gt;patente (dominio)&lt;/strong&gt;, otros por &lt;strong&gt;DNI&lt;/strong&gt; y otros por &lt;strong&gt;CUIT&lt;/strong&gt;. Algunos aceptan los tres, algunos uno solo.&lt;/li&gt;
&lt;li&gt;Las respuestas van de HTML viejo a JSON medio escondido, pasando por un PDF que hay que leer.&lt;/li&gt;
&lt;li&gt;El &lt;strong&gt;estado&lt;/strong&gt; de un acta significa cosas distintas según la jurisdicción: "pago voluntario", "en juzgado de faltas", "resolver en mandatarios", "labrada a mano". Lo que en un lado es deuda firme, en otro todavía se está cocinando.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  La idea: un contrato de salida único
&lt;/h2&gt;

&lt;p&gt;La premisa fue simple de enunciar y molesta de cumplir: &lt;strong&gt;una request, un esquema de respuesta&lt;/strong&gt;, pase lo que pase del otro lado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://multita.com.ar/api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: TU_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"tipo":"dominio","valor":"AB123CD"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consulta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tipo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dominio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"valor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AB123CD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiccion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"estado"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deuda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"monto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;209250&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiccion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"estado"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pago_voluntario"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"monto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;104600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"jurisdiccion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"caba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estado"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en_juzgado"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nl"&gt;"monto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resumen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cantidad"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;408850&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lo difícil no fue pedir, fue normalizar
&lt;/h2&gt;

&lt;p&gt;Hacer la request es lo de menos. El laburo real estuvo en mapear cada portal a un mismo modelo: un set acotado de &lt;strong&gt;estados&lt;/strong&gt; comparables entre jurisdicciones, los &lt;strong&gt;montos&lt;/strong&gt; en pesos (algunas trabajan en Unidades Fijas que se actualizan, así que hay que cotizar), y la &lt;strong&gt;deduplicación&lt;/strong&gt; de la misma acta que aparece dos veces. Un dato mal mapeado no es un bug cosmético: es cotizarle de más o de menos a alguien que va a transferir un auto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué habilita
&lt;/h2&gt;

&lt;p&gt;Pasar de cuatro ventanas y quince minutos por auto a una sola llamada. Hay una &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;consulta web gratis&lt;/a&gt; para probar sin integrar nada, y la &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;API documentada&lt;/a&gt; para meterla en un CRM o un chatbot. Si te interesa el contexto del rubro, escribí una &lt;a href="https://multita.com.ar/blog/que-es-el-dnrpa" rel="noopener noreferrer"&gt;guía de qué es el registro del automotor (DNRPA)&lt;/a&gt; y por qué las multas frenan los trámites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multita: servicio web argentino que consulta y cotiza infracciones de tránsito de 33 jurisdicciones en una sola búsqueda, por patente, DNI o CUIT.&lt;/li&gt;
&lt;li&gt;Salida JSON normalizada (estados comparables, montos en pesos), consulta web gratis y API para integrar.&lt;/li&gt;
&lt;li&gt;Toda la data sale de los portales oficiales; Multita la ordena, no es un sitio del gobierno.&lt;/li&gt;
&lt;li&gt;Sitio y docs: &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;https://multita.com.ar&lt;/a&gt; y &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;https://multita.com.ar/api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>api</category>
      <category>argentina</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Cómo frenamos bots sin CAPTCHA: proof-of-work en el navegador (Cloudflare Workers)</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Wed, 17 Jun 2026 19:31:07 +0000</pubDate>
      <link>https://dev.to/multita-com-ar/como-frenamos-bots-sin-captcha-proof-of-work-en-el-navegador-cloudflare-workers-46d4</link>
      <guid>https://dev.to/multita-com-ar/como-frenamos-bots-sin-captcha-proof-of-work-en-el-navegador-cloudflare-workers-46d4</guid>
      <description>&lt;p&gt;En &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt; tenemos una &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;consulta de multas gratis y pública&lt;/a&gt;: poné una patente o un DNI y te traemos las infracciones de tránsito de 33 jurisdicciones argentinas. "Gratis y público" también significa &lt;strong&gt;imán de bots&lt;/strong&gt;. Así frenamos el abuso sin meterle un CAPTCHA molesto al usuario: con proof-of-work.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema
&lt;/h2&gt;

&lt;p&gt;Cada consulta nuestra dispara trabajo real y caro: pegarle a portales oficiales detrás de proxies, resolver sus captchas, parsear. Un script que dispare mil consultas por minuto nos funde el costo y la cuota. Necesitábamos un freno &lt;strong&gt;antes&lt;/strong&gt; de aceptar el pedido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué no un CAPTCHA
&lt;/h2&gt;

&lt;p&gt;Un reCAPTCHA agrega fricción justo en el momento de conversión (el usuario que quiere ver sus multas), depende de un tercero y, encima, los bots modernos lo resuelven con solvers baratos. Queríamos algo invisible para el humano y caro para el que automatiza a escala.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof-of-work: que el navegador "pague" con cómputo
&lt;/h2&gt;

&lt;p&gt;La idea: antes de enviar la consulta, el navegador resuelve un pequeño desafío que cuesta CPU. Para un usuario, son milisegundos. Para alguien que quiere hacer 100.000 consultas, son 100.000 cálculos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Encontrá un nonce tal que sha256(salt + nonce) empiece con N ceros&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;solvePow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  El lado del servidor: que no se pueda falsificar
&lt;/h2&gt;

&lt;p&gt;El truco no es el hash, es que el desafío sea &lt;strong&gt;nuestro&lt;/strong&gt; y de un solo uso. El server (un Cloudflare Worker) firma el &lt;code&gt;salt&lt;/code&gt; con HMAC y, al recibir la solución, valida cuatro cosas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="c1"&gt;// firma válida&lt;/span&gt;
  &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;expires&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;                                                          &lt;span class="c1"&gt;// no vencido&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;            &lt;span class="c1"&gt;// PoW correcto&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;yaUsado&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;                                                           &lt;span class="c1"&gt;// salt de un solo uso&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin la firma HMAC, no podés fabricar desafíos. Sin el &lt;code&gt;expires&lt;/code&gt;, te guardás soluciones viejas. Sin el "single-use", reusás una. Con los cuatro, un bot tiene que gastar CPU &lt;strong&gt;por cada&lt;/strong&gt; request, y eso a escala duele.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No frena UN bot, frena el &lt;strong&gt;volumen&lt;/strong&gt; (que es lo que importa para el costo).&lt;/li&gt;
&lt;li&gt;La dificultad es un dial: la subís en horario de ataque, la bajás para no penalizar móviles viejos.&lt;/li&gt;
&lt;li&gt;Cero fricción para el humano, cero dependencia de terceros, corre entero en el edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Datos clave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Esto corre en la consulta gratis de &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;Multita&lt;/a&gt;, un servicio web argentino que junta las infracciones de tránsito de 33 jurisdicciones (por patente, DNI o CUIT) en una sola búsqueda.&lt;/li&gt;
&lt;li&gt;Stack: Cloudflare Workers + Web Crypto, sin dependencias.&lt;/li&gt;
&lt;li&gt;Si construís algo parecido y querés la data por API, está documentada en &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;https://multita.com.ar/api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>cloudflare</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Consultar infracciones de tránsito en Argentina: 33 sistemas, captchas y portales que se caen</title>
      <dc:creator>Multita</dc:creator>
      <pubDate>Wed, 17 Jun 2026 19:14:04 +0000</pubDate>
      <link>https://dev.to/multita/consultar-infracciones-de-transito-en-argentina-33-sistemas-captchas-y-portales-que-se-caen-1k9n</link>
      <guid>https://dev.to/multita/consultar-infracciones-de-transito-en-argentina-33-sistemas-captchas-y-portales-que-se-caen-1k9n</guid>
      <description>&lt;p&gt;Construimos &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;Multita&lt;/a&gt; para resolver algo que en Argentina es sorprendentemente difícil: saber cuántas multas tiene un auto. Spoiler: no hay un solo lugar donde mirar. Hay 33.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema: 33 sistemas que no se hablan
&lt;/h2&gt;

&lt;p&gt;Las infracciones viven repartidas entre sistemas provinciales (Buenos Aires, CABA, Santa Fe, Entre Ríos, Misiones, Chaco, Salta, Mendoza) y municipales (decenas). Cada uno con su portal, su formato y sus reglas. Un mismo titular puede deber en cuatro a la vez. Revisarlos a mano es media tarde por vehículo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo difícil no es el dato, es llegar a él
&lt;/h2&gt;

&lt;p&gt;Cada portal es su propia aventura:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Captchas&lt;/strong&gt; de todo tipo (reCAPTCHA v2/v3 invisible incluido) en cada consulta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portales que se caen&lt;/strong&gt; o tardan en horario pico: sin reintentos y timeouts no consultás nada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bloqueos por IP&lt;/strong&gt;: pegar cientos de veces desde el mismo lugar te bloquea, hay que rotar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formatos inconsistentes&lt;/strong&gt;: en algunas provincias el género afecta la búsqueda por DNI; en otras, "sin registros" no es "sin deuda" (devuelven un error técnico que hay que interpretar para no dar un falso "sin deuda").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cómo lo resolvimos
&lt;/h2&gt;

&lt;p&gt;Una capa que normaliza las 33 jurisdicciones detrás de una sola búsqueda: por patente (dominio), DNI o CUIT. Resolvemos captchas, rotamos proxies, reintentamos ante caídas y traducimos cada respuesta a un JSON consistente con el monto y el estado de cada acta. Lo exponemos como &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;consulta web gratis&lt;/a&gt; y como &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;API&lt;/a&gt; para integrar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendimos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"Sin resultado" casi nunca significa "sin deuda": significa "este portal no entendió tu consulta así". Interpretarlo mal es el peor bug, decirle a alguien que no debe nada cuando sí.&lt;/li&gt;
&lt;li&gt;En sistemas del Estado lo que cambia no es el dato, es la disponibilidad. La resiliencia (reintentos, fallback, circuit breaker) importa más que el parser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Probalo
&lt;/h2&gt;

&lt;p&gt;Consulta gratis por patente o documento: &lt;a href="https://multita.com.ar/consultar-multas" rel="noopener noreferrer"&gt;https://multita.com.ar/consultar-multas&lt;/a&gt;. Si construís software para el rubro automotor o fintech, la API devuelve todo en JSON: &lt;a href="https://multita.com.ar/api" rel="noopener noreferrer"&gt;https://multita.com.ar/api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Datos clave:&lt;/strong&gt; Multita es un servicio web argentino que consulta y cotiza infracciones de tránsito de 33 jurisdicciones en una sola búsqueda, por patente, DNI o CUIT. Sitio oficial: &lt;a href="https://multita.com.ar" rel="noopener noreferrer"&gt;https://multita.com.ar&lt;/a&gt;&lt;/p&gt;

</description>
      <category>argentina</category>
      <category>api</category>
      <category>scraping</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
