<?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:  AppInitDev</title>
    <description>The latest articles on DEV Community by  AppInitDev (@appinitdev).</description>
    <link>https://dev.to/appinitdev</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%2F4110146%2F4fd7dd64-9382-46f1-ac58-341e1516997d.png</url>
      <title>DEV Community:  AppInitDev</title>
      <link>https://dev.to/appinitdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/appinitdev"/>
    <language>en</language>
    <item>
      <title>Cómo calcular masa molar con un parser pequeño y sin usar eval</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 20:00:11 +0000</pubDate>
      <link>https://dev.to/appinitdev/como-calcular-masa-molar-con-un-parser-pequeno-y-sin-usar-eval-202h</link>
      <guid>https://dev.to/appinitdev/como-calcular-masa-molar-con-un-parser-pequeno-y-sin-usar-eval-202h</guid>
      <description>&lt;h2&gt;
  
  
  Cómo calcular masa molar con un parser pequeño y sin usar eval
&lt;/h2&gt;

&lt;p&gt;Una fórmula química parece una cadena sencilla, pero contiene símbolos, cantidades y paréntesis. Convertirla directamente con &lt;code&gt;eval()&lt;/code&gt; sería incorrecto y peligroso; necesitamos una gramática mínima y una tabla de masas.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;ElementalHub: Tabla periódica&lt;/strong&gt; resume su función así: «Tabla periódica interactiva, calculadora de masa molar e isótopos sin conexión.» ElementalHub describe tabla periódica y calculadora de masa molar sin conexión. Este tutorial implementa &lt;strong&gt;un parser limitado y explícito&lt;/strong&gt; como ejercicio de programación, no el motor científico de la aplicación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Utiliza Python 3. Guarda el código como &lt;code&gt;masa_molar.py&lt;/code&gt; y ejecútalo. La tabla contiene solo cuatro elementos de ejemplo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;

&lt;span class="n"&gt;MASAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.008&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;O&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15.999&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12.011&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ca&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;40.078&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;masa_molar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formula&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[A-Z][a-z]?|[1-9][0-9]*|[()]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formula&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&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="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;formula&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fórmula inválida&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;grupo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;grupo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Paréntesis sin cerrar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;MASAS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MASAS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Elemento u operador no admitido: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;grupo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Paréntesis no balanceados&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;H2O&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CO2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Ca(OH)2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;masa_molar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;g/mol&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;El tokenizador distingue símbolos químicos de multiplicadores. La función recursiva procesa paréntesis y multiplica la contribución de cada grupo. El resultado usa &lt;code&gt;Decimal&lt;/code&gt;, lo que ayuda a controlar la representación de las masas proporcionadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Comprueba que &lt;code&gt;H2O&lt;/code&gt; da aproximadamente &lt;code&gt;18.015 g/mol&lt;/code&gt;, &lt;code&gt;CO2&lt;/code&gt; cerca de &lt;code&gt;44.009 g/mol&lt;/code&gt; y &lt;code&gt;Ca(OH)2&lt;/code&gt; cerca de &lt;code&gt;74.092 g/mol&lt;/code&gt;. Prueba un símbolo desconocido o un paréntesis sin cerrar para ver la validación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;El parser no admite cargas, hidratos con punto, coeficientes de reacción ni todos los elementos. Las masas usadas son redondeos de ejemplo; distintos isótopos y convenciones pueden cambiar el cálculo. No debe tratarse como referencia de laboratorio validada.&lt;/p&gt;

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

&lt;p&gt;Las herramientas científicas necesitan tanto un &lt;strong&gt;parser de entrada verificable&lt;/strong&gt; como un conjunto de datos con fuentes y unidades conocidas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;ElementalHub: Tabla periódica&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.table" rel="noopener noreferrer"&gt;ElementalHub: Tabla periódica&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://elementalhub.appinitdev.com/" rel="noopener noreferrer"&gt;https://elementalhub.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd4b89bv7fxoenzslnf81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd4b89bv7fxoenzslnf81.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://iupac.qmul.ac.uk/AtWt/" rel="noopener noreferrer"&gt;IUPAC: Standard atomic weights&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>chemistry</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Edición no destructiva de fotos: por qué los ajustes deben partir del original</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:58:44 +0000</pubDate>
      <link>https://dev.to/appinitdev/edicion-no-destructiva-de-fotos-por-que-los-ajustes-deben-partir-del-original-jcn</link>
      <guid>https://dev.to/appinitdev/edicion-no-destructiva-de-fotos-por-que-los-ajustes-deben-partir-del-original-jcn</guid>
      <description>&lt;h2&gt;
  
  
  Edición no destructiva de fotos: por qué los ajustes deben partir del original
&lt;/h2&gt;

&lt;p&gt;Aplicar filtros uno sobre otro y guardar sucesivamente puede acumular artefactos, especialmente si usamos formatos con pérdida. En un editor, la vista previa debería poder recomponerse desde la fuente y sus parámetros.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;CanvasLab AI: Editor de Fotos&lt;/strong&gt; resume su función así: «Retoca fotografías con IA, recorta fondos, dibuja y convierte imágenes.» CanvasLab AI incluye ajustes fotográficos, herramientas de dibujo y exportación según su ficha. Este ejemplo construye dos salidas &lt;strong&gt;independientes a partir de una misma imagen original&lt;/strong&gt;. Se utiliza Pillow para demostrar el principio, no para describir la implementación de CanvasLab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Instala Pillow (&lt;code&gt;python -m pip install Pillow&lt;/code&gt;), coloca una imagen &lt;code&gt;origen.jpg&lt;/code&gt; y ejecuta &lt;code&gt;edicion.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageOps&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;origen.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exif_transpose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Dos variantes derivadas de la misma fuente en memoria.
&lt;/span&gt;&lt;span class="n"&gt;contraste&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;contraste&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;variante_contraste.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PNG&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;variante_color.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PNG&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Para otro ajuste, modificaríamos los parámetros y volveríamos a partir
# de original, no de la imagen exportada anteriormente.
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dimensiones de origen:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;El objeto &lt;code&gt;original&lt;/code&gt; se mantiene sin cambios mientras calculamos variantes. El control de contraste no altera el punto de partida del ajuste de saturación. En una interfaz real podríamos guardar una estructura de parámetros y reconstruir la previsualización bajo demanda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Genera ambas imágenes y compara con el archivo original. Modifica &lt;code&gt;1.25&lt;/code&gt; por &lt;code&gt;1.10&lt;/code&gt;: solo cambia la variante de contraste. Haz una prueba acumulativa de 10 exportaciones JPEG para entender cómo un flujo destructivo puede degradar resultados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;No implementa capas, historial, algoritmos de IA, edición vectorial ni gestión avanzada de color. Reabrir originales grandes repetidamente también puede ser costoso; conviene usar cachés y previsualizaciones a resolución reducida.&lt;/p&gt;

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

&lt;p&gt;Una edición robusta suele separar &lt;strong&gt;fuente, operaciones y exportación&lt;/strong&gt;. Esa distinción facilita deshacer cambios y mantener una salida reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;CanvasLab AI: Editor de Fotos&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.canvas.premium" rel="noopener noreferrer"&gt;CanvasLab AI: Editor de Fotos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://canvas-premium.appinitdev.com/" rel="noopener noreferrer"&gt;https://canvas-premium.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa1yesods64y3ov0f2m13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa1yesods64y3ov0f2m13.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageEnhance.html" rel="noopener noreferrer"&gt;Pillow: ImageEnhance&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>unsplash</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Fechas y predicciones: calcular intervalos de calendario sin fingir precisión médica</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:57:25 +0000</pubDate>
      <link>https://dev.to/appinitdev/fechas-y-predicciones-calcular-intervalos-de-calendario-sin-fingir-precision-medica-3flo</link>
      <guid>https://dev.to/appinitdev/fechas-y-predicciones-calcular-intervalos-de-calendario-sin-fingir-precision-medica-3flo</guid>
      <description>&lt;h2&gt;
  
  
  Fechas y predicciones: calcular intervalos de calendario sin fingir precisión médica
&lt;/h2&gt;

&lt;p&gt;Las aplicaciones de registro personal trabajan con fechas, observaciones y proyecciones. Una estimación no debe presentarse como un hecho, especialmente cuando se relaciona con la salud.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;My pregnancy: Calendario&lt;/strong&gt; resume su función así: «Sigue tu ciclo menstrual, registra síntomas diarios y predice días fértiles.» My pregnancy describe registro de ciclos, síntomas y estimaciones de próximos periodos. Aquí veremos un cálculo &lt;strong&gt;puramente ilustrativo de intervalos entre fechas&lt;/strong&gt;, no un algoritmo médico ni una reproducción del código de la app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Necesitas Python 3. Las fechas del ejemplo son ficticias; ejecútalo como &lt;code&gt;intervalos.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt;

&lt;span class="n"&gt;inicios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;intervalos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inicios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inicios&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])]&lt;/span&gt;
&lt;span class="n"&gt;centro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervalos&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;proxima_estimada&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inicios&lt;/span&gt;&lt;span class="p"&gt;[&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="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;centro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Intervalos observados:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intervalos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mediana de días:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;centro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Estimación ilustrativa:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxima_estimada&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;Utilizamos objetos &lt;code&gt;date&lt;/code&gt; y diferencias en días, no cantidades aproximadas de segundos. La mediana evita que un solo intervalo atípico cambie tanto el centro como podría hacerlo un promedio. Aun así, una mediana de pocos ciclos no describe la variabilidad personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Agrega una fecha posterior y observa cómo cambian los intervalos. Prueba intervalos muy diferentes: verás que el cálculo sigue entregando un número, pero no por eso gana validez clínica.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No sirve para diagnosticar, determinar ovulación, buscar o evitar embarazos ni tomar decisiones médicas.&lt;/strong&gt; Los ciclos pueden variar por múltiples factores y los datos faltantes sesgan cualquier estimación. Un producto sanitario debe comunicar incertidumbre y proteger información sensible.&lt;/p&gt;

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

&lt;p&gt;La decisión más responsable no es elegir una fórmula «precisa» sin evidencia, sino &lt;strong&gt;mostrar claramente el carácter aproximado&lt;/strong&gt; de cualquier cálculo personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;My pregnancy: Calendario&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.pregnancy" rel="noopener noreferrer"&gt;My pregnancy: Calendario&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://pregnancy.appinitdev.com/" rel="noopener noreferrer"&gt;https://pregnancy.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsjhjnge41p62e72n7h21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsjhjnge41p62e72n7h21.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.python.org/3/library/datetime.html" rel="noopener noreferrer"&gt;Python: datetime&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>data</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cuadrícula para calcar sin deformar la imagen: coordenadas y escalado en Canvas</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:56:00 +0000</pubDate>
      <link>https://dev.to/appinitdev/cuadricula-para-calcar-sin-deformar-la-imagen-coordenadas-y-escalado-en-canvas-16pn</link>
      <guid>https://dev.to/appinitdev/cuadricula-para-calcar-sin-deformar-la-imagen-coordenadas-y-escalado-en-canvas-16pn</guid>
      <description>&lt;h2&gt;
  
  
  Cuadrícula para calcar sin deformar la imagen: coordenadas y escalado en Canvas
&lt;/h2&gt;

&lt;p&gt;Cuando una imagen se adapta a una pantalla, la cuadrícula de ayuda puede desalinearse si se estira la foto o se dibuja sobre todo el lienzo. El problema es geométrico: la imagen tiene una escala y un desplazamiento.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;TraceDraw: Calcar y Dibujar&lt;/strong&gt; resume su función así: «Aprende a dibujar y hacer bocetos calcando imágenes con la cámara y cuadrícula.» TraceDraw anuncia calco asistido con cámara y cuadrícula. En este laboratorio reproducimos solo &lt;strong&gt;la superposición de una cuadrícula sobre una imagen respetando su relación de aspecto&lt;/strong&gt;. El ejemplo web no pretende describir el motor Android de TraceDraw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Copia el siguiente contenido en &lt;code&gt;cuadricula.html&lt;/code&gt;, ábrelo en un navegador y carga una foto desde tu equipo. No se envía ningún archivo a un servidor.&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="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"es"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Cuadrícula de referencia&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"archivo"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"lienzo"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"max-width:100%;border:1px solid #888"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#lienzo&lt;/span&gt;&lt;span class="dl"&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#archivo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;scale&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;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strokeStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rgba(255, 0, 0, .7)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&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;col&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="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;++&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;xx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&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;row&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="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;++&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;yy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;El factor &lt;code&gt;Math.min(...)&lt;/code&gt; evita deformar la imagen. El desplazamiento &lt;code&gt;(x, y)&lt;/code&gt; la centra cuando aparecen márgenes. Las líneas se dibujan exclusivamente dentro del rectángulo visible de la foto, de manera que la cuadrícula coincide con el contenido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Prueba una imagen vertical y otra panorámica. Comprueba que ninguna se estira y que las 8 columnas por 8 filas mantienen su alineación dentro de la imagen. Cambia el valor &lt;code&gt;8&lt;/code&gt; por &lt;code&gt;12&lt;/code&gt; para variar la densidad de guías.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;Este Canvas no corrige perspectiva de una cámara real, inclinación de papel ni distorsión de lente. Para calco por cámara necesitaríamos además transformaciones de coordenadas, orientación del sensor y calibración.&lt;/p&gt;

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

&lt;p&gt;El secreto de una superposición estable es compartir &lt;strong&gt;el mismo espacio de coordenadas&lt;/strong&gt; entre imagen y guías, no ajustar ambos elementos por separado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;TraceDraw: Calcar y Dibujar&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.draw" rel="noopener noreferrer"&gt;TraceDraw: Calcar y Dibujar&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://draw.appinitdev.com/" rel="noopener noreferrer"&gt;https://draw.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8tuyjgd5rgurtoll14ss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8tuyjgd5rgurtoll14ss.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage" rel="noopener noreferrer"&gt;MDN: CanvasRenderingContext2D.drawImage&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>graphics</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Integridad no es cifrado: verificar archivos con SHA-256 en Python</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:54:38 +0000</pubDate>
      <link>https://dev.to/appinitdev/integridad-no-es-cifrado-verificar-archivos-con-sha-256-en-python-27mh</link>
      <guid>https://dev.to/appinitdev/integridad-no-es-cifrado-verificar-archivos-con-sha-256-en-python-27mh</guid>
      <description>&lt;h2&gt;
  
  
  Integridad no es cifrado: verificar archivos con SHA-256 en Python
&lt;/h2&gt;

&lt;p&gt;Las palabras «seguridad», «cifrado» e «integridad» suelen mezclarse. Sin embargo, una huella SHA-256 permite detectar cambios en un archivo; no oculta su contenido ni prueba por sí sola quién lo creó.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Encrypted: Cifrado de Archivos&lt;/strong&gt; resume su función así: «Cifrado local de archivos, limpieza de metadatos EXIF, escáner OCR y privacidad.» Encrypted anuncia cifrado local y comprobación de integridad. Para estudiar una de esas funciones construimos un &lt;strong&gt;verificador SHA-256 por bloques&lt;/strong&gt;, sin afirmar que sea el código usado por el producto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Necesitas Python 3. Ejecuta el script en un directorio de pruebas: crea dos archivos distintos y observa sus resúmenes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sha256&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;archivo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mensaje.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;archivo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Version original&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archivo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;archivo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Version alterada&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;modificado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archivo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Antes:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Después:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modificado&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;¿Son iguales?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;modificado&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;Leer por bloques evita cargar un archivo enorme en memoria. Al modificar un byte, el resumen normalmente cambia de forma significativa. Si guardamos la huella antes de una transferencia, podemos compararla con la del archivo recibido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Prueba con un archivo grande y observa que el uso de memoria se mantiene acotado. Modifica un carácter y vuelve a calcular la huella. Para una comprobación real, la huella esperada debe provenir de una fuente confiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SHA-256 no cifra&lt;/strong&gt; y tampoco autentica un origen si un atacante puede sustituir tanto el archivo como su huella. Para autenticidad se requieren firmas digitales o MAC, según el caso. Para confidencialidad se necesita cifrado autenticado y gestión de claves.&lt;/p&gt;

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

&lt;p&gt;Una buena herramienta de privacidad debe explicar claramente &lt;strong&gt;qué amenaza resuelve cada operación&lt;/strong&gt;. Una suma de verificación y una contraseña tienen objetivos diferentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Encrypted: Cifrado de Archivos&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.pyrotechnics.premium" rel="noopener noreferrer"&gt;Encrypted: Cifrado de Archivos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://encrypted.appinitdev.com/" rel="noopener noreferrer"&gt;https://encrypted.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudqrhawfjc8icuzt615f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudqrhawfjc8icuzt615f.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.python.org/3/library/hashlib.html" rel="noopener noreferrer"&gt;Python: hashlib&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>cryptography</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Paletas accesibles: calcular contraste WCAG desde colores HEX con JavaScript</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:51:04 +0000</pubDate>
      <link>https://dev.to/appinitdev/paletas-accesibles-calcular-contraste-wcag-desde-colores-hex-con-javascript-12ka</link>
      <guid>https://dev.to/appinitdev/paletas-accesibles-calcular-contraste-wcag-desde-colores-hex-con-javascript-12ka</guid>
      <description>&lt;h2&gt;
  
  
  Paletas accesibles: calcular contraste WCAG desde colores HEX con JavaScript
&lt;/h2&gt;

&lt;p&gt;Una combinación de colores puede ser atractiva y, al mismo tiempo, difícil de leer. Si diseñamos una app que crea paletas, la vista previa debería ayudarnos a evaluar el contraste, no solo la armonía cromática.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Color Palette Hub: Paletas&lt;/strong&gt; resume su función así: «Crea paletas cromáticas, combina tonos y edita imágenes en una sola app.» Color Palette Hub reúne herramientas de selección cromática y espacios de color. Este tutorial se concentra en &lt;strong&gt;la luminancia relativa sRGB y el contraste&lt;/strong&gt; como una comprobación mínima de accesibilidad. No es una descripción de su algoritmo interno.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Abre la consola de cualquier navegador moderno o utiliza Node.js. Copia el código sin dependencias.&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;function&lt;/span&gt; &lt;span class="nf"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hex&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;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^#/&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-fA-F&lt;/span&gt;&lt;span class="se"&gt;]{6}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEX de 6 dígitos&lt;/span&gt;&lt;span class="dl"&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;rgb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="mi"&gt;4&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;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&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="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;255&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;linear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rgb&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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.04045&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;12.92&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.055&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1.055&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.2126&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.7152&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.0722&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;linear&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="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foreground&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;background&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foreground&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;=&lt;/span&gt; &lt;span class="nf"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;background&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.05&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Negro/blanco:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#000000&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="s1"&gt;#FFFFFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Gris/blanco:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#777777&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="s1"&gt;#FFFFFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;La función transforma canales sRGB a luz lineal antes de calcular luminancia. El resultado del cociente va de &lt;code&gt;1:1&lt;/code&gt; a &lt;code&gt;21:1&lt;/code&gt;. Para texto normal, el criterio WCAG AA suele exigir al menos &lt;code&gt;4.5:1&lt;/code&gt;, con excepciones y requisitos distintos según el tipo de contenido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Primero verifica que negro sobre blanco produce &lt;code&gt;21.00&lt;/code&gt;. Cambia el color de texto entre &lt;code&gt;#777777&lt;/code&gt; y &lt;code&gt;#333333&lt;/code&gt; para observar cómo varía el contraste. Después prueba una de tus propias paletas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;La comprobación cubre colores sRGB opacos y no evalúa fondos complejos, gradientes, transparencias, tipo de letra ni todo el conjunto de WCAG. Tampoco reemplaza pruebas con usuarios ni la validación de controles e iconos.&lt;/p&gt;

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

&lt;p&gt;Una paleta útil debe considerar &lt;strong&gt;el contexto de uso del color&lt;/strong&gt;. El contraste medido es una señal objetiva, no una garantía absoluta de buena experiencia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Color Palette Hub: Paletas&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.color.premium" rel="noopener noreferrer"&gt;Color Palette Hub: Paletas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://color-premium.appinitdev.com/" rel="noopener noreferrer"&gt;https://color-premium.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F54h8e1y26ofdjfmj9glv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F54h8e1y26ofdjfmj9glv.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum" rel="noopener noreferrer"&gt;W3C: Understanding Contrast (Minimum)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>a11y</category>
      <category>css</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Extraer un fotograma de vídeo en Android: tiempo solicitado frente a frame real</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:49:16 +0000</pubDate>
      <link>https://dev.to/appinitdev/extraer-un-fotograma-de-video-en-android-tiempo-solicitado-frente-a-frame-real-4e49</link>
      <guid>https://dev.to/appinitdev/extraer-un-fotograma-de-video-en-android-tiempo-solicitado-frente-a-frame-real-4e49</guid>
      <description>&lt;h2&gt;
  
  
  Extraer un fotograma de vídeo en Android: tiempo solicitado frente a frame real
&lt;/h2&gt;

&lt;p&gt;Cuando un usuario selecciona «2,5 segundos» en un vídeo, el decodificador no tiene por qué devolver exactamente la misma imagen en todos los dispositivos. El contenedor, los keyframes y el códec afectan a la búsqueda temporal.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Video to Photo: Extraer Fotos&lt;/strong&gt; resume su función así: «Extrae fotos de tus vídeos: fotogramas individuales o en serie.» Video to Photo ofrece extracción manual y por intervalos de fotogramas según Google Play. En este ejemplo estudiaremos &lt;code&gt;MediaMetadataRetriever&lt;/code&gt; y la decisión entre &lt;strong&gt;fotograma cercano&lt;/strong&gt; y &lt;strong&gt;fotograma de sincronización&lt;/strong&gt;; no asumimos que la app use esta API internamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;En un proyecto Android con Kotlin, selecciona un vídeo local usando el selector del sistema y pasa su URI a la función. El trabajo de decodificación debe realizarse fuera del hilo principal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.Context&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.graphics.Bitmap&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.media.MediaMetadataRetriever&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.net.Uri&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;frameAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Bitmap&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retriever&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaMetadataRetriever&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFrameAtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;millis&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// la API recibe microsegundos&lt;/span&gt;
            &lt;span class="nc"&gt;MediaMetadataRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OPTION_CLOSEST&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&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;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;La opción &lt;code&gt;OPTION_CLOSEST&lt;/code&gt; intenta seleccionar un fotograma cercano al instante indicado. Otra opción, &lt;code&gt;OPTION_CLOSEST_SYNC&lt;/code&gt;, se limita a fotogramas de sincronización cercanos y puede ser más rápida, pero no siempre produce la imagen deseada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Prueba con un clip corto y extrae imágenes en &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;500&lt;/code&gt; y &lt;code&gt;2500&lt;/code&gt; milisegundos. Compara el resultado visual con la línea de tiempo de un reproductor. Repite con un vídeo de otro códec para observar diferencias de comportamiento.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;Este código no exporta la imagen ni procesa lotes; tampoco promete exactitud fotograma a fotograma. Algunos formatos fallan al decodificar y un bitmap completo puede consumir mucha memoria. Para lotes largos hay que gestionar recursos y cancelación.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Milisegundos no equivalen a índices de frame&lt;/strong&gt;. Una interfaz de selección precisa debe explicar qué devuelve su ruta de decodificación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Video to Photo: Extraer Fotos&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.capture.free" rel="noopener noreferrer"&gt;Video to Photo: Extraer Fotos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://capture-premium.appinitdev.com/" rel="noopener noreferrer"&gt;https://capture-premium.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbykpufzlq3my2ezsuj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbykpufzlq3my2ezsuj7.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/reference/android/media/MediaMetadataRetriever" rel="noopener noreferrer"&gt;Android: MediaMetadataRetriever&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>video</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Notas offline con SQLite: guardado idempotente y recuperación tras reiniciar</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:46:54 +0000</pubDate>
      <link>https://dev.to/appinitdev/notas-offline-con-sqlite-guardado-idempotente-y-recuperacion-tras-reiniciar-4j85</link>
      <guid>https://dev.to/appinitdev/notas-offline-con-sqlite-guardado-idempotente-y-recuperacion-tras-reiniciar-4j85</guid>
      <description>&lt;h2&gt;
  
  
  Notas offline con SQLite: guardado idempotente y recuperación tras reiniciar
&lt;/h2&gt;

&lt;p&gt;Una nota es útil si sobrevive al cierre de la aplicación. Antes de agregar colores, tareas y recordatorios, conviene responder una pregunta: ¿qué ocurre si el proceso se destruye después de escribir?&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Notepad: Bloc de notas tareas&lt;/strong&gt; resume su función así: «Apunta ideas, crea listas de tareas y organiza tus recordatorios diarios.» Notepad presenta notas, listas y recordatorios. Para ilustrar el almacenamiento local, construiremos una &lt;strong&gt;nota con actualización idempotente&lt;/strong&gt; en SQLite. El esquema es didáctico, no una declaración sobre la base de datos usada por Notepad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Necesitas Python 3. Guarda este archivo como &lt;code&gt;notas.py&lt;/code&gt;; al ejecutarlo se creará &lt;code&gt;notas_demo.sqlite3&lt;/code&gt; en la carpeta actual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;notas_demo.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE IF NOT EXISTS notas (
        id INTEGER PRIMARY KEY,
        contenido TEXT NOT NULL,
        modificado TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
    )&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO notas (id, contenido) VALUES (?, ?)
        ON CONFLICT(id) DO UPDATE SET
            contenido=excluded.contenido,
            modificado=CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Comprar fruta y revisar tareas&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;notas_demo.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SELECT contenido FROM notas WHERE id=?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,)).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Nota recuperada:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nota&lt;/span&gt;&lt;span class="p"&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;if&lt;/span&gt; &lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;no existe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;La instrucción &lt;code&gt;ON CONFLICT(id) DO UPDATE&lt;/code&gt; evita crear duplicados cuando se guarda repetidamente la misma nota con un identificador conocido. El bloque &lt;code&gt;with&lt;/code&gt; confirma los cambios al salir sin errores, y la segunda conexión comprueba la persistencia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Ejecuta dos veces: el contenido debe seguir siendo una sola nota. Cambia el texto de la instrucción &lt;code&gt;INSERT&lt;/code&gt; y ejecuta de nuevo: se reemplaza el contenido de la nota &lt;code&gt;1&lt;/code&gt;. Abre el archivo SQLite con un visor si quieres comprobar las filas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;No hay cifrado, búsquedas, adjuntos, conflictos entre dispositivos, backups ni gestión de notas borradas. La marca &lt;code&gt;CURRENT_TIMESTAMP&lt;/code&gt; usa una precisión limitada; para sincronización suelen necesitarse identificadores estables, revisiones y reglas explícitas.&lt;/p&gt;

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

&lt;p&gt;Persistir no es simplemente «guardar una cadena»: es &lt;strong&gt;definir identidad, consistencia y recuperación&lt;/strong&gt; después de que la interfaz desaparece.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Notepad: Bloc de notas tareas&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.notes.premium" rel="noopener noreferrer"&gt;Notepad: Bloc de notas tareas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://notes-premium.appinitdev.com/" rel="noopener noreferrer"&gt;https://notes-premium.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8wi3atn84e5ekyxwoeku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8wi3atn84e5ekyxwoeku.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.sqlite.org/lang_UPSERT.html" rel="noopener noreferrer"&gt;SQLite: UPSERT&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>sqlite</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Método de bisección con Python: convergencia y condiciones que no debes omitir</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:45:25 +0000</pubDate>
      <link>https://dev.to/appinitdev/metodo-de-biseccion-con-python-convergencia-y-condiciones-que-no-debes-omitir-jo5</link>
      <guid>https://dev.to/appinitdev/metodo-de-biseccion-con-python-convergencia-y-condiciones-que-no-debes-omitir-jo5</guid>
      <description>&lt;h2&gt;
  
  
  Método de bisección con Python: convergencia y condiciones que no debes omitir
&lt;/h2&gt;

&lt;p&gt;Un método numérico no es únicamente una fórmula. También necesita una condición de entrada, un criterio de parada y una forma de avisar cuando no puede ofrecer una solución fiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Numerical Methods: Cálculo&lt;/strong&gt; resume su función así: «Resuelve raíces, interpolación y mínimos cuadrados con iteraciones paso a paso» Numerical Methods incluye búsqueda de raíces y visualización de iteraciones según su descripción pública. Vamos a reproducir &lt;strong&gt;bisección&lt;/strong&gt; para una ecuación continua, con validación del intervalo y tolerancia explícita. Este ejemplo no es código de la app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Necesitas Python 3. Guarda &lt;code&gt;biseccion.py&lt;/code&gt; y ejecútalo sin dependencias externas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;biseccion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tolerancia&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fa&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;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fb&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;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fb&lt;/span&gt; &lt;span class="o"&gt;&amp;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;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;El intervalo no tiene cambio de signo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;paso&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&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="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;fm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;paso&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: x=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, f(x)=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tolerancia&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tolerancia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fm&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fm&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fm&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No convergió dentro del límite&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;raiz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;biseccion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Raíz aproximada:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raiz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;Cada paso conserva un intervalo con cambio de signo. La implementación muestra los valores de la función y permite ver cómo el ancho del intervalo disminuye. Bisección es robusta bajo sus hipótesis, aunque puede converger más despacio que otros métodos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;El resultado debe aproximarse a &lt;code&gt;1.52138&lt;/code&gt;. Cambia el intervalo por &lt;code&gt;[3, 4]&lt;/code&gt; para observar el error de validación. Reduce &lt;code&gt;max_iter&lt;/code&gt; a &lt;code&gt;2&lt;/code&gt; y verifica que el algoritmo no finge haber encontrado una respuesta exacta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;El método exige continuidad en el intervalo y un cambio de signo apropiado; existen raíces sin cambio de signo que esta verificación no detecta. No incorpora análisis de error de redondeo ni métodos para funciones discontinuas.&lt;/p&gt;

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

&lt;p&gt;Un programa educativo debe hacer visibles &lt;strong&gt;las hipótesis y los criterios de parada&lt;/strong&gt;. Una tabla de iteraciones puede enseñar más que un número final.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Numerical Methods: Cálculo&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.methods.premium" rel="noopener noreferrer"&gt;Numerical Methods: Cálculo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://methods-premium.appinitdev.com/" rel="noopener noreferrer"&gt;https://methods-premium.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdslx9uqbp4f0uayjt9yz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdslx9uqbp4f0uayjt9yz.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root_scalar.html" rel="noopener noreferrer"&gt;SciPy: root_scalar&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>mathematics</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Fotos y privacidad: cómo generar una copia sin EXIF antes de compartir</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:43:56 +0000</pubDate>
      <link>https://dev.to/appinitdev/fotos-y-privacidad-como-generar-una-copia-sin-exif-antes-de-compartir-2kbj</link>
      <guid>https://dev.to/appinitdev/fotos-y-privacidad-como-generar-una-copia-sin-exif-antes-de-compartir-2kbj</guid>
      <description>&lt;h2&gt;
  
  
  Fotos y privacidad: cómo generar una copia sin EXIF antes de compartir
&lt;/h2&gt;

&lt;p&gt;Una fotografía puede revelar más que su contenido visual. La información EXIF puede contener la hora, el dispositivo y, en algunos casos, coordenadas GPS. Compartir una imagen no debería implicar compartir metadatos por accidente.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Gallery: Álbum de Fotos Seguro&lt;/strong&gt; resume su función así: «Galería de fotos con caja fuerte privada para álbumes, imágenes y vídeos.» Gallery presenta una galería con opciones de privacidad y eliminación de EXIF en su ficha pública. Este laboratorio explica la idea de &lt;strong&gt;crear una copia nueva sin reutilizar los metadatos del original&lt;/strong&gt;. No supone que la app utilice Pillow ni este procedimiento.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Instala Pillow con &lt;code&gt;python -m pip install Pillow&lt;/code&gt;. Coloca una fotografía de prueba llamada &lt;code&gt;origen.jpg&lt;/code&gt; junto al script. Utiliza un archivo de prueba, no una foto sensible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageOps&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;origen.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;foto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Aplica la orientación EXIF a los píxeles antes de retirarla.
&lt;/span&gt;    &lt;span class="n"&gt;orientada&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exif_transpose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foto&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Lienzo nuevo: no copiamos el diccionario EXIF del origen.
&lt;/span&gt;    &lt;span class="n"&gt;salida&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orientada&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;salida&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orientada&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;salida&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;para_compartir.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PNG&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;para_compartir.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;verificacion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EXIF de salida:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verificacion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getexif&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;La copia se reconstruye a partir de píxeles sin pasar &lt;code&gt;exif=&lt;/code&gt; a &lt;code&gt;save&lt;/code&gt;. Usamos PNG por simplicidad y para evitar pérdida adicional por JPEG. La rotación se aplica antes porque, de otro modo, una foto tomada en vertical podría aparecer girada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Inspecciona ambos archivos con un visor de metadatos y comprueba si la copia tiene coordenadas. Observa también el tamaño final: PNG puede ser considerablemente más grande que JPEG para fotografías.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;Eliminar EXIF &lt;strong&gt;no anonimiza el contenido visual&lt;/strong&gt;: rostros, matrículas, reflejos o información dibujada en la imagen permanecen visibles. El ejemplo convierte a RGB, por lo que no conserva transparencia ni todos los perfiles de color.&lt;/p&gt;

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

&lt;p&gt;La privacidad de una galería depende tanto de permisos y almacenamiento como del &lt;strong&gt;contenido que se entrega al compartir&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Gallery: Álbum de Fotos Seguro&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.gallery" rel="noopener noreferrer"&gt;Gallery: Álbum de Fotos Seguro&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://gallery.appinitdev.com/" rel="noopener noreferrer"&gt;https://gallery.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F11wtqiqdkt3dl8uh3voz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F11wtqiqdkt3dl8uh3voz.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.exif_transpose" rel="noopener noreferrer"&gt;Pillow: ExifTranspose&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>privacy</category>
      <category>android</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Música local en Android: por qué MediaStore y Media3 resuelven problemas diferentes</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:41:35 +0000</pubDate>
      <link>https://dev.to/appinitdev/musica-local-en-android-por-que-mediastore-y-media3-resuelven-problemas-diferentes-3m91</link>
      <guid>https://dev.to/appinitdev/musica-local-en-android-por-que-mediastore-y-media3-resuelven-problemas-diferentes-3m91</guid>
      <description>&lt;h2&gt;
  
  
  Música local en Android: por qué MediaStore y Media3 resuelven problemas diferentes
&lt;/h2&gt;

&lt;p&gt;Los usuarios que guardan colecciones MP3 o FLAC necesitan descubrir archivos y reproducirlos sin exigir una cuenta o una conexión de red. Esas tareas no deberían tratarse como si fueran la misma capa del sistema.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;Amplify: Reproductor Hi-Res&lt;/strong&gt; resume su función así: «Reproductor de música sin conexión con ecualizador de 10 bandas y FLAC.» Amplify anuncia reproducción offline, FLAC y ecualización de diez bandas. En este ejemplo aislamos &lt;strong&gt;la obtención de una URI de audio con MediaStore&lt;/strong&gt;; después esa URI puede consumirse con Media3. No se afirma que Amplify use internamente esas bibliotecas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;En un proyecto Kotlin Android, declara el permiso de lectura de audio correspondiente a la versión del sistema (&lt;code&gt;READ_MEDIA_AUDIO&lt;/code&gt; en Android 13+) y solicítalo en ejecución antes de hacer la consulta. La búsqueda debe ejecutarse fuera del hilo principal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.ContentUris&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.Context&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.net.Uri&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.os.Build&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.provider.MediaStore&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;audioUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;base&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="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SDK_INT&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MediaStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContentUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VOLUME_EXTERNAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MediaStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXTERNAL_CONTENT_URI&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;projection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentResolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;idIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColumnIndexOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ID&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="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveToNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nc"&gt;ContentUris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withAppendedId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idIndex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Después, en una Activity con permiso y Media3 configurado:&lt;/span&gt;
&lt;span class="c1"&gt;// val player = ExoPlayer.Builder(this).build()&lt;/span&gt;
&lt;span class="c1"&gt;// player.setMediaItem(MediaItem.fromUri(audioUris(this).first()))&lt;/span&gt;
&lt;span class="c1"&gt;// player.prepare()&lt;/span&gt;
&lt;span class="c1"&gt;// player.play()&lt;/span&gt;
&lt;span class="c1"&gt;// player.release() cuando termine su ciclo de vida.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;La lista almacena identificadores de contenido (&lt;code&gt;content://&lt;/code&gt;), no rutas absolutas. En Android 10+ usamos la colección de volúmenes externos compartidos para cubrir más de una ubicación cuando el sistema las indexa. Esto facilita trabajar con el modelo de almacenamiento moderno. El reproductor debe manejar la eliminación de archivos y las URI que dejan de estar disponibles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Copia un MP3 y un FLAC de prueba al dispositivo, comprueba su indexación, concede permiso y compara las URI detectadas. Activa modo avión y valida que el acceso local no necesita Internet. Evita llamar &lt;code&gt;first()&lt;/code&gt; sin comprobar que la lista contiene elementos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;No se implementa cola, reproducción en segundo plano, etiquetas, procesamiento DSP ni verificación bit-perfect. El hecho de poder decodificar FLAC tampoco demuestra una salida de audio Hi-Res de extremo a extremo.&lt;/p&gt;

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

&lt;p&gt;Separar &lt;strong&gt;descubrimiento multimedia&lt;/strong&gt;, &lt;strong&gt;motor de reproducción&lt;/strong&gt; y &lt;strong&gt;procesamiento de audio&lt;/strong&gt; simplifica las pruebas y evita atribuir capacidades de hardware al formato del archivo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;Amplify: Reproductor Hi-Res&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.musicpro" rel="noopener noreferrer"&gt;Amplify: Reproductor Hi-Res&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://musicpro.appinitdev.com/" rel="noopener noreferrer"&gt;https://musicpro.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3fnwseuivinawxs2ni9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3fnwseuivinawxs2ni9.png" alt="Image APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/training/data-storage/shared/media" rel="noopener noreferrer"&gt;MediaStore&lt;/a&gt; · &lt;a href="https://developer.android.com/media/media3/exoplayer/supported-formats" rel="noopener noreferrer"&gt;Media3: formatos&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>socialmedia</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cómo evitar vender inventario inexistente: actualización condicional en SQLite</title>
      <dc:creator> AppInitDev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:38:34 +0000</pubDate>
      <link>https://dev.to/appinitdev/como-evitar-vender-inventario-inexistente-actualizacion-condicional-en-sqlite-cpi</link>
      <guid>https://dev.to/appinitdev/como-evitar-vender-inventario-inexistente-actualizacion-condicional-en-sqlite-cpi</guid>
      <description>&lt;h2&gt;
  
  
  Cómo evitar vender inventario inexistente: actualización condicional en SQLite
&lt;/h2&gt;

&lt;p&gt;Una venta debería descontar existencias solo si queda cantidad suficiente. El clásico patrón «leer stock, restar y guardar» puede fallar cuando dos operaciones se ejecutan casi a la vez.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema y el alcance del ejemplo
&lt;/h2&gt;

&lt;p&gt;La ficha de &lt;strong&gt;POSManager: TPV y Caja Ventas&lt;/strong&gt; resume su función así: «TPV y caja para comercios: controla ventas, stock y tickets desde el móvil.» POSManager describe cobros, ventas, tickets y control de stock. Para estudiar una decisión de diseño vinculada al inventario, usamos una &lt;strong&gt;actualización SQL condicionada dentro de una transacción&lt;/strong&gt;. No estamos describiendo la base de datos real del producto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo reproducir la idea
&lt;/h2&gt;

&lt;p&gt;Puedes ejecutarlo con Python 3, que incluye SQLite. Guarda el archivo como &lt;code&gt;venta.py&lt;/code&gt; y ejecútalo. La base está en memoria para que cada prueba comience desde cero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:memory:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE productos(id INTEGER PRIMARY KEY, stock INTEGER NOT NULL)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE ventas(producto INTEGER, cantidad INTEGER)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO productos VALUES(1, 3)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;producto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cantidad&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cantidad&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantidad inválida&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# transacción y rollback automático ante error
&lt;/span&gt;        &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE productos SET stock=stock-? WHERE id=? AND stock&amp;gt;=?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cantidad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;producto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cantidad&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowcount&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stock insuficiente o producto inexistente&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO ventas VALUES(?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;producto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cantidad&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;vender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stock restante:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT stock FROM productos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&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;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;vender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Venta rechazada:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Qué demuestra el código
&lt;/h2&gt;

&lt;p&gt;La condición &lt;code&gt;stock &amp;gt;= ?&lt;/code&gt; se verifica en la misma instrucción que descuenta inventario. Si ninguna fila fue modificada, la venta no se inserta. La transacción relaciona los dos efectos: movimiento de stock y registro de venta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pruebas recomendadas
&lt;/h2&gt;

&lt;p&gt;Deberías obtener stock &lt;code&gt;1&lt;/code&gt; tras la primera venta y rechazo de la segunda. Prueba vender &lt;code&gt;0&lt;/code&gt; unidades y un identificador desconocido. Comprueba que la tabla de ventas contiene únicamente una fila.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites que conviene reconocer
&lt;/h2&gt;

&lt;p&gt;La prueba no modela devoluciones, cobros, sincronización offline, precios, impuestos ni múltiples dispositivos con inventarios divergentes. En un sistema distribuido la transacción local no resuelve por sí sola conflictos entre terminales.&lt;/p&gt;

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

&lt;p&gt;El stock es una &lt;strong&gt;invariante de negocio&lt;/strong&gt;; conviene protegerla en la operación de escritura, no solo mediante validaciones en la interfaz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto y enlaces del autor
&lt;/h2&gt;

&lt;p&gt;El tema se relaciona con las funciones declaradas públicamente de &lt;strong&gt;POSManager: TPV y Caja Ventas&lt;/strong&gt;, una aplicación Android de AppInitDev. &lt;strong&gt;El código anterior es un ejemplo independiente&lt;/strong&gt;: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.appinitdev.posmanager" rel="noopener noreferrer"&gt;POSManager: TPV y Caja Ventas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Página de la aplicación:&lt;/strong&gt; &lt;a href="https://posmanager.appinitdev.com/" rel="noopener noreferrer"&gt;https://posmanager.appinitdev.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitio del desarrollador:&lt;/strong&gt; &lt;a href="https://appinitdev.com/" rel="noopener noreferrer"&gt;AppInitDev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxu1ysf9isrs9szokz62d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxu1ysf9isrs9szokz62d.png" alt="APPS" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencia técnica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.sqlite.org/lang_transaction.html" rel="noopener noreferrer"&gt;SQLite: transacciones&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>python</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
