<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3279603830404251843</id><updated>2026-10-20T09:00:00.120-05:00</updated><category term="Errores y soluciones"/><category term="Linux"/><category term="Java"/><category term="Windows"/><category term="Centos"/><category term="Alfresco"/><category term="EJB"/><category term="Git"/><category term="JavaScript"/><category term="DOTCMIS"/><category term="DevOps"/><category term="JBoss"/><category term="Python"/><category term="TypeScript"/><category term="App Web"/><category term="Bases de datos"/><category term="CMIS"/><category term="CMIS - Workbench"/><category term="Codificación"/><category term="Desarrollo de Software"/><category term="Eclipse"/><category term="Errores"/><category term="Generación de código"/><category term="Innovación Tecnológica"/><category term="Inteligencia Artificial"/><category term="Next.js"/><category term="Node.js"/><category term="Postgresql"/><category term="Prisma"/><category term="Programación"/><category term="Tecnología"/><category term="Ubuntu"/><category term="recomendaciones personalizadas"/><category term="AI"/><category term="AI entretenimiento"/><category term="AI hogar"/><category term="Alexa"/><category term="Angular 11"/><category term="Aprendizaje Automático"/><category term="Aprendizaje Online"/><category term="Asistentes AI"/><category term="Asistentes Inteligentes"/><category term="Automatización del Hogar"/><category term="Ayuda digital"/><category term="CMS"/><category term="CSS"/><category term="Chatbots"/><category term="Chrome"/><category term="Cirugía"/><category term="Comparación"/><category term="Compras Online"/><category term="Comunicación"/><category term="Comunidad"/><category term="Control de Versiones"/><category term="Dart"/><category term="Desarrollo Multiplataforma"/><category term="Desarrollo de Aplicaciones Móviles"/><category term="Dialogflow"/><category term="Disney+"/><category term="Dispositivos Conectados"/><category term="Docker"/><category term="Drupal"/><category term="Drupal 8"/><category term="Educación"/><category term="Electrodomésticos Inteligentes"/><category term="Energías Renovables"/><category term="Experiencia de Usuario IA"/><category term="Facilidad de Uso"/><category term="Flutter"/><category term="Futuro"/><category term="Futuro AI"/><category term="GStreamer"/><category term="Google Assistant"/><category term="Herramientas de IA"/><category term="Hogar Inteligente"/><category term="Hulu"/><category term="IA cotidiana"/><category term="IA en Retail"/><category term="IA en la vida diaria"/><category term="IA salud bienestar"/><category term="Implementación de IA"/><category term="Información"/><category term="Innovación"/><category term="Inteligencia Artificial en Comercio"/><category term="Log4j"/><category term="Medio Ambiente"/><category term="Netflix"/><category term="Pidgin"/><category term="PowerShell"/><category term="React"/><category term="React Native"/><category term="Rendimiento"/><category term="Rhythmbox"/><category term="SLF4j"/><category term="Salud"/><category term="Salud AI"/><category term="Servicio al Cliente"/><category term="Siri"/><category term="Sistema de Recomendación"/><category term="Soporte"/><category term="Spirng Roo"/><category term="Subversion"/><category term="Tecnología AI"/><category term="Tecnología Doméstica"/><category term="Tecnología Verde"/><category term="Tecnología de Compras"/><category term="Tecnología en el Aula"/><category term="Telemedicina"/><category term="Tendencias"/><category term="Tendencias E-Commerce"/><category term="TensorFlow"/><category term="Transformación Digital"/><category term="Tutoriales Técnicos"/><category term="Vida Cotidiana"/><category term="Vida moderna"/><category term="WebService"/><category term="algoritmos de IA"/><category term="apps fitness inteligentes"/><category term="asistentes salud digitales"/><category term="binge-watching"/><category term="colaboración en equipo"/><category term="desarrollo bajo código"/><category term="desarrollo web"/><category term="diseño de interfaz de usuario"/><category term="eficiencia en codificación"/><category term="elecciones de entretenimiento"/><category term="frontend"/><category term="inteligencia artificial cuidado personal."/><category term="monitor sueño IA"/><category term="programación fácil"/><category term="recordatorios hidratación IA"/><category term="series de ciencia ficción"/><category term="servicios Angular"/><category term="software simplificado"/><category term="streaming"/><category term="tecnología bienestar personalizado"/><category term="tendencias de visualización"/><category term="tutorial Angular"/><title type='text'>Tutoriales Técnicos</title><subtitle type='html'>¡Explora y ríe con Tutoriales Técnicos! Aquí, desenredamos el desarrollo de software con guías amenas y te mantenemos al tanto de lo último en tecnología. Aprende, diviértete y lleva tus habilidades digitales al próximo nivel. ¡Tu aventura tecnológica comienza aquí!</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>68</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-3132231730459009154</id><published>2026-10-20T09:00:00.000-05:00</published><updated>2026-10-20T09:00:00.115-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Bases de datos"/><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>sqlite3.OperationalError: database is locked: solución</title><content type='html'>&lt;p&gt;Varios procesos escriben en el mismo archivo de SQLite (por ejemplo, un ciclo de ingesta continuo y trabajos de reprocesado o de escaneo) y alguno falla con:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Traceback (most recent call last):
  File &quot;ingesta.py&quot;, line 42, in guardar
    con.execute(&quot;INSERT INTO eventos(valor) VALUES (?)&quot;, (valor,))
sqlite3.OperationalError: database is locked&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lo peor es el efecto silencioso: un trabajo lanzado a mano y olvidado puede competir con el ciclo programado y dejar la canalización parada durante horas, sin más rastro que este error en un registro. Este artículo explica por qué ocurre, qué configurar en SQLite y cómo encontrar al proceso que estorba.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;SQLite admite un solo escritor a la vez. La documentación de los códigos de resultado lo describe así: si el proceso A está en mitad de una transacción de escritura y el proceso B intenta iniciar otra, B recibe &lt;code&gt;SQLITE_BUSY&lt;/code&gt; y debe esperar a que A termine. El módulo &lt;code&gt;sqlite3&lt;/code&gt; de Python lo muestra como &lt;code&gt;OperationalError: database is locked&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;En el modo de diario por defecto (rollback journal), además, lectores y escritores se estorban: varios procesos pueden leer a la vez (bloqueo &lt;code&gt;SHARED&lt;/code&gt;), pero para confirmar una escritura hace falta un bloqueo &lt;code&gt;EXCLUSIVE&lt;/code&gt;, con el que no puede coexistir ningún otro bloqueo. Un escritor puede tener que esperar a que terminen los lectores, y mientras confirma bloquea las lecturas nuevas.&lt;/p&gt;
&lt;p&gt;La espera depende de dos cosas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;SQLite, por defecto, no tiene ningún manejador de ocupado (el valor por defecto es &lt;code&gt;NULL&lt;/code&gt;), así que devolvería el error al instante.&lt;/li&gt;
  &lt;li&gt;Python lo compensa con el parámetro &lt;code&gt;timeout&lt;/code&gt; de &lt;code&gt;sqlite3.connect()&lt;/code&gt;, cuyo valor por defecto son 5 segundos. Pasado ese tiempo, lanza el error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cualquier transacción que retenga el bloqueo más de esos 5 segundos (un proceso que lee, procesa durante minutos y escribe, o uno que quedó colgado con una transacción abierta) provoca el error en los demás.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Activa el modo WAL una sola vez.&lt;/strong&gt; La documentación de SQLite explica que, con WAL, los lectores no bloquean a los escritores ni al revés, y que el modo es persistente: una vez fijado, la base sigue en WAL aunque se cierre y se reabra.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sqlite3

con = sqlite3.connect(&quot;datos.db&quot;)
print(con.execute(&quot;PRAGMA journal_mode=WAL&quot;).fetchone())  # (&#39;wal&#39;,)
con.close()&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;WAL no elimina la regla de un solo escritor: según la documentación, solo puede haber un escritor a la vez. Por eso el paso siguiente sigue siendo necesario. Conviene ejecutar este paso con los demás procesos detenidos o, al menos, sin transacciones activas.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Define cuánto debe esperar cada conexión.&lt;/strong&gt; La espera es por conexión y no se hereda: debes fijarla en cada proceso. Con Python, el parámetro &lt;code&gt;timeout&lt;/code&gt; (en segundos) lo hace; también puedes usar el pragma, en milisegundos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def abrir(ruta: str) -&amp;gt; sqlite3.Connection:
    con = sqlite3.connect(ruta, timeout=30, isolation_level=None)  # transacciones explícitas
    con.execute(&quot;PRAGMA busy_timeout=30000&quot;)  # equivale a timeout=30
    return con&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Una conexión solo puede tener un manejador de ocupado: &lt;code&gt;busy_timeout&lt;/code&gt; reemplaza cualquier otro definido antes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Haz transacciones de escritura cortas y con &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt;.&lt;/strong&gt; Prepara los datos antes (lecturas, análisis, llamadas externas) y abre la transacción solo para escribir:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;con = abrir(&quot;datos.db&quot;)
lote = [(v,) for v in valores]   # preparado antes de bloquear nada

con.execute(&quot;BEGIN IMMEDIATE&quot;)
try:
    con.executemany(&quot;INSERT INTO eventos(valor) VALUES (?)&quot;, lote)
    con.execute(&quot;COMMIT&quot;)
except Exception:
    con.execute(&quot;ROLLBACK&quot;)
    raise&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La documentación de SQLite recomienda &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; para no encontrar &lt;code&gt;SQLITE_BUSY&lt;/code&gt; a mitad de una transacción: puede fallar al iniciar, pero si tiene éxito, ninguna operación posterior hasta el &lt;code&gt;COMMIT&lt;/code&gt; devolverá ese error. Hay un motivo más: si una transacción lee primero y luego escribe, y otra conexión escribió entremedias, SQLite devuelve el error de inmediato aunque el &lt;code&gt;timeout&lt;/code&gt; sea de 30 segundos, porque esperar causaría un bloqueo mutuo. En WAL ese caso es &lt;code&gt;SQLITE_BUSY_SNAPSHOT&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Reduce el número de escritores.&lt;/strong&gt; Si varios procesos solo producen datos, que envíen sus filas por una cola a un único proceso escritor. Las lecturas pueden seguir en paralelo.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Encuentra procesos olvidados y evita duplicados.&lt;/strong&gt; En Linux, &lt;code&gt;ps -ef --forest&lt;/code&gt; muestra el árbol de procesos con su PID y su PPID (el proceso padre); un trabajo huérfano suele colgar de PID 1. Para saber quién tiene abierta la base:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ps -ef --forest | grep -E &#39;ingesta|reprocesar|escaneo&#39; | grep -v grep
fuser datos.db
lsof datos.db&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Detén el que sobra con &lt;code&gt;kill &amp;lt;PID&amp;gt;&lt;/code&gt;. Para que no vuelva a pasar, ejecuta los trabajos largos como servicios (por ejemplo, con systemd) y protege los lanzamientos manuales con &lt;code&gt;flock -n /tmp/escaneo.lock python escaneo.py&lt;/code&gt;, que no arranca si ya hay otra instancia con el mismo archivo de bloqueo.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ten en cuenta las restricciones de WAL que indica la documentación: todos los procesos deben estar en el mismo equipo, WAL no funciona sobre un sistema de archivos de red, y el archivo &lt;code&gt;-wal&lt;/code&gt; forma parte del estado persistente de la base. Si copias o mueves &lt;code&gt;datos.db&lt;/code&gt;, hazlo junto con su &lt;code&gt;-wal&lt;/code&gt;; sin él podrías perder transacciones ya confirmadas. Además, si siempre hay al menos un lector activo, los puntos de control no pueden completarse y el archivo &lt;code&gt;-wal&lt;/code&gt; crece sin límite.&lt;/p&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;con = abrir(&quot;datos.db&quot;)
print(con.execute(&quot;PRAGMA journal_mode&quot;).fetchone()[0])  # wal
print(con.execute(&quot;PRAGMA busy_timeout&quot;).fetchone()[0])  # 30000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Después simula la concurrencia con varios procesos que escriben a la vez:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import multiprocessing as mp

def escribir(n):
    con = abrir(&quot;datos.db&quot;)
    for i in range(200):
        con.execute(&quot;BEGIN IMMEDIATE&quot;)
        con.execute(&quot;INSERT INTO eventos(valor) VALUES (?)&quot;, (f&quot;{n}-{i}&quot;,))
        con.execute(&quot;COMMIT&quot;)
    con.close()

if __name__ == &quot;__main__&quot;:
    with mp.Pool(4) as pool:
        pool.map(escribir, range(4))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Debe terminar sin &lt;code&gt;OperationalError&lt;/code&gt; y añadir 800 filas. En producción, comprueba que el registro de tus procesos no vuelve a mostrar &lt;code&gt;database is locked&lt;/code&gt; tras una noche de trabajo y que tu contador de elementos procesados sigue creciendo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sqlite3 datos.db &quot;SELECT count(*) FROM procesados;&quot;
sleep 60
sqlite3 datos.db &quot;SELECT count(*) FROM procesados;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si el segundo número es mayor que el primero, el proceso avanza. Por último, &lt;code&gt;ps -ef --forest&lt;/code&gt; no debe mostrar más de una instancia de cada trabajo.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;database table is locked&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Es otro código: &lt;code&gt;SQLITE_LOCKED&lt;/code&gt; indica un conflicto dentro de la misma conexión (o con una conexión que comparte caché), no entre procesos. Se reproduce, por ejemplo, ejecutando &lt;code&gt;DROP TABLE&lt;/code&gt; mientras un cursor de la misma conexión aún lee esa tabla. Cerrar o terminar de leer el cursor lo resuelve; &lt;code&gt;busy_timeout&lt;/code&gt; no ayuda.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;ProgrammingError: SQLite objects created in a thread can only be used in that same thread&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Aparece si compartes una conexión entre hilos. Abre una conexión por hilo o por proceso, que además encaja con la configuración por conexión de &lt;code&gt;busy_timeout&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;La base está en un volumen de red&lt;/h3&gt;
&lt;p&gt;WAL no funciona sobre un sistema de archivos de red. Si varios servidores deben escribir en la misma base, SQLite deja de ser la herramienta adecuada y conviene considerar un motor cliente-servidor.&lt;/p&gt;
&lt;h3&gt;Transacciones implícitas del módulo &lt;code&gt;sqlite3&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Con el control de transacciones heredado (el valor por defecto), el módulo abre una transacción implícita antes de &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; o &lt;code&gt;REPLACE&lt;/code&gt;, de tipo &lt;code&gt;DEFERRED&lt;/code&gt; salvo que indiques otra, y &lt;code&gt;with con:&lt;/code&gt; confirma al salir. Si prefieres ese estilo, pasa &lt;code&gt;isolation_level=&quot;IMMEDIATE&quot;&lt;/code&gt; a &lt;code&gt;connect()&lt;/code&gt; para que esa transacción implícita sea &lt;code&gt;IMMEDIATE&lt;/code&gt;. Los ejemplos de este artículo usan &lt;code&gt;isolation_level=None&lt;/code&gt; y &lt;code&gt;BEGIN&lt;/code&gt;/&lt;code&gt;COMMIT&lt;/code&gt; explícitos para que el momento del bloqueo sea evidente. Desde Python 3.12 existe además el atributo &lt;code&gt;autocommit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;SQLite permite un solo escritor a la vez; el segundo recibe &lt;code&gt;SQLITE_BUSY&lt;/code&gt;, que Python muestra como &lt;code&gt;database is locked&lt;/code&gt; tras agotar su &lt;code&gt;timeout&lt;/code&gt; por defecto de 5 segundos.&lt;/li&gt;
  &lt;li&gt;Activa &lt;code&gt;PRAGMA journal_mode=WAL&lt;/code&gt; una vez (es persistente) y fija la espera en cada conexión con &lt;code&gt;timeout=30&lt;/code&gt; o &lt;code&gt;PRAGMA busy_timeout=30000&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Usa transacciones cortas con &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; y prepara los datos antes de abrirlas.&lt;/li&gt;
  &lt;li&gt;Busca procesos olvidados con &lt;code&gt;ps -ef --forest&lt;/code&gt;, &lt;code&gt;fuser&lt;/code&gt; o &lt;code&gt;lsof&lt;/code&gt; y evita duplicados con servicios o &lt;code&gt;flock&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;WAL exige que todos los procesos estén en el mismo equipo y no funciona sobre sistemas de archivos de red.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.sqlite.org/wal.html&quot;&gt;SQLite: Write-Ahead Logging (persistencia, un solo escritor, restricciones)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.sqlite.org/rescode.html&quot;&gt;SQLite: Result and Error Codes (&lt;code&gt;SQLITE_BUSY&lt;/code&gt;, &lt;code&gt;SQLITE_LOCKED&lt;/code&gt;, &lt;code&gt;SQLITE_BUSY_SNAPSHOT&lt;/code&gt; y &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/sqlite3.html&quot;&gt;Python: módulo &lt;code&gt;sqlite3&lt;/code&gt; (parámetro &lt;code&gt;timeout&lt;/code&gt;, control de transacciones)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/3132231730459009154/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/sqlite3operationalerror-database-is.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3132231730459009154'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3132231730459009154'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/sqlite3operationalerror-database-is.html' title='sqlite3.OperationalError: database is locked: solución'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-6864471253459446178</id><published>2026-10-19T15:00:00.000-05:00</published><updated>2026-10-19T15:00:00.169-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Node.js"/><category scheme="http://www.blogger.com/atom/ns#" term="TypeScript"/><title type='text'>NestJS: property should not exist (forbidNonWhitelisted)</title><content type='html'>&lt;p&gt;Tu API de NestJS rechaza una petición &lt;code&gt;PATCH&lt;/code&gt; o &lt;code&gt;PUT&lt;/code&gt; con un 400 que nombra una propiedad concreta:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;message&quot;: [&quot;property categoriaId should not exist&quot;],
  &quot;error&quot;: &quot;Bad Request&quot;,
  &quot;statusCode&quot;: 400
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pasa en aplicaciones que registran el &lt;code&gt;ValidationPipe&lt;/code&gt; de forma global con estas opciones:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.useGlobalPipes(
  new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aparece, por ejemplo, después de que el backend cambie un campo escalar por un arreglo, cuando el formulario del frontend reenvía campos deshabilitados o bloqueados, o cuando el servidor sigue ejecutando código antiguo. Este artículo explica cada caso y cómo distinguirlos por el nombre de la propiedad del mensaje.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;La documentación de NestJS describe las dos opciones: con &lt;code&gt;whitelist: true&lt;/code&gt;, el validador elimina del objeto las propiedades que no usan ningún decorador de validación; con &lt;code&gt;forbidNonWhitelisted: true&lt;/code&gt;, en lugar de eliminarlas lanza una excepción. El resultado es que cualquier propiedad del cuerpo que el DTO no declare, o que declare sin ningún decorador de class-validator, produce un 400 con el mensaje &lt;code&gt;property &amp;lt;nombre&amp;gt; should not exist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;El nombre de la propiedad indica la causa probable:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Un nombre antiguo&lt;/strong&gt; (&lt;code&gt;categoriaId&lt;/code&gt;): el backend lo renombró a &lt;code&gt;categoriaIds&lt;/code&gt; y el frontend sigue enviando el anterior.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Un campo de solo lectura&lt;/strong&gt; (&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;createdAt&lt;/code&gt;, un campo bloqueado): el formulario envía el objeto completo y el DTO no los acepta.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Un campo que sí existe en tu DTO actual&lt;/strong&gt;: el servidor que responde no ejecuta tu código nuevo. Lo más habitual es un proceso antiguo que quedó en marcha con un &lt;code&gt;dist/&lt;/code&gt; compilado antes del cambio.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Una propiedad declarada sin decorador:&lt;/strong&gt; está en la clase, pero para el validador no existe.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Lee el nombre de la propiedad y decide de qué lado está el fallo.&lt;/strong&gt; Si es un nombre antiguo o un campo de solo lectura, el error está en el payload del cliente. Si es un campo que acabas de añadir al DTO, revisa el decorador (paso 3) y que el servidor ejecute el código nuevo (paso 4).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Alinea el payload con el DTO.&lt;/strong&gt; Envía solo los campos declarados, traduce los renombrados y omite los bloqueados. Para un PATCH, además, omite la clave cuando el valor no cambió:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function mismosIds(a: number[], b: number[]): boolean {
  const x = [...a].sort();
  const y = [...b].sort();
  return x.length === y.length &amp;amp;&amp;amp; x.every((v, i) =&amp;gt; v === y[i]);
}

// FormValues es el tipo de los valores de tu formulario
function construirPayload(form: FormValues, original: FormValues) {
  const payload: { nombre?: string; categoriaIds?: number[] } = {};
  if (form.nombre !== original.nombre) payload.nombre = form.nombre;
  if (!mismosIds(form.categoriaIds, original.categoriaIds)) {
    payload.categoriaIds = form.categoriaIds;
  }
  return payload; // las claves sin cambios no viajan
}

await api.patch(`/items/${id}`, construirPayload(form, original));&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Hay un detalle con los arreglos: en algunos servicios, el arreglo recibido se interpreta como el conjunto completo de relaciones. Un &lt;code&gt;[]&lt;/code&gt; enviado por descuido puede significar &quot;quitar todo&quot;. Si no hubo cambios, no envíes la clave, y asegúrate de que el formulario conserva los valores cargados antes de enviarlos.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Declara cada propiedad con al menos un decorador.&lt;/strong&gt; Para el DTO de actualización, deriva del de creación con &lt;code&gt;PartialType&lt;/code&gt; (paquete &lt;code&gt;@nestjs/mapped-types&lt;/code&gt;), que según la documentación deriva la variante sin repetir las propiedades ni sus decoradores de validación:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { PartialType } from &#39;@nestjs/mapped-types&#39;;
import { IsArray, IsInt, IsNotEmpty, IsString } from &#39;class-validator&#39;;

export class CreateItemDto {
  @IsString()
  @IsNotEmpty()
  nombre: string;

  @IsArray()
  @IsInt({ each: true })
  categoriaIds: number[];
}

export class UpdateItemDto extends PartialType(CreateItemDto) {}&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si una propiedad no necesita ninguna validación concreta, class-validator ofrece &lt;code&gt;@Allow()&lt;/code&gt; para que no se elimine ni se rechace.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Si el cambio no se aplica, reinicia el proceso correcto.&lt;/strong&gt; En un proyecto generado con Nest CLI, &lt;code&gt;start:prod&lt;/code&gt; ejecuta &lt;code&gt;node dist/main&lt;/code&gt;, es decir, el código ya compilado. No recoge cambios de &lt;code&gt;src/&lt;/code&gt; hasta que ejecutes &lt;code&gt;nest build&lt;/code&gt;. Detén el proceso antiguo, compila y vuelve a iniciar. En Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netstat -ano | findstr :3000
taskkill /PID 12345 /T /F
npm run build
npm run start:prod&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;En Linux o macOS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsof -i :3000
kill 12345
npm run build
npm run start:prod&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Sustituye &lt;code&gt;3000&lt;/code&gt; por tu puerto y &lt;code&gt;12345&lt;/code&gt; por el PID de la última columna de &lt;code&gt;netstat&lt;/code&gt; (la opción &lt;code&gt;-o&lt;/code&gt; lo añade) o de &lt;code&gt;lsof&lt;/code&gt;. En &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill&quot;&gt;&lt;code&gt;taskkill&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;/T&lt;/code&gt; termina también los procesos hijos y &lt;code&gt;/F&lt;/code&gt; fuerza el cierre. Si al iniciar la versión nueva ves un error como &lt;code&gt;Error: listen EADDRINUSE: address already in use :::3000&lt;/code&gt;, otro proceso sigue ocupando el puerto: Node lo describe como un intento de enlazar un servidor a una dirección que otro servidor ya ocupa.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Prueba la petición con los campos correctos (en PowerShell usa &lt;code&gt;curl.exe&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -i -X PATCH http://localhost:3000/items/1 \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{&quot;nombre&quot;:&quot;prueba&quot;,&quot;categoriaIds&quot;:[1,2]}&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Debe responder &lt;code&gt;200&lt;/code&gt;. Después consulta el recurso con un &lt;code&gt;GET&lt;/code&gt; y comprueba que el selector queda con los valores marcados y que las relaciones previas se conservaron. Como control, envía un campo inventado:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -i -X PATCH http://localhost:3000/items/1 \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{&quot;campoInventado&quot;:1}&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Debe responder &lt;code&gt;400&lt;/code&gt; con &lt;code&gt;property campoInventado should not exist&lt;/code&gt;: confirma que el pipe sigue activo y que el servidor carga el DTO nuevo. También puedes cubrirlo con una prueba unitaria del pipe, sin levantar la aplicación:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const pipe = new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true });
const meta = { type: &#39;body&#39; as const, metatype: UpdateItemDto };

it(&#39;acepta los campos declarados&#39;, async () =&amp;gt; {
  await expect(pipe.transform({ nombre: &#39;x&#39;, categoriaIds: [1] }, meta)).resolves.toBeDefined();
});

it(&#39;rechaza un campo no declarado&#39;, async () =&amp;gt; {
  const error = await pipe.transform({ campoViejo: 1 }, meta).catch((e) =&amp;gt; e);
  expect(error.getResponse()).toMatchObject({ message: [&#39;property campoViejo should not exist&#39;] });
});&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Solo &lt;code&gt;whitelist: true&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Sin &lt;code&gt;forbidNonWhitelisted&lt;/code&gt;, las propiedades desconocidas se eliminan en silencio. El &lt;code&gt;PATCH&lt;/code&gt; responde 200, pero el campo con el nombre antiguo no se guarda y nadie se entera. Si prefieres detectar estos desajustes pronto, mantén &lt;code&gt;forbidNonWhitelisted&lt;/code&gt; activado.&lt;/p&gt;
&lt;h3&gt;No aparece el detalle del error&lt;/h3&gt;
&lt;p&gt;La opción &lt;code&gt;disableErrorMessages: true&lt;/code&gt; del &lt;code&gt;ValidationPipe&lt;/code&gt; hace que la respuesta no incluya los mensajes detallados. Si ves un 400 sin el nombre de la propiedad, revisa esa opción.&lt;/p&gt;
&lt;h3&gt;Las pruebas e2e pasan pero producción falla&lt;/h3&gt;
&lt;p&gt;La configuración de &lt;code&gt;app.useGlobalPipes(...)&lt;/code&gt; suele estar en &lt;code&gt;main.ts&lt;/code&gt;. Una prueba e2e que crea la aplicación desde el módulo de pruebas no la ejecuta, así que no reproducirá el 400 salvo que registres el mismo pipe en la prueba.&lt;/p&gt;
&lt;h3&gt;El DTO de actualización no deriva del de creación&lt;/h3&gt;
&lt;p&gt;Si &lt;code&gt;UpdateItemDto&lt;/code&gt; es una clase independiente, cada campo nuevo que añadas a &lt;code&gt;CreateItemDto&lt;/code&gt; será rechazado en el &lt;code&gt;PATCH&lt;/code&gt; hasta que lo declares también ahí. Derivarlo con &lt;code&gt;PartialType&lt;/code&gt; evita esa divergencia.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Con &lt;code&gt;whitelist&lt;/code&gt; y &lt;code&gt;forbidNonWhitelisted&lt;/code&gt;, toda propiedad sin decorador de validación en el DTO produce &lt;code&gt;property X should not exist&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;El nombre de la propiedad orienta el diagnóstico: nombre antiguo o campo de solo lectura (frontend), o campo nuevo (decorador o proceso antiguo).&lt;/li&gt;
  &lt;li&gt;Envía solo los campos declarados y omite la clave de un arreglo cuando no cambió: un &lt;code&gt;[]&lt;/code&gt; puede significar &quot;borrar todo&quot;.&lt;/li&gt;
  &lt;li&gt;Si los cambios del backend no se aplican, detén el proceso antiguo, ejecuta &lt;code&gt;nest build&lt;/code&gt; y vuelve a iniciar.&lt;/li&gt;
  &lt;li&gt;Verifica con un &lt;code&gt;PATCH&lt;/code&gt; válido (200) y uno con un campo inventado (400).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.nestjs.com/techniques/validation&quot;&gt;NestJS: Validation (&lt;code&gt;ValidationPipe&lt;/code&gt;, &lt;code&gt;whitelist&lt;/code&gt;, &lt;code&gt;forbidNonWhitelisted&lt;/code&gt; y tipos mapeados)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/typestack/class-validator&quot;&gt;class-validator: sección Whitelisting y decorador &lt;code&gt;@Allow()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat&quot;&gt;Microsoft: &lt;code&gt;netstat&lt;/code&gt; (opciones &lt;code&gt;-a&lt;/code&gt;, &lt;code&gt;-n&lt;/code&gt; y &lt;code&gt;-o&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/6864471253459446178/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/nestjs-property-should-not-exist.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/6864471253459446178'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/6864471253459446178'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/nestjs-property-should-not-exist.html' title='NestJS: property should not exist (forbidNonWhitelisted)'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-5590991191722974619</id><published>2026-10-19T09:00:00.000-05:00</published><updated>2026-10-19T09:00:00.117-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><category scheme="http://www.blogger.com/atom/ns#" term="React"/><title type='text'>useEffect borra el estado: dependencia con referencia nueva</title><content type='html'>&lt;p&gt;Un componente de React pierde lo que el usuario acaba de escribir sin que nadie haya tocado ese estado. Se ha visto de tres formas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Un diálogo para enviar un informe por correo pierde los destinatarios escritos y el mensaje de error después de un envío fallido.&lt;/li&gt;
  &lt;li&gt;Un formulario pierde los importes editados cuando termina una recarga de datos en segundo plano.&lt;/li&gt;
  &lt;li&gt;Una prueba nueva con Vitest y Testing Library se cuelga o falla.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En el tercer caso, la consola muestra este aviso:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn&#39;t have a dependency array, or one of the dependencies changes on every render.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La causa común es un &lt;code&gt;useEffect&lt;/code&gt; que inicializa o reinicia estado local y depende de un valor cuya referencia cambia en cada render. Este artículo explica por qué se dispara y cómo corregirlo.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;La documentación de React indica que compara cada dependencia con su valor anterior mediante &lt;code&gt;Object.is&lt;/code&gt;. Un array u objeto creado durante el render es un valor distinto en cada render, aunque su contenido sea idéntico, así que el efecto se vuelve a ejecutar. Si ese efecto llama a &lt;code&gt;setState&lt;/code&gt; con valores iniciales, pisa lo que escribió el usuario.&lt;/p&gt;
&lt;p&gt;Los tres casos son variantes de esto:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Diálogo:&lt;/strong&gt; el padre calcula &lt;code&gt;const iniciales = guardados.length ? guardados : [&#39;&#39;]&lt;/code&gt;. Cuando no hay destinatarios guardados, &lt;code&gt;[&#39;&#39;]&lt;/code&gt; es un array nuevo en cada render del padre. Al fallar el envío, el padre cambia su estado y se renderiza, el efecto detecta una dependencia &quot;nueva&quot; y restablece destinatarios y error.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Formulario con datos remotos:&lt;/strong&gt; TanStack Query comparte estructuralmente los resultados, y su documentación indica que la referencia de &lt;code&gt;data&lt;/code&gt; no cambia si los datos no cambiaron. Pero si la recarga trae algo distinto (aunque sea un campo que no editas), la referencia cambia y el efecto reinicializa el formulario. Las consultas están obsoletas por defecto y &lt;code&gt;refetchOnWindowFocus&lt;/code&gt; vale &lt;code&gt;true&lt;/code&gt;, así que esa recarga ocurre sola al volver a la ventana.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bucle de renders:&lt;/strong&gt; con &lt;code&gt;const { data = [] } = useQuery(...)&lt;/code&gt;, el valor por defecto crea un array nuevo en cada render mientras &lt;code&gt;data&lt;/code&gt; es &lt;code&gt;undefined&lt;/code&gt;. Si un efecto depende de él y hace &lt;code&gt;setFilas(data.map(...))&lt;/code&gt;, cada render provoca otro. React solo registra el aviso de arriba en la consola y el bucle continúa: una prueba que espera ese render no termina.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Además, según la guía &quot;You Might Not Need an Effect&quot;, los efectos son para sincronizar con sistemas externos; reiniciar estado por un cambio de props normalmente no necesita un efecto.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Identifica la dependencia que cambia.&lt;/strong&gt; La documentación de &lt;code&gt;useEffect&lt;/code&gt; sugiere registrar las dependencias y compararlas con &lt;code&gt;Object.is&lt;/code&gt; entre renders:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const anterior = useRef(destinatariosIniciales);
console.log(&#39;misma referencia:&#39;, Object.is(anterior.current, destinatariosIniciales));
anterior.current = destinatariosIniciales;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Es solo para depurar: bórralo después. Si imprime &lt;code&gt;false&lt;/code&gt; sin que el contenido haya cambiado, esa es la dependencia.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Inicializa solo en la transición de cerrado a abierto.&lt;/strong&gt; Guarda el valor anterior de &lt;code&gt;open&lt;/code&gt; en un &lt;code&gt;useRef&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function InformeDialog({ open, destinatariosIniciales, onEnviar }) {
  const [destinatarios, setDestinatarios] = useState([]);
  const [error, setError] = useState(null);
  const estabaAbierto = useRef(false);

  useEffect(() =&amp;gt; {
    if (open &amp;amp;&amp;amp; !estabaAbierto.current) {
      setDestinatarios(destinatariosIniciales);
      setError(null);
    }
    estabaAbierto.current = open;
  }, [open, destinatariosIniciales]);

  // ...
}&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La lista de dependencias sigue completa (el linter no se queja), pero la condición impide reinicializar cuando solo cambia la referencia del array.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Reinicializa solo cuando cambia la clave de negocio.&lt;/strong&gt; Para formularios con datos remotos, guarda para qué registro se inicializó el estado:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function FormularioFactura({ id }) {
  const { data } = useQuery({ queryKey: [&#39;factura&#39;, id], queryFn: () =&amp;gt; obtenerFactura(id) });
  const [monto, setMonto] = useState(&#39;&#39;);
  const inicializadoPara = useRef(null);

  useEffect(() =&amp;gt; {
    if (data &amp;amp;&amp;amp; inicializadoPara.current !== data.id) {
      inicializadoPara.current = data.id;
      setMonto(String(data.monto));
    }
  }, [data]);

  // ...
}&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La alternativa que recomienda React es no usar un efecto: separa los campos en un componente hijo con &lt;code&gt;useState(() =&amp;gt; String(factura.monto))&lt;/code&gt; y renderízalo con &lt;code&gt;key={data.id}&lt;/code&gt;. React reinicia el estado solo cuando cambia la clave. Una dependencia primitiva como &lt;code&gt;dataUpdatedAt&lt;/code&gt; evita el problema de la referencia, pero cambia en cada consulta exitosa, así que sirve para vistas de solo lectura y no para pantallas donde se edita.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Usa referencias estables.&lt;/strong&gt; Saca los valores por defecto del componente o memoízalos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const SIN_FILAS = [];

function ListaFilas({ obtenerFilas }) {
  const { data = SIN_FILAS } = useQuery({ queryKey: [&#39;filas&#39;], queryFn: obtenerFilas });
  // ...
}

const destinatariosIniciales = useMemo(
  () =&amp;gt; (guardados.length ? guardados : [&#39;&#39;]),
  [guardados],
);&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;React presenta &lt;code&gt;useMemo&lt;/code&gt; como último recurso para estabilizar una dependencia; primero conviene mover el valor fuera del componente o leer solo valores primitivos. Si pasas una función &lt;code&gt;select&lt;/code&gt; a &lt;code&gt;useQuery&lt;/code&gt;, la documentación de TanStack recomienda memoizarla.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Reduce las recargas donde el usuario edita.&lt;/strong&gt; La documentación de TanStack indica que &lt;code&gt;staleTime&lt;/code&gt; es la forma recomendada de evitar recargas excesivas:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;useQuery({
  queryKey: [&#39;factura&#39;, id],
  queryFn: () =&amp;gt; obtenerFactura(id),
  refetchOnWindowFocus: false,
  staleTime: 60_000,
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Escribe pruebas que fallen con el código anterior.&lt;/strong&gt; La primera reproduce la secuencia del diálogo; la segunda usa &lt;a href=&quot;https://react.dev/reference/react/Profiler&quot;&gt;&lt;code&gt;Profiler&lt;/code&gt;&lt;/a&gt;, cuyo &lt;code&gt;onRender&lt;/code&gt; se llama en cada commit, para cortar un bucle:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;it(&#39;conserva destinatarios y error tras un envío fallido&#39;, async () =&amp;gt; {
  const user = userEvent.setup();
  const enviar = vi.fn().mockRejectedValue(new Error(&#39;fallo&#39;));
  render(&amp;lt;PaginaInforme enviar={enviar} /&amp;gt;);

  await user.click(screen.getByText(&#39;Abrir&#39;));
  const campo = screen.getByLabelText(&#39;Destinatarios&#39;);
  await user.clear(campo);
  await user.type(campo, &#39;ana, luis&#39;);
  await user.click(screen.getByText(&#39;Enviar&#39;));

  expect(await screen.findByRole(&#39;alert&#39;)).toBeTruthy();
  expect(screen.getByLabelText(&#39;Destinatarios&#39;).value).toBe(&#39;ana, luis&#39;);
});

it(&#39;no entra en un bucle de renders&#39;, () =&amp;gt; {
  let commits = 0;
  const onRender = () =&amp;gt; {
    commits += 1;
    if (commits &amp;gt; 10) throw new Error(&#39;Demasiados renders: posible bucle de efectos&#39;);
  };
  render(
    &amp;lt;QueryClientProvider client={new QueryClient()}&amp;gt;
      &amp;lt;Profiler id=&quot;lista&quot; onRender={onRender}&amp;gt;
        &amp;lt;ListaFilas obtenerFilas={() =&amp;gt; new Promise(() =&amp;gt; {})} /&amp;gt;
      &amp;lt;/Profiler&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;,
  );
  expect(commits).toBeLessThan(10);
});&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Las pruebas asumen un componente &lt;code&gt;PaginaInforme&lt;/code&gt; (el padre que renderiza el diálogo y le pasa &lt;code&gt;enviar&lt;/code&gt;) y los imports habituales de Vitest, Testing Library y TanStack Query. El &lt;code&gt;throw&lt;/code&gt; es lo que evita el cuelgue: sin él, &lt;code&gt;render&lt;/code&gt; nunca devuelve el control cuando hay un bucle. Según su documentación, &lt;code&gt;Profiler&lt;/code&gt; está desactivado por defecto en las compilaciones de producción, así que este guardia es solo para pruebas.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Ejecuta las pruebas con el código anterior: deben fallar (el campo queda vacío, el aviso desaparece o se lanza el error de &quot;Demasiados renders&quot;). Con la corrección deben pasar.&lt;/li&gt;
  &lt;li&gt;Reproduce en el navegador la secuencia exacta: abre el diálogo, escribe destinatarios, fuerza un fallo del envío (por ejemplo, con la red desconectada en las herramientas de desarrollo) y comprueba que los destinatarios y el mensaje siguen ahí.&lt;/li&gt;
  &lt;li&gt;En el formulario, edita un importe, cambia de pestaña y vuelve a la ventana: el valor editado debe conservarse.&lt;/li&gt;
  &lt;li&gt;Ejecuta la suite completa: ningún archivo de pruebas debería tardar de forma anómala. Un archivo que antes se colgaba debe terminar en pocos segundos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Efecto sin lista de dependencias&lt;/h3&gt;
&lt;p&gt;Un &lt;code&gt;useEffect&lt;/code&gt; sin segundo argumento se ejecuta tras cada render. La documentación pide revisar primero que no falte la lista de dependencias.&lt;/p&gt;
&lt;h3&gt;Funciones como dependencia&lt;/h3&gt;
&lt;p&gt;Una función definida dentro del componente también es nueva en cada render y provoca lo mismo. La guía &lt;a href=&quot;https://react.dev/learn/removing-effect-dependencies&quot;&gt;Removing Effect Dependencies&lt;/a&gt; propone moverla fuera del componente o dentro del efecto; &lt;code&gt;useCallback&lt;/code&gt; queda como último recurso.&lt;/p&gt;
&lt;h3&gt;Ajustar estado durante el render&lt;/h3&gt;
&lt;p&gt;React también documenta guardar la información del render anterior en el estado y ajustarlo durante el render. Es más eficiente que un efecto, pero la propia guía advierte que la mayoría de los componentes no lo necesitan: prefiere &lt;code&gt;key&lt;/code&gt; o calcular el valor al renderizar.&lt;/p&gt;
&lt;h3&gt;StrictMode&lt;/h3&gt;
&lt;p&gt;El patrón del paso 2 funciona igual dentro de &lt;code&gt;&amp;lt;StrictMode&amp;gt;&lt;/code&gt;, que en desarrollo ejecuta un ciclo adicional de configuración y limpieza de cada efecto al montar: la condición sobre &lt;code&gt;estabaAbierto&lt;/code&gt; evita inicializar dos veces.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;React compara las dependencias con &lt;code&gt;Object.is&lt;/code&gt;: un array u objeto creado en el render es &quot;nuevo&quot; cada vez y vuelve a disparar el efecto.&lt;/li&gt;
  &lt;li&gt;Un efecto que inicializa estado con ese tipo de dependencia borra lo que el usuario escribió; si además cambia el estado con un valor nuevo, genera un bucle.&lt;/li&gt;
  &lt;li&gt;Inicializa solo en la transición cerrado a abierto o cuando cambia la clave de negocio, o usa &lt;code&gt;key&lt;/code&gt; para reiniciar el componente.&lt;/li&gt;
  &lt;li&gt;Estabiliza los valores por defecto (constantes fuera del componente o &lt;code&gt;useMemo&lt;/code&gt;) y limita las recargas en pantallas de edición.&lt;/li&gt;
  &lt;li&gt;Cubre el caso con pruebas que fallen antes y pasen después, incluyendo un guardia de menos de 10 renders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/useEffect&quot;&gt;React: &lt;code&gt;useEffect&lt;/code&gt;, comparación de dependencias con &lt;code&gt;Object.is&lt;/code&gt; y solución de problemas de bucles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://react.dev/learn/you-might-not-need-an-effect&quot;&gt;React: You Might Not Need an Effect (reiniciar estado con &lt;code&gt;key&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults&quot;&gt;TanStack Query: Important Defaults (consultas obsoletas, recarga al enfocar la ventana, structural sharing)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/5590991191722974619/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/useeffect-borra-el-estado-dependencia.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/5590991191722974619'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/5590991191722974619'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/useeffect-borra-el-estado-dependencia.html' title='useEffect borra el estado: dependencia con referencia nueva'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-121215480435695088</id><published>2026-10-18T15:00:00.000-05:00</published><updated>2026-10-18T15:00:00.181-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>FileNotFoundError por ruta larga en Windows (MAX_PATH)</title><content type='html'>&lt;p&gt;Un script de Python escribe un archivo en una carpeta que existe y falla igualmente:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FileNotFoundError: [Errno 2] No such file or directory: &#39;C:\\proyectos\\mi-app\\tmp\\sesion\\resultados\\...\\informe_final.xlsx&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En Node.js, una herramienta que lanza procesos desde una carpeta muy anidada puede abortar con un mensaje como este (el texto exacto depende de la herramienta):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Error: Current working directory has a path longer than allowed for a Win32 working directory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El patrón siempre es el mismo: todo funciona en carpetas cortas y falla solo en carpetas temporales o de sesión muy anidadas. No falta nada: la ruta completa supera el límite clásico de Windows de 260 caracteres. Este artículo explica cómo confirmarlo, qué soluciones hay y qué cubre cada una.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;En la API de Windows, la longitud máxima de una ruta es &lt;code&gt;MAX_PATH&lt;/code&gt;, definida en 260 caracteres. Esa cifra incluye la letra de unidad, los dos puntos, la barra inicial, los nombres separados por barras y el carácter nulo final, así que en la práctica quedan unos 259 caracteres. Una carpeta base larga más nombres de archivo largos (títulos de informes, identificadores, fechas) suman más de 260 con facilidad.&lt;/p&gt;
&lt;p&gt;Cuando se supera el límite, Windows responde &quot;ruta no encontrada&quot; y Python lo traduce a &lt;code&gt;FileNotFoundError&lt;/code&gt;, aunque la carpeta exista. Con rutas largas desactivadas, &lt;code&gt;os.path.isdir()&lt;/code&gt; devuelve &lt;code&gt;False&lt;/code&gt; sobre una carpeta que sí existe, y &lt;code&gt;os.makedirs()&lt;/code&gt; falla con &lt;code&gt;[WinError 3]&lt;/code&gt; (&quot;El sistema no puede encontrar la ruta especificada&quot; en Windows en español). Cualquier biblioteca que escriba en esa ruta con &lt;code&gt;open()&lt;/code&gt; fallará igual: guardar un libro de Excel con &lt;code&gt;openpyxl&lt;/code&gt;, una imagen con Pillow o una figura con &lt;code&gt;savefig&lt;/code&gt; de matplotlib.&lt;/p&gt;
&lt;p&gt;El directorio de trabajo tiene además su propio límite. La documentación de &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcurrentdirectory&quot;&gt;&lt;code&gt;SetCurrentDirectory&lt;/code&gt;&lt;/a&gt; indica que, por defecto, el nombre está limitado a &lt;code&gt;MAX_PATH&lt;/code&gt; caracteres y que fijar un directorio actual más largo hace fallar &lt;code&gt;CreateProcessW&lt;/code&gt;, la función con la que Windows crea procesos. Por eso lanzar un subproceso con un &lt;code&gt;cwd&lt;/code&gt; largo falla incluso en programas que sí pueden escribir archivos con rutas largas.&lt;/p&gt;
&lt;p&gt;Desde Windows 10 versión 1607 el límite puede levantarse, pero es opcional por aplicación: según Microsoft, hacen falta dos condiciones. El valor de registro &lt;code&gt;LongPathsEnabled&lt;/code&gt; debe estar en 1 y la aplicación debe declarar &lt;code&gt;longPathAware&lt;/code&gt; en su manifiesto. Activar el valor de registro no afecta a las aplicaciones que no lo declaran, y el sistema lo lee una vez por proceso, por lo que puede ser necesario reiniciar.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Localiza las rutas largas.&lt;/strong&gt; Este script lista los archivos de una carpeta cuya ruta tiene 250 caracteres o más. Usa el prefijo &lt;code&gt;\\?\&lt;/code&gt; para poder recorrer lo que una ruta normal no deja abrir:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import sys

def rutas_largas(raiz, umbral=250):
    for carpeta, _, archivos in os.walk(&quot;\\\\?\\&quot; + os.path.abspath(raiz)):
        for nombre in archivos:
            ruta = os.path.join(carpeta, nombre)[4:]  # quita el prefijo \\?\
            if len(ruta) &amp;gt;= umbral:
                yield len(ruta), ruta

for largo, ruta in rutas_largas(sys.argv[1]):
    print(largo, ruta)&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Trabaja desde una ruta base corta.&lt;/strong&gt; Es la solución más fiable porque no depende de ninguna herramienta. Crea una carpeta como &lt;code&gt;C:\tmp\trabajo&lt;/code&gt;, genera ahí los archivos intermedios y copia al destino definitivo solo el resultado final. Muchas herramientas toman la carpeta temporal de las variables &lt;code&gt;TMP&lt;/code&gt; y &lt;code&gt;TEMP&lt;/code&gt; (la función &lt;code&gt;GetTempPath&lt;/code&gt; de Windows las consulta en ese orden), así que en PowerShell puedes redirigirlas para esa sesión:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-Item -ItemType Directory -Force C:\tmp\trabajo | Out-Null
$env:TMP = &quot;C:\tmp\trabajo&quot;
$env:TEMP = &quot;C:\tmp\trabajo&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;em&gt;Qué cubre:&lt;/em&gt; todo, incluido el directorio de trabajo, &lt;code&gt;cmd.exe&lt;/code&gt; y las herramientas que no admiten rutas largas.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Acorta los nombres que genera tu código.&lt;/strong&gt; Usa identificadores cortos y guarda la correspondencia con el nombre largo en un archivo de manifiesto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import csv
from pathlib import Path

def guardar_lote(carpeta: Path, items: list[tuple[str, bytes]]) -&amp;gt; None:
    carpeta.mkdir(parents=True, exist_ok=True)
    with (carpeta / &quot;manifiesto.csv&quot;).open(&quot;w&quot;, newline=&quot;&quot;, encoding=&quot;utf-8&quot;) as m:
        w = csv.writer(m)
        w.writerow([&quot;id&quot;, &quot;nombre_original&quot;])
        for n, (nombre, datos) in enumerate(items, start=1):
            id_corto = f&quot;{n:04d}&quot;
            (carpeta / f&quot;{id_corto}.bin&quot;).write_bytes(datos)
            w.writerow([id_corto, nombre])&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;em&gt;Qué cubre:&lt;/em&gt; ataca la causa (la longitud total) y funciona en cualquier equipo, sin tocar la configuración de Windows.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Activa las rutas largas en Windows.&lt;/strong&gt; En una consola de PowerShell con permisos de administrador:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-ItemProperty -Path &quot;HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem&quot; `
  -Name &quot;LongPathsEnabled&quot; -Value 1 -PropertyType DWORD -Force&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;También puede activarse con la directiva de grupo &quot;Enable Win32 long paths&quot; (Configuración del equipo, Plantillas administrativas, Sistema, Sistema de archivos). Reinicia el equipo: Microsoft indica que puede ser necesario porque algunos procesos pudieron arrancar antes del cambio. La documentación de Python para Windows indica que, con este valor activado y tras reiniciar, &lt;code&gt;open()&lt;/code&gt;, el módulo &lt;code&gt;os&lt;/code&gt; y la mayoría de las funciones de rutas aceptan rutas de más de 260 caracteres sin más configuración.&lt;/p&gt;
    &lt;p&gt;Git tiene su propio ajuste. Con &lt;code&gt;--system&lt;/code&gt; escribe en la configuración del sistema, por lo que normalmente requiere permisos de administrador:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --system core.longpaths true&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;em&gt;Qué cubre:&lt;/em&gt; las aplicaciones que declaran &lt;code&gt;longPathAware&lt;/code&gt; (Python, según su documentación) y los comandos de Git escritos en C. No cubre las herramientas que no lo declaran. Git for Windows advierte además que los comandos de Git implementados como scripts pueden seguir fallando.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Usa el prefijo &lt;code&gt;\\?\&lt;/code&gt; en el código que controlas.&lt;/strong&gt; Para las funciones de la API que lo admiten, una ruta absoluta con este prefijo se pasa al sistema con modificaciones mínimas:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os

ruta = r&quot;C:\tmp\trabajo\una\carpeta\muy\larga\informe.txt&quot;
with open(&quot;\\\\?\\&quot; + os.path.abspath(ruta), &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(&quot;ok&quot;)&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Microsoft precisa que con este prefijo no puedes usar barras normales, ni &lt;code&gt;.&lt;/code&gt; ni &lt;code&gt;..&lt;/code&gt;, y que las rutas relativas siguen limitadas a &lt;code&gt;MAX_PATH&lt;/code&gt;. &lt;em&gt;Qué cubre:&lt;/em&gt; solo el código propio que llama a funciones compatibles; no sirve como argumento para otros programas ni para el directorio de trabajo.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Si activaste las rutas largas, comprueba el valor de registro:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ItemProperty -Path &quot;HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem&quot; -Name LongPathsEnabled&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Debe mostrar &lt;code&gt;LongPathsEnabled : 1&lt;/code&gt;. Después prueba una ruta que supere los 260 caracteres sin el prefijo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pathlib import Path

ruta = Path(r&quot;C:\tmp\trabajo&quot;) / (&quot;a&quot; * 120) / (&quot;b&quot; * 120) / &quot;prueba.txt&quot;
print(len(str(ruta)))
try:
    ruta.parent.mkdir(parents=True, exist_ok=True)
    ruta.write_text(&quot;ok&quot;, encoding=&quot;utf-8&quot;)
    print(&quot;OK: la ruta larga funciona&quot;)
except FileNotFoundError as e:
    print(&quot;FALLA:&quot;, e)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Con rutas largas desactivadas debe imprimir &lt;code&gt;FALLA&lt;/code&gt;; con ellas activas (y tras reiniciar), &lt;code&gt;OK&lt;/code&gt;. Si optaste por la ruta corta o los nombres cortos, vuelve a ejecutar el script original desde &lt;code&gt;C:\tmp\trabajo&lt;/code&gt;: debe guardar los gráficos y el libro de Excel sin &lt;code&gt;FileNotFoundError&lt;/code&gt;, y el libro debe abrirse con normalidad.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Node.js y subprocesos&lt;/h3&gt;
&lt;p&gt;El síntoma cambia según la API. En una prueba con Node.js 24 en Windows 11 con rutas largas desactivadas, &lt;code&gt;process.chdir()&lt;/code&gt; falló con &lt;code&gt;ENAMETOOLONG&lt;/code&gt; y &lt;code&gt;child_process.spawnSync&lt;/code&gt; con un &lt;code&gt;cwd&lt;/code&gt; largo falló con un engañoso &lt;code&gt;spawnSync cmd ENOENT&lt;/code&gt;, mientras que &lt;code&gt;fs.writeFileSync&lt;/code&gt; con una ruta absoluta larga funcionó. En Python, &lt;code&gt;subprocess.run(..., cwd=ruta_larga)&lt;/code&gt; lanzó &lt;code&gt;NotADirectoryError: [WinError 267]&lt;/code&gt; (&quot;El nombre del directorio no es válido&quot;). Es un comportamiento observado que puede variar entre versiones; la solución es la misma: ejecuta desde una ruta corta.&lt;/p&gt;
&lt;h3&gt;Git: Filename too long&lt;/h3&gt;
&lt;p&gt;Al clonar un repositorio con nombres largos dentro de una carpeta que ya es larga, Git puede informar de que no puede crear un archivo porque el nombre es demasiado largo (&lt;code&gt;Filename too long&lt;/code&gt;). Microsoft menciona justo este caso. Clona en una carpeta corta como &lt;code&gt;C:\src&lt;/code&gt; o activa &lt;code&gt;core.longpaths&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;El límite de los directorios es menor&lt;/h3&gt;
&lt;p&gt;Al crear un directorio con la API, la ruta no puede ser tan larga que no quepa después un nombre 8.3: el nombre del directorio no puede superar &lt;code&gt;MAX_PATH&lt;/code&gt; menos 12 caracteres. Puedes encontrar errores con rutas de 250 caracteres.&lt;/p&gt;
&lt;h3&gt;Explorador, cmd.exe y otras aplicaciones&lt;/h3&gt;
&lt;p&gt;Aunque tu script funcione, el Explorador de archivos, &lt;code&gt;cmd.exe&lt;/code&gt; y muchas otras aplicaciones (incluidas las utilidades que ejecutas desde Git Bash, si no declaran soporte) pueden seguir sin admitir rutas largas. Microsoft señala que el shell y el sistema de archivos tienen requisitos distintos, y que activar el registro solo afecta a las aplicaciones preparadas.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Un &lt;code&gt;FileNotFoundError&lt;/code&gt; con la carpeta existente y una ruta de más de 260 caracteres apunta al límite &lt;code&gt;MAX_PATH&lt;/code&gt; de Windows.&lt;/li&gt;
  &lt;li&gt;El directorio de trabajo tiene su propio límite: un &lt;code&gt;cwd&lt;/code&gt; largo hace fallar la creación del proceso.&lt;/li&gt;
  &lt;li&gt;Lo más fiable es trabajar desde una ruta corta (&lt;code&gt;C:\tmp\trabajo&lt;/code&gt;) y usar nombres cortos con un manifiesto.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;LongPathsEnabled&lt;/code&gt; y &lt;code&gt;core.longpaths&lt;/code&gt; ayudan solo a las aplicaciones y comandos que lo admiten; el prefijo &lt;code&gt;\\?\&lt;/code&gt; solo al código que lo usa.&lt;/li&gt;
  &lt;li&gt;Verifica con el valor de registro y una prueba que cree un archivo en una ruta de más de 260 caracteres.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation&quot;&gt;Microsoft: Maximum Path Length Limitation (MAX_PATH, prefijo &lt;code&gt;\\?\&lt;/code&gt;, &lt;code&gt;LongPathsEnabled&lt;/code&gt; y &lt;code&gt;longPathAware&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/using/windows.html#removing-the-max-path-limitation&quot;&gt;Python: Using Python on Windows, sección &quot;Removing the MAX_PATH Limitation&quot;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gitforwindows.org/git-cannot-create-a-file-or-directory-with-a-long-path.html&quot;&gt;Git for Windows: Git cannot create a file or directory with a long path (&lt;code&gt;core.longpaths&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/121215480435695088/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/filenotfounderror-por-ruta-larga-en.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/121215480435695088'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/121215480435695088'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/filenotfounderror-por-ruta-larga-en.html' title='FileNotFoundError por ruta larga en Windows (MAX_PATH)'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-9142316762045465155</id><published>2026-10-18T09:00:00.000-05:00</published><updated>2026-10-18T09:00:00.173-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Git"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>git status muestra todos los archivos modificados en WSL</title><content type='html'>&lt;p&gt;Haces un &lt;code&gt;merge&lt;/code&gt; o un &lt;code&gt;checkout&lt;/code&gt; con Git para Windows, vuelves a la terminal de WSL2 y &lt;code&gt;git status&lt;/code&gt; lista cientos de archivos como modificados, aunque no editaste ninguno:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git status --short
 M src/app.ts
 M src/lib/formato.ts
 M README.md
 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si el repositorio usa &lt;code&gt;git worktree&lt;/code&gt; y alguno se creó desde Windows, en WSL aparece además este error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fatal: not a git repository: //wsl.localhost/Ubuntu/home/usuario/mi-app/.git/worktrees/rama-x&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Y al intentar filtrar el ruido con &lt;code&gt;git status --ignore-cr-at-eol&lt;/code&gt;, Git responde:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error: unknown option `ignore-cr-at-eol&#39;
usage: git status [&amp;lt;options&amp;gt;] [--] [&amp;lt;pathspec&amp;gt;...]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Los tres síntomas tienen una causa común: dos instalaciones de Git (la de Windows y la de WSL) trabajan sobre el mismo árbol de archivos. Este artículo explica cómo confirmarlo, cómo limpiar el estado sin perder cambios reales y cómo evitar que se repita.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Git para Windows y Git dentro de WSL son instalaciones independientes, cada una con sus propios archivos de configuración. Es habitual que la de Windows tenga &lt;code&gt;core.autocrlf=true&lt;/code&gt; (el libro Pro Git recomienda ese valor en Windows) y que la de WSL no tenga ninguna conversión configurada. Compruébalo ejecutando el mismo comando en ambos lados:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --show-origin --get-all core.autocrlf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En Windows puede devolver algo como &lt;code&gt;file:C:/Program Files/Git/etc/gitconfig  true&lt;/code&gt;; en WSL, ninguna línea.&lt;/p&gt;
&lt;p&gt;Según la documentación de Git, &lt;code&gt;core.autocrlf=true&lt;/code&gt; equivale a poner &lt;code&gt;text=auto&lt;/code&gt; en todos los archivos y &lt;code&gt;core.eol=crlf&lt;/code&gt;: al hacer checkout o merge, Git para Windows escribe los archivos con CRLF y los convierte a LF al guardarlos en el índice, que sigue teniendo LF. Git en WSL, sin conversión, compara los bytes del disco (CRLF) con los del índice (LF) y concluye que el archivo cambió. Solo se ven afectados los archivos que esa operación reescribió: el número (100, 400...) depende del merge o del checkout, no del tamaño del repositorio.&lt;/p&gt;
&lt;p&gt;Para verlo, &lt;code&gt;git ls-files --eol&lt;/code&gt; muestra el fin de línea del índice (&lt;code&gt;i/&lt;/code&gt;) y del directorio de trabajo (&lt;code&gt;w/&lt;/code&gt;). Los archivos fantasma aparecen como &lt;code&gt;i/lf    w/crlf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La opción &lt;code&gt;--ignore-cr-at-eol&lt;/code&gt; pertenece a &lt;code&gt;git diff&lt;/code&gt; (ignora el retorno de carro al final de la línea al comparar), no a &lt;code&gt;git status&lt;/code&gt;; por eso este último responde &lt;code&gt;unknown option&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;El error de los worktrees tiene un origen parecido. Un worktree enlazado tiene un archivo &lt;code&gt;.git&lt;/code&gt; que apunta a un directorio privado dentro del &lt;code&gt;.git&lt;/code&gt; del repositorio principal (&lt;code&gt;.git/worktrees/&amp;lt;nombre&amp;gt;&lt;/code&gt;), y ese directorio guarda a su vez la ruta del worktree. Si el worktree lo creó Git para Windows, esas rutas se escribieron tal como Windows ve el repositorio (una ruta UNC como &lt;code&gt;//wsl.localhost/Ubuntu/...&lt;/code&gt; o una ruta de unidad como &lt;code&gt;C:/...&lt;/code&gt;), y Git en Linux no sabe resolverlas.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Confirma que son cambios fantasma y separa los reales.&lt;/strong&gt; Desde WSL, en la raíz del repositorio:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git diff --ignore-cr-at-eol --stat
git diff --ignore-cr-at-eol --name-only
git ls-files --eol | grep &#39;w/crlf&#39;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si el primer comando no imprime nada, todas las diferencias son de fin de línea. Si imprime archivos, esos sí tienen cambios reales: el segundo comando los lista y conviene conservarlos. El tercero muestra los archivos con CRLF en disco.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Restaura desde WSL los archivos afectados.&lt;/strong&gt; Desde una terminal de WSL, o desde PowerShell anteponiendo &lt;code&gt;wsl.exe&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wsl.exe -d Ubuntu -e bash -lc &quot;cd ~/mi-app &amp;amp;&amp;amp; git restore .&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;git restore .&lt;/code&gt; (equivalente a &lt;code&gt;git checkout -- .&lt;/code&gt; en versiones antiguas) descarta todos los cambios sin confirmar. Si el paso anterior mostró cambios reales, exclúyelos con un pathspec:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git restore -- . &#39;:(exclude)src/cambio-real.ts&#39;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Los archivos se reescriben con LF, que es lo que tiene el índice.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Fija el fin de línea con &lt;code&gt;.gitattributes&lt;/code&gt;.&lt;/strong&gt; En la raíz del repositorio:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Según la documentación de Git, &lt;code&gt;core.autocrlf&lt;/code&gt; solo se consulta cuando el atributo &lt;code&gt;text&lt;/code&gt; no está definido; con este archivo, las dos instalaciones de Git se comportan igual. La combinación &lt;code&gt;text=auto eol=lf&lt;/code&gt; requiere Git 2.10 o posterior. Las dos últimas líneas conservan CRLF en los scripts de Windows, que suelen necesitarlo. Después renormaliza y revisa antes de confirmar:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add .gitattributes
git add --renormalize .
git status
git commit -m &quot;chore: normalize line endings to LF&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si el repositorio tenía archivos guardados con CRLF en el índice (&lt;code&gt;git ls-files --eol | grep &#39;i/crlf&#39;&lt;/code&gt;), aparecerán en &lt;code&gt;git status&lt;/code&gt; como cambios preparados: es la conversión única a LF y es lo esperado.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Repara los worktrees creados desde Windows.&lt;/strong&gt; En WSL, desde el repositorio principal, indica la ruta Linux real del worktree:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree list
git worktree repair ~/mi-app-rama-x&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La documentación de &lt;code&gt;git worktree&lt;/code&gt; describe &lt;code&gt;repair&lt;/code&gt; justo para este caso: restablece la conexión entre el repositorio principal y los worktrees cuando sus archivos administrativos quedaron desactualizados. Si prefieres hacerlo a mano, el archivo &lt;code&gt;.git&lt;/code&gt; del worktree debe contener &lt;code&gt;gitdir: /home/usuario/mi-app/.git/worktrees/rama-x&lt;/code&gt;, y el archivo &lt;code&gt;gitdir&lt;/code&gt; dentro de &lt;code&gt;.git/worktrees/rama-x/&lt;/code&gt; debe contener la ruta Linux de ese &lt;code&gt;.git&lt;/code&gt;. Como última opción, usa &lt;code&gt;git worktree prune&lt;/code&gt; y vuelve a crear el worktree desde WSL con &lt;code&gt;git worktree add&lt;/code&gt;; el contenido sin confirmar del directorio viejo no se mueve solo, así que cópialo antes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Haz las operaciones que escriben desde un único Git.&lt;/strong&gt; Ejecuta &lt;code&gt;checkout&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt;, &lt;code&gt;rebase&lt;/code&gt;, &lt;code&gt;stash&lt;/code&gt; y &lt;code&gt;worktree add&lt;/code&gt; dentro de WSL, aunque edites los archivos con herramientas de Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wsl.exe -d Ubuntu -e bash -lc &quot;cd ~/mi-app &amp;amp;&amp;amp; git pull --ff-only&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Tampoco ejecutes sobre todo el repositorio formateadores ni conversiones de fin de línea del editor que escriban CRLF.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git status --short
git diff --ignore-cr-at-eol --stat
git ls-files --eol | grep -c &#39;w/crlf&#39;
git config --show-origin --get-all core.autocrlf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Los dos primeros no deben imprimir nada. El tercero debe dar &lt;code&gt;0&lt;/code&gt;, salvo por los &lt;code&gt;.bat&lt;/code&gt; y &lt;code&gt;.cmd&lt;/code&gt; si los tienes, que conservan CRLF a propósito. Después haz un &lt;code&gt;git checkout&lt;/code&gt; o un &lt;code&gt;git pull&lt;/code&gt; desde WSL y repite el &lt;code&gt;git status&lt;/code&gt;: debe seguir limpio. Por último, abre el repositorio con tus herramientas de Windows (editor, cliente gráfico) y confirma que tampoco muestran cambios.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;El repositorio está en &lt;code&gt;/mnt/c/...&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Funciona igual, pero la &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/wsl/filesystems&quot;&gt;documentación de Microsoft sobre sistemas de archivos en WSL&lt;/a&gt; recomienda no trabajar con los mismos archivos desde ambos sistemas operativos salvo que haya una razón, y guardarlos en el sistema de archivos donde corren las herramientas. Si trabajas en WSL, un repositorio en &lt;code&gt;~/mi-app&lt;/code&gt; deja a Git para Windows fuera de la ecuación.&lt;/p&gt;
&lt;h3&gt;No puedes confirmar un &lt;code&gt;.gitattributes&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Alinea las configuraciones. La &lt;a href=&quot;https://code.visualstudio.com/docs/remote/troubleshooting&quot;&gt;documentación de solución de problemas de VS Code&lt;/a&gt; propone, en Windows, &lt;code&gt;git config --global core.autocrlf input&lt;/code&gt; (convierte a LF al confirmar y no escribe CRLF) o &lt;code&gt;false&lt;/code&gt; (sin conversión), y avisa de que puede hacer falta volver a clonar para que el cambio surta efecto.&lt;/p&gt;
&lt;h3&gt;El diff muestra &lt;code&gt;old mode&lt;/code&gt; y &lt;code&gt;new mode&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Ya no son fines de línea sino cambios en el bit ejecutable. La documentación de &lt;code&gt;core.fileMode&lt;/code&gt; menciona que un repositorio visitado desde otro entorno puede perder ese bit y que en ese caso conviene ponerlo en &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Worktrees que se abren desde ambos lados&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git worktree&lt;/code&gt; admite enlaces con rutas relativas (&lt;code&gt;--relative-paths&lt;/code&gt; o &lt;code&gt;worktree.useRelativePaths&lt;/code&gt;), pensados para repositorios que se mueven entre entornos. La propia documentación avisa de que los Git antiguos no los entienden. Pruébalo primero en un repositorio de ensayo.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Cientos de archivos &quot;modificados&quot; sin cambios reales suelen ser CRLF escrito por Git para Windows (&lt;code&gt;core.autocrlf=true&lt;/code&gt;) y leído por un Git en WSL sin conversión.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;--ignore-cr-at-eol&lt;/code&gt; es una opción de &lt;code&gt;git diff&lt;/code&gt;: úsala con &lt;code&gt;--stat&lt;/code&gt; o &lt;code&gt;--name-only&lt;/code&gt; para separar lo real de lo fantasma.&lt;/li&gt;
  &lt;li&gt;Restaura desde WSL con &lt;code&gt;git restore .&lt;/code&gt;, excluyendo con un pathspec los archivos que sí cambiaste.&lt;/li&gt;
  &lt;li&gt;Evita la recaída con &lt;code&gt;* text=auto eol=lf&lt;/code&gt; en &lt;code&gt;.gitattributes&lt;/code&gt; y ejecutando las operaciones que escriben desde un solo Git.&lt;/li&gt;
  &lt;li&gt;Los worktrees creados desde Windows guardan rutas que Linux no resuelve: arréglalos con &lt;code&gt;git worktree repair &amp;lt;ruta-linux&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/gitattributes&quot;&gt;Git: &lt;code&gt;gitattributes&lt;/code&gt;, atributos &lt;code&gt;text&lt;/code&gt; y &lt;code&gt;eol&lt;/code&gt; y sección &quot;End-of-line conversion&quot;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot;&gt;Git: &lt;code&gt;git-worktree&lt;/code&gt;, subcomando &lt;code&gt;repair&lt;/code&gt; y rutas relativas&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-git&quot;&gt;Microsoft: Get started using Git on WSL (sección &quot;Git line endings&quot;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/9142316762045465155/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/git-status-muestra-todos-los-archivos.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/9142316762045465155'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/9142316762045465155'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/git-status-muestra-todos-los-archivos.html' title='git status muestra todos los archivos modificados en WSL'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-1912035360778157189</id><published>2026-10-17T15:00:00.000-05:00</published><updated>2026-10-17T15:00:00.120-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Docker"/><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><title type='text'>Docker build cache llena el disco: docker builder prune</title><content type='html'>&lt;p&gt;El disco de un servidor Linux está al 60 % y no sabes por qué. Las bases de datos pesan unos pocos MB, la aplicación apenas ocupa espacio y aun así el sistema de archivos raíz va llenándose con cada despliegue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       193G  115G   78G  60% /&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Antes de contratar un servidor más grande, revisa lo que Docker guarda en el disco. Cuando los despliegues construyen las imágenes en el propio servidor, la caché de compilación (&lt;em&gt;build cache&lt;/em&gt;) puede acumular decenas de GB sin que ningún contenedor ni imagen lo muestre.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;BuildKit, el motor de compilación de Docker, guarda el resultado intermedio de cada paso de un build para reutilizarlo en el siguiente. Esa caché es independiente de las imágenes, los contenedores y los volúmenes: no aparece en &lt;code&gt;docker images&lt;/code&gt; ni en &lt;code&gt;docker ps&lt;/code&gt;, y el tamaño de tus bases de datos no influye. Cada build que cambia capas añade registros nuevos.&lt;/p&gt;
&lt;p&gt;La documentación de Docker explica que BuildKit tiene un recolector de basura que limpia la caché de forma periódica según unas políticas, a diferencia de &lt;code&gt;docker builder prune&lt;/code&gt;, que lo hace a petición. Aun así, en la práctica la caché puede llegar a ocupar decenas de GB, como en este ejemplo, así que conviene medirla en lugar de asumir que se limpia sola. El comando que lo muestra es este:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker system df
TYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE
Images          ...
Containers      ...
Local Volumes   ...
Build Cache     ...     ...      107.7GB   100.1GB&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En este ejemplo, la fila &lt;code&gt;Build Cache&lt;/code&gt; ocupa 107,7 GB y 100,1 GB son recuperables. Las demás filas se omiten porque lo relevante es la diferencia de tamaño.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Diagnostica qué ocupa el espacio.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df -h /
docker system df
docker system df -v
docker buildx du&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;docker system df -v&lt;/code&gt; detalla el uso de cada imagen, contenedor y volumen. &lt;code&gt;docker buildx du&lt;/code&gt; lista los registros de la caché de compilación. Si &lt;code&gt;docker system df&lt;/code&gt; no explica el espacio, el consumo está fuera de Docker: revisa con &lt;code&gt;du -xh --max-depth=1 /var | sort -h&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Confirma qué necesitas conservar.&lt;/strong&gt; &lt;code&gt;docker builder prune&lt;/code&gt; solo elimina caché de compilación: no toca contenedores, imágenes ni volúmenes. Si guardas etiquetas de reversión (&lt;em&gt;rollback&lt;/em&gt;), son imágenes y se conservan; compruébalas con &lt;code&gt;docker image ls&lt;/code&gt;. El costo es que el siguiente build no tendrá caché y tardará más.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Elimina la caché de compilación.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker builder prune -af&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;-a&lt;/code&gt; amplía la limpieza a toda la caché sin uso (la referencia de buildx lo describe como incluir las imágenes internas y de frontend) y &lt;code&gt;-f&lt;/code&gt; evita la confirmación interactiva. Si prefieres conservar la caché reciente para que los próximos builds sigan siendo rápidos, usa un filtro por antigüedad:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker builder prune -af --filter until=168h&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;El valor es una duración (por ejemplo &lt;code&gt;24h&lt;/code&gt;); este ejemplo conserva lo usado en los últimos 7 días. También hay opciones por espacio, como &lt;code&gt;--reserved-space 10GB&lt;/code&gt;. En documentación antigua aparece &lt;code&gt;--keep-storage&lt;/code&gt;; el CLI reciente la marca como obsoleta y la reemplaza por &lt;code&gt;--reserved-space&lt;/code&gt;. Confirma las opciones de tu versión con &lt;code&gt;docker builder prune --help&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Programa la limpieza para que no vuelva a acumularse.&lt;/strong&gt; Puedes hacerlo al final del script o del flujo de despliegue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker compose up -d --build
docker builder prune -f --filter until=168h&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;O con una tarea mensual en &lt;code&gt;cron&lt;/code&gt; (en el &lt;code&gt;crontab&lt;/code&gt; de un usuario con acceso a Docker), por ejemplo el día 1 a las 03:00:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 3 1 * * docker builder prune -af --filter until=720h&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Otra opción es ajustar el recolector de basura de BuildKit en &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;, con un límite de tamaño para la caché. La documentación de Docker muestra esta forma:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;builder&quot;: {
    &quot;gc&quot;: {
      &quot;enabled&quot;: true,
      &quot;defaultKeepStorage&quot;: &quot;20GB&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Elige un valor acorde con tu disco y consulta en la documentación cómo aplicar el cambio de configuración del demonio.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;df -h /
docker system df
docker ps --format &quot;table {{.Names}}\t{{.Status}}&quot;
curl -s -o /dev/null -w &quot;%{http_code}\n&quot; https://example.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El uso del disco debe bajar de forma notable y la fila &lt;code&gt;Build Cache&lt;/code&gt; de &lt;code&gt;docker system df&lt;/code&gt; debe quedar cerca de cero. En el caso de referencia, el disco pasó de 115 GB usados (60 %) a 15 GB (8 %). Los contenedores deben seguir con el mismo tiempo en marcha (&lt;code&gt;Up 3 weeks&lt;/code&gt;, no &lt;code&gt;Up 5 seconds&lt;/code&gt;), lo que confirma que no se reiniciaron. Por último, las páginas deben responder &lt;code&gt;200&lt;/code&gt;. Cambia &lt;code&gt;https://example.com&lt;/code&gt; por la URL de tu aplicación.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;La diferencia con &lt;code&gt;docker system prune&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Según la documentación, &lt;code&gt;docker system prune&lt;/code&gt; elimina contenedores detenidos, redes sin uso, imágenes huérfanas (&lt;em&gt;dangling&lt;/em&gt;) y caché de compilación sin uso. Con &lt;code&gt;-a&lt;/code&gt; también borra todas las imágenes que no tenga asociadas un contenedor: en un servidor que conserva imágenes de reversión, eso las elimina. Con &lt;code&gt;--volumes&lt;/code&gt; añade los volúmenes anónimos sin uso. Por defecto no borra volúmenes, pero conviene no ejecutarlo a ciegas en producción. &lt;code&gt;docker builder prune&lt;/code&gt; es la opción acotada: solo caché.&lt;/p&gt;
&lt;h3&gt;Los logs de los contenedores&lt;/h3&gt;
&lt;p&gt;Si &lt;code&gt;docker system df&lt;/code&gt; no muestra una caché grande, revisa el tamaño de los logs. Con el controlador &lt;code&gt;json-file&lt;/code&gt;, &lt;code&gt;max-size&lt;/code&gt; viene sin límite por defecto. Puedes activar la rotación en &lt;code&gt;daemon.json&lt;/code&gt; con &lt;code&gt;log-opts&lt;/code&gt; (&lt;code&gt;max-size&lt;/code&gt; y &lt;code&gt;max-file&lt;/code&gt;); la documentación indica que se aplica a los contenedores creados después de reiniciar Docker, no a los existentes.&lt;/p&gt;
&lt;h3&gt;Imágenes antiguas&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;docker image prune&lt;/code&gt; elimina las imágenes huérfanas; con &lt;code&gt;-a&lt;/code&gt; elimina todas las que no usa ningún contenedor. Antes de usar &lt;code&gt;-a&lt;/code&gt;, asegúrate de que no necesitas esas imágenes para volver a una versión anterior.&lt;/p&gt;
&lt;h3&gt;Otro constructor de buildx&lt;/h3&gt;
&lt;p&gt;Si usas un constructor distinto del predeterminado, la limpieza se aplica al constructor seleccionado. La opción &lt;code&gt;--builder&lt;/code&gt; permite indicar otro. En versiones recientes del CLI, la ayuda de &lt;code&gt;docker builder prune&lt;/code&gt; muestra el uso &lt;code&gt;docker buildx prune&lt;/code&gt;, por lo que la referencia de buildx citada abajo también aplica.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Un disco lleno con datos pequeños suele deberse a la caché de compilación; confírmalo con &lt;code&gt;docker system df&lt;/code&gt; (fila &lt;code&gt;Build Cache&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;docker builder prune -af&lt;/code&gt; elimina esa caché sin tocar contenedores, imágenes ni volúmenes; el siguiente build será más lento.&lt;/li&gt;
  &lt;li&gt;Con &lt;code&gt;--filter until=...&lt;/code&gt; o &lt;code&gt;--reserved-space&lt;/code&gt; conservas la caché reciente; revisa las opciones con &lt;code&gt;--help&lt;/code&gt; en tu versión.&lt;/li&gt;
  &lt;li&gt;Programa la limpieza (fin del despliegue o &lt;code&gt;cron&lt;/code&gt; mensual) o limita el recolector de BuildKit en &lt;code&gt;daemon.json&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;No uses &lt;code&gt;docker system prune -a&lt;/code&gt; a ciegas: puede borrar imágenes de reversión.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/reference/cli/docker/buildx/prune/&quot;&gt;Docker: &lt;code&gt;docker buildx prune&lt;/code&gt; (opciones y filtros)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/reference/cli/docker/system/prune/&quot;&gt;Docker: &lt;code&gt;docker system prune&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/build/cache/garbage-collection/&quot;&gt;Docker: Build garbage collection (recolector de basura de BuildKit)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/1912035360778157189/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/docker-build-cache-llena-el-disco.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/1912035360778157189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/1912035360778157189'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/11/docker-build-cache-llena-el-disco.html' title='Docker build cache llena el disco: docker builder prune'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-3303460868804602091</id><published>2026-10-17T09:00:00.000-05:00</published><updated>2026-10-17T09:00:00.126-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Bases de datos"/><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Prisma"/><title type='text'>Prisma P2028: Transaction already closed (timeout)</title><content type='html'>&lt;p&gt;Una operación grande falla en producción con este error de Prisma, aunque en las pruebas con un solo elemento funcionaba:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PrismaClientKnownRequestError:
Transaction API error: Transaction already closed: A query cannot be executed on an expired transaction. The timeout for this transaction was 5000 ms, however 7413 ms passed since the start of the transaction. Consider increasing the interactive transaction timeout or doing less work in the transaction.
  code: &#39;P2028&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Según la operación que llegue tarde, el texto dice &lt;code&gt;A query&lt;/code&gt; o &lt;code&gt;A commit&lt;/code&gt;, pero el código es siempre &lt;code&gt;P2028&lt;/code&gt;. Este artículo cubre el caso de &lt;code&gt;prisma.$transaction(async (tx) =&amp;gt; ...)&lt;/code&gt; en Prisma Client 6 con PostgreSQL, y la combinación de causas que lo provoca con más frecuencia: usar el cliente global dentro de la transacción y hacer una consulta por cada elemento.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Una transacción interactiva (&lt;code&gt;$transaction&lt;/code&gt; con una función asíncrona) permanece abierta mientras la función se ejecuta. La documentación de Prisma define dos límites: &lt;code&gt;timeout&lt;/code&gt;, el tiempo máximo que la transacción puede ejecutarse antes de cancelarse y revertirse (5 segundos por defecto), y &lt;code&gt;maxWait&lt;/code&gt;, el tiempo máximo que Prisma Client espera para obtener la transacción de la base de datos (2 segundos por defecto). Cuando se supera &lt;code&gt;timeout&lt;/code&gt;, las consultas posteriores fallan con &lt;code&gt;P2028&lt;/code&gt;, cuya descripción oficial es &quot;Transaction API error&quot;.&lt;/p&gt;
&lt;p&gt;Con un elemento, la función termina en milisegundos y nunca se nota el límite. Con cientos, se suman dos problemas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Cliente global dentro de la transacción.&lt;/strong&gt; La documentación indica que las llamadas hechas sobre la instancia &lt;code&gt;tx&lt;/code&gt; quedan dentro de la transacción; las que se hacen con otro cliente no forman parte de ella. Si un servicio importa el &lt;code&gt;prisma&lt;/code&gt; global y lo usa en medio del callback, esas consultas se ejecutan fuera de la transacción (no se revierten con el resto) y, mientras tanto, la transacción sigue abierta consumiendo tiempo.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consultas N+1.&lt;/strong&gt; Calcular el número de secuencia de cada elemento con un &lt;code&gt;COUNT(*)&lt;/code&gt; completo significa una consulta adicional por elemento, todas en serie. Con cientos de elementos, esos viajes a la base de datos pueden bastar para pasar de los 5 segundos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La propia documentación advierte que mantener transacciones abiertas durante mucho tiempo perjudica el rendimiento y puede causar bloqueos mutuos, y recomienda evitar peticiones de red y consultas lentas dentro de la función y mantener la transacción lo más corta posible.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Localiza el trabajo innecesario dentro del callback.&lt;/strong&gt; Este es el patrón problemático, simplificado:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await prisma.$transaction(async (tx) =&amp;gt; {
  for (const linea of lineas) {
    // N+1: un COUNT(*) completo por cada línea
    const total = await tx.movimiento.count({ where: { loteId } });

    await tx.movimiento.create({
      data: { loteId, productoId: linea.productoId, cantidad: linea.cantidad, numero: total + 1 },
    });

    // este servicio usa el cliente global `prisma` por dentro, no `tx`
    await servicioStock.ajustar(linea.productoId, linea.cantidad);
  }
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Pasa &lt;code&gt;tx&lt;/code&gt; a todo lo que deba ocurrir dentro de la transacción.&lt;/strong&gt; Las funciones auxiliares reciben el cliente transaccional como parámetro en lugar de importar el global:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Prisma } from &quot;@prisma/client&quot;;

export async function ajustarStock(tx: Prisma.TransactionClient, lineas: Linea[]) {
  for (const linea of lineas) {
    await tx.stock.update({
      where: { productoId: linea.productoId },
      data: { cantidad: { increment: linea.cantidad } },
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;Prisma.TransactionClient&lt;/code&gt; es el tipo que genera Prisma Client para &lt;code&gt;tx&lt;/code&gt;; la documentación oficial de la versión 6 no lo describe, así que confirma que existe en tu versión.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Calcula una sola vez y deriva el resto en memoria.&lt;/strong&gt; Cuenta antes del bucle y asigna los números con el índice. Si puedes, inserta en bloque con &lt;code&gt;createMany&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const base = await tx.movimiento.count({ where: { loteId } });

await tx.movimiento.createMany({
  data: lineas.map((linea, i) =&amp;gt; ({
    loteId,
    productoId: linea.productoId,
    cantidad: linea.cantidad,
    numero: base + i + 1,
  })),
});&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si el número debe ser único aunque dos procesos reciban a la vez, contar filas no lo garantiza: apóyate en una secuencia de la base de datos o en una restricción única con reintento.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Saca de la transacción lo que no necesita ser atómico.&lt;/strong&gt; Dentro queda lo que debe confirmarse o revertirse en conjunto. Auditoría, notificaciones, llamadas HTTP o invalidación de caché se ejecutan después del &lt;code&gt;commit&lt;/code&gt;, con el valor que devuelve &lt;code&gt;$transaction&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const resultado = await prisma.$transaction(
  async (tx) =&amp;gt; {
    const base = await tx.movimiento.count({ where: { loteId } });
    // construirMovimientos arma el arreglo con `numero: base + i + 1`, como en el paso 3
    await tx.movimiento.createMany({ data: construirMovimientos(lineas, loteId, base) });
    await ajustarStock(tx, lineas);
    return { loteId, cantidadLineas: lineas.length };
  },
  { timeout: 30000, maxWait: 10000 },
);

// la transacción ya está confirmada
await registrarAuditoria(resultado);
await notificarRecepcion(resultado);&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si uno de esos pasos posteriores falla, los datos ya están guardados: hazlos reintentables o idempotentes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Añade un margen con &lt;code&gt;timeout&lt;/code&gt; y &lt;code&gt;maxWait&lt;/code&gt; después de optimizar, no en lugar de optimizar.&lt;/strong&gt; Subir el límite no hace más rápido el código, solo retrasa el fallo, y una transacción larga mantiene bloqueos. Úsalo como colchón. Si lo necesitas en todas las transacciones, el constructor acepta &lt;code&gt;transactionOptions&lt;/code&gt; con los mismos campos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const prisma = new PrismaClient({
  transactionOptions: { timeout: 30000, maxWait: 10000 },
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Prueba con un volumen realista.&lt;/strong&gt; Un caso con un solo elemento no detecta este problema. Ejecuta la operación con cientos de elementos, igual que en producción.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Ejecuta la operación con un volumen parecido al real y mide el tiempo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.time(&quot;recepcion&quot;);
await recibirLote(loteId, lineas); // 300-400 elementos
console.timeEnd(&quot;recepcion&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No debe aparecer &lt;code&gt;P2028&lt;/code&gt;. Comprueba además la integridad de los datos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT numero, COUNT(*) FROM movimiento WHERE lote_id = 1 GROUP BY numero HAVING COUNT(*) &amp;gt; 1;
SELECT SUM(cantidad) FROM movimiento WHERE lote_id = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La primera consulta no debe devolver filas (números únicos) y la segunda debe coincidir con la suma de las cantidades enviadas. En una prueba local con 350 elementos, la operación terminó en unos 7,4 segundos sin error. Ese tiempo supera los 5 segundos por defecto, lo que confirma que el límite también influía y que el margen del paso 5 era necesario.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Una primera corrección parcial no basta&lt;/h3&gt;
&lt;p&gt;Si solo cambias el cliente global por &lt;code&gt;tx&lt;/code&gt; y pruebas con un elemento, el error seguirá en producción. Revisa ambas causas y prueba con volumen.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;maxWait&lt;/code&gt; agotado&lt;/h3&gt;
&lt;p&gt;Si en lugar de superar el tiempo de ejecución no se logra iniciar la transacción a tiempo, el límite que se agota es &lt;code&gt;maxWait&lt;/code&gt;. Revisa también el pool de conexiones y que no haya muchas transacciones largas a la vez.&lt;/p&gt;
&lt;h3&gt;P2024: agotado el pool de conexiones&lt;/h3&gt;
&lt;p&gt;La descripción oficial de &lt;code&gt;P2024&lt;/code&gt; es &quot;Timed out fetching a new connection from the connection pool&quot;. Puede aparecer cuando muchas operaciones concurrentes mantienen conexiones ocupadas, por ejemplo transacciones largas.&lt;/p&gt;
&lt;h3&gt;Consultas sin &lt;code&gt;await&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Una consulta lanzada dentro del callback sin &lt;code&gt;await&lt;/code&gt; (por ejemplo en un &lt;code&gt;forEach&lt;/code&gt;) puede ejecutarse cuando la transacción ya terminó y producir un error similar. Usa &lt;code&gt;for...of&lt;/code&gt; con &lt;code&gt;await&lt;/code&gt;, o &lt;code&gt;Promise.all&lt;/code&gt; sobre un &lt;code&gt;map&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;P2034: conflicto de escritura o bloqueo mutuo&lt;/h3&gt;
&lt;p&gt;Es otro error de transacciones: &quot;Transaction failed due to a write conflict or a deadlock&quot;. Su descripción oficial pide reintentar la transacción.&lt;/p&gt;
&lt;h3&gt;Versión de la documentación&lt;/h3&gt;
&lt;p&gt;Este artículo se basa en la documentación de Prisma 6. La documentación publicada para versiones más recientes puede describir una API de transacciones distinta, así que confirma que consultas la versión de tu proyecto.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;P2028&lt;/code&gt; &quot;Transaction already closed&quot; significa que la transacción interactiva superó su &lt;code&gt;timeout&lt;/code&gt; (5 s por defecto) antes de que llegara otra consulta.&lt;/li&gt;
  &lt;li&gt;Dentro de &lt;code&gt;$transaction&lt;/code&gt; usa siempre &lt;code&gt;tx&lt;/code&gt;; un servicio que importe el cliente global ejecuta fuera de la transacción.&lt;/li&gt;
  &lt;li&gt;Elimina el N+1: calcula el conteo base una vez y deriva los números en memoria, con &lt;code&gt;createMany&lt;/code&gt; si es posible.&lt;/li&gt;
  &lt;li&gt;Mueve a después del &lt;code&gt;commit&lt;/code&gt; lo que no necesita ser atómico y añade &lt;code&gt;{ timeout, maxWait }&lt;/code&gt; solo como margen.&lt;/li&gt;
  &lt;li&gt;Prueba siempre con un volumen realista, no con un único elemento.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.prisma.io/docs/v6/orm/prisma-client/queries/transactions&quot;&gt;Prisma 6: Transactions and batch queries (transacciones interactivas, &lt;code&gt;maxWait&lt;/code&gt; y &lt;code&gt;timeout&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.prisma.io/docs/v6/orm/reference/error-reference&quot;&gt;Prisma 6: Error message reference (&lt;code&gt;P2028&lt;/code&gt;, &lt;code&gt;P2024&lt;/code&gt; y &lt;code&gt;P2034&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.prisma.io/docs/v6/orm/reference/prisma-client-reference&quot;&gt;Prisma 6: Prisma Client API reference (opción &lt;code&gt;transactionOptions&lt;/code&gt; del constructor)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/3303460868804602091/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/prisma-p2028-transaction-already-closed.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3303460868804602091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3303460868804602091'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/prisma-p2028-transaction-already-closed.html' title='Prisma P2028: Transaction already closed (timeout)'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-503038469179600683</id><published>2026-10-16T15:00:00.000-05:00</published><updated>2026-10-16T15:00:00.198-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="CSS"/><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Next.js"/><title type='text'>Tailwind 4: las utilidades no se aplican (CSS sin @layer)</title><content type='html'>&lt;p&gt;Migras a Tailwind CSS 4 (o empiezas un proyecto nuevo, por ejemplo con shadcn/ui en Next.js) y algunas utilidades dejan de tener efecto. La clase está en el HTML y la regla aparece en el CSS compilado, pero el estilo calculado sigue siendo el global:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;border border-red-500&quot;&amp;gt;Aviso&amp;lt;/div&amp;gt;
&amp;lt;h1 class=&quot;text-white&quot;&amp;gt;Título&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;getComputedStyle(document.querySelector(&quot;.border-red-500&quot;)).borderTopColor
// el gris de tu regla global, no el rojo de la utilidad&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pasa con &lt;code&gt;border-*&lt;/code&gt;, &lt;code&gt;divide-*&lt;/code&gt;, &lt;code&gt;text-*&lt;/code&gt; y cualquier utilidad que choque con una regla global de tu hoja de estilos. No hay mensaje de error: en las herramientas de desarrollo del navegador, la declaración de la utilidad suele aparecer tachada. Este artículo explica la causa y cómo corregirla.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Tailwind 4 usa capas de cascada nativas de CSS. El archivo que importa con &lt;code&gt;@import &quot;tailwindcss&quot;&lt;/code&gt; declara el orden &lt;code&gt;theme, base, components, utilities&lt;/code&gt;, inyecta Preflight en la capa &lt;code&gt;base&lt;/code&gt; y coloca todas las utilidades en la capa &lt;code&gt;utilities&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La regla de CSS que rige aquí está documentada en MDN: los estilos que no están dentro de ninguna capa siempre prevalecen sobre los estilos declarados en capas, para declaraciones normales (sin &lt;code&gt;!important&lt;/code&gt;). Además, una vez establecido el orden de capas, la especificidad y el orden de aparición dejan de contar entre capas distintas. Por eso, una regla global como esta gana a cualquier utilidad, aunque la utilidad tenga una clase y la regla global solo un selector &lt;code&gt;*&lt;/code&gt; o &lt;code&gt;h1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* {
  border-color: hsl(var(--border));
}

h1, h2, h3 {
  color: var(--text-heading);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En Tailwind 3 esto no ocurría con estos selectores. Allí no se usaban capas nativas (la directiva &lt;code&gt;@layer&lt;/code&gt; la procesaba Tailwind), y la clase de la utilidad ganaba por especificidad al selector &lt;code&gt;*&lt;/code&gt; o &lt;code&gt;h1&lt;/code&gt;. En Tailwind 4 la comparación se decide antes, por capas, y el CSS sin capa gana. Si pegas el mismo código en un proyecto nuevo, falla sin avisar.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Localiza las reglas sin capa.&lt;/strong&gt; Abre tu hoja de estilos global (en Next.js suele ser &lt;code&gt;app/globals.css&lt;/code&gt;) y busca selectores que estén fuera de cualquier &lt;code&gt;@layer&lt;/code&gt;: &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, etc. Las que fijan propiedades que las utilidades también controlan (color, borde, fuente, fondo) son las sospechosas.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Muévelas a &lt;code&gt;@layer base&lt;/code&gt;.&lt;/strong&gt; Es la capa que Tailwind reserva para los estilos por defecto de elementos y la que usa su documentación para ese fin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@import &quot;tailwindcss&quot;;

@layer base {
  * {
    border-color: hsl(var(--border));
  }

  h1, h2, h3 {
    color: var(--text-heading);
  }
}&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La guía de instalación manual de shadcn/ui sigue el mismo patrón: sus reglas de &lt;code&gt;*&lt;/code&gt; y &lt;code&gt;body&lt;/code&gt; están dentro de &lt;code&gt;@layer base&lt;/code&gt;. Las variables CSS de &lt;code&gt;:root&lt;/code&gt; y &lt;code&gt;.dark&lt;/code&gt; no compiten con las utilidades, así que no hace falta moverlas; la guía de migración de shadcn/ui a Tailwind 4 incluso las saca de &lt;code&gt;@layer base&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No confundas acotar con dar prioridad.&lt;/strong&gt; Si una regla no debe afectar a todo el documento, limita su alcance (por ejemplo &lt;code&gt;.contenido h1&lt;/code&gt;), pero déjala igualmente dentro de una capa. Acotar el selector no devuelve la prioridad a las utilidades: una regla sin capa gana sin importar lo específico que sea su selector. Si quieres que sea un estilo de componente que las utilidades puedan sobrescribir, usa &lt;code&gt;@layer components&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Pon en una capa el CSS de terceros.&lt;/strong&gt; Una hoja de estilos importada sin capa tiene el mismo efecto. Impórtala dentro de una capa:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@import &quot;tailwindcss&quot;;
@import &quot;libreria-ui/estilos.css&quot; layer(components);&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Revisa las pantallas después del cambio.&lt;/strong&gt; Elementos que se veían grises por error ahora mostrarán el color real de su utilidad. Un caso típico: un spinner con &lt;code&gt;border-white&lt;/code&gt; sobre un fondo claro, que antes se veía gris y ahora queda invisible. Recorre las pantallas en modo claro y oscuro.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Mide el estilo calculado antes y después del cambio, en modo claro y en modo oscuro (alterna el modo como lo haga tu proyecto, por ejemplo con la clase &lt;code&gt;dark&lt;/code&gt; en &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;). En la consola del navegador:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (const sel of [&quot;.border-red-500&quot;, &quot;.border-info\\/30&quot;, &quot;h1.text-white&quot;]) {
  const el = document.querySelector(sel);
  if (el) {
    const estilo = getComputedStyle(el);
    console.log(sel, estilo.borderTopColor, estilo.color);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Antes de la corrección, todos los bordes devuelven el mismo gris global y el título conserva su color global. Después, cada elemento devuelve el color de su utilidad. Para descartar regresiones, ejecuta también el lint, la comprobación de tipos y la compilación de producción de tu proyecto.&lt;/p&gt;
&lt;p&gt;Si comparas dos versiones del CSS compilado desde un script de Node con &lt;code&gt;@tailwindcss/postcss&lt;/code&gt;, ejecuta cada una en un proceso distinto. El plugin guarda en caché el compilador por archivo de entrada y lo reutiliza entre compilaciones, así que dos compilaciones en el mismo proceso pueden no ser independientes.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Los bordes se ven del color del texto tras migrar&lt;/h3&gt;
&lt;p&gt;No es este problema. En Tailwind 4, el color de borde por defecto de &lt;code&gt;border-*&lt;/code&gt; y &lt;code&gt;divide-*&lt;/code&gt; pasó a ser &lt;code&gt;currentColor&lt;/code&gt; (en Tailwind 3 era &lt;code&gt;gray-200&lt;/code&gt;). La guía de actualización ofrece una regla de compatibilidad que, de nuevo, va dentro de &lt;code&gt;@layer base&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-gray-200, currentColor);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Una clase propia en &lt;code&gt;@layer utilities&lt;/code&gt; deja de funcionar con variantes&lt;/h3&gt;
&lt;p&gt;En Tailwind 4, &lt;code&gt;@layer utilities&lt;/code&gt; ya no registra utilidades propias: la guía de actualización indica usar &lt;code&gt;@utility&lt;/code&gt; para que funcionen con variantes como &lt;code&gt;hover&lt;/code&gt; o &lt;code&gt;lg&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;!important&lt;/code&gt; como parche&lt;/h3&gt;
&lt;p&gt;MDN explica que &lt;code&gt;!important&lt;/code&gt; invierte el orden: las declaraciones importantes dentro de una capa ganan a las importantes fuera de ella. Aun así, forzar cada utilidad es un parche frágil. Es mejor corregir la capa de la regla global.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Si una utilidad de Tailwind 4 está en el CSS compilado pero no se aplica, busca reglas globales fuera de &lt;code&gt;@layer&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;El CSS sin capa gana al CSS en capas, sin importar la especificidad; las utilidades de Tailwind 4 están en &lt;code&gt;@layer utilities&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Mueve esas reglas a &lt;code&gt;@layer base&lt;/code&gt; (o a &lt;code&gt;components&lt;/code&gt;) e importa el CSS de terceros con &lt;code&gt;layer(...)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Revisa las pantallas: los elementos que parecían grises ahora muestran su color real, y algunos (como un borde blanco) pueden dejar de verse.&lt;/li&gt;
  &lt;li&gt;Verifica con &lt;code&gt;getComputedStyle&lt;/code&gt; antes y después, en modo claro y oscuro.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@layer&quot;&gt;MDN: &lt;code&gt;@layer&lt;/code&gt; (precedencia de estilos con y sin capa)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/docs/upgrade-guide&quot;&gt;Tailwind CSS: Upgrade guide (capas nativas, color de borde por defecto y &lt;code&gt;@utility&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/docs/adding-custom-styles&quot;&gt;Tailwind CSS: Adding custom styles (&lt;code&gt;@layer base&lt;/code&gt; y &lt;code&gt;@layer components&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/503038469179600683/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/tailwind-4-las-utilidades-no-se-aplican.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/503038469179600683'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/503038469179600683'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/tailwind-4-las-utilidades-no-se-aplican.html' title='Tailwind 4: las utilidades no se aplican (CSS sin @layer)'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-3078634234323405947</id><published>2026-10-16T09:00:00.000-05:00</published><updated>2026-10-16T09:00:00.224-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Prisma"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>Prisma generate EPERM: query_engine-windows.dll.node</title><content type='html'>&lt;p&gt;Ejecutas &lt;code&gt;prisma generate&lt;/code&gt; en Windows (a mano, en un script de instalación o dentro de un hook de Git) y falla con un error como este:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EPERM: operation not permitted, rename &#39;C:\proyectos\mi-app\node_modules\...\query_engine-windows.dll.node.tmp1234&#39; -&amp;gt; &#39;C:\proyectos\mi-app\node_modules\...\query_engine-windows.dll.node&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Los puntos suspensivos sustituyen la parte intermedia de la ruta, que cambia según el gestor de paquetes (npm, pnpm, Yarn). Si el comando forma parte de un hook &lt;code&gt;pre-push&lt;/code&gt;, el error bloquea el push aunque el lint, los tests y el build pasen. Este artículo explica qué proceso tiene bloqueado el archivo y cómo liberarlo sin matar todos los procesos de Node.js del equipo.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;El cliente de Prisma incluye el motor de consultas (query engine), un módulo nativo con extensión &lt;code&gt;.node&lt;/code&gt; que Node.js carga en el proceso. La documentación de Prisma indica que, en las versiones con motor en Rust, el cliente carga el motor como una librería Node-API por defecto, y que &lt;code&gt;prisma generate&lt;/code&gt; deja los archivos del motor junto al cliente generado.&lt;/p&gt;
&lt;p&gt;Al regenerar el cliente, Prisma escribe primero un archivo temporal con sufijo &lt;code&gt;.tmpNNNN&lt;/code&gt; y luego lo renombra encima del archivo del motor. Si otro proceso &lt;code&gt;node.exe&lt;/code&gt; tiene ese archivo cargado, Windows no permite reemplazarlo y el renombrado falla con &lt;code&gt;EPERM&lt;/code&gt;. Los procesos que suelen mantenerlo cargado son:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;el servidor de desarrollo (&lt;code&gt;next dev&lt;/code&gt;, &lt;code&gt;nest start --watch&lt;/code&gt; y similares),&lt;/li&gt;
  &lt;li&gt;un worker o script en segundo plano que usa el cliente de Prisma,&lt;/li&gt;
  &lt;li&gt;la sesión de otro editor o terminal que tiene el proyecto en ejecución.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El mensaje habla de permisos, pero la causa es un archivo en uso. Esto se puede reproducir sin Prisma: si un proceso carga una DLL y otro intenta renombrar un archivo encima de ella, Node.js devuelve &lt;code&gt;EPERM&lt;/code&gt; en la operación &lt;code&gt;rename&lt;/code&gt;, y el archivo temporal queda huérfano en la carpeta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alcance:&lt;/strong&gt; el error pertenece a la arquitectura con el motor de consultas en Rust. Prisma ORM 7 hizo que el cliente sin Rust fuera el predeterminado y eliminó los motores de tipo librería y binario, así que este artículo aplica a Prisma 6 y versiones anteriores.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Detén el servidor de desarrollo y Prisma Studio de la forma habitual&lt;/strong&gt; (Ctrl+C en su terminal) y cierra otras sesiones del proyecto. Si el error persiste, localiza los procesos &lt;code&gt;node.exe&lt;/code&gt; que pertenecen a ese proyecto, y solo a ese. Con PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$proyecto = &#39;C:\proyectos\mi-app&#39;

$procesos = Get-CimInstance Win32_Process -Filter &quot;Name = &#39;node.exe&#39;&quot; |
  Where-Object { $_.CommandLine -and $_.CommandLine.IndexOf($proyecto, [StringComparison]::OrdinalIgnoreCase) -ge 0 }

$procesos | Select-Object ProcessId, CommandLine | Format-List&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La clase &lt;code&gt;Win32_Process&lt;/code&gt; expone la línea de comandos de cada proceso en la propiedad &lt;code&gt;CommandLine&lt;/code&gt;; el filtro conserva solo los que mencionan la ruta de tu proyecto. Revisa la lista antes de continuar: un proceso cuya línea de comandos no incluya la ruta no aparecerá aquí.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Deténlos.&lt;/strong&gt; Primero una simulación con &lt;code&gt;-WhatIf&lt;/code&gt; y luego la acción real:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$procesos | ForEach-Object { Stop-Process -Id $_.ProcessId -WhatIf }
$procesos | ForEach-Object { Stop-Process -Id $_.ProcessId }&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Borra el archivo &lt;code&gt;.tmp&lt;/code&gt; huérfano.&lt;/strong&gt; Desde la raíz del proyecto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ChildItem -Path .\node_modules -Recurse -Force -Filter &#39;query_engine-windows.dll.node.tmp*&#39; | Remove-Item -Force&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Puede tardar unos segundos en proyectos con muchas dependencias. No toques el archivo &lt;code&gt;query_engine-windows.dll.node&lt;/code&gt; sin sufijo: &lt;code&gt;prisma generate&lt;/code&gt; lo reemplaza por ti.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Regenera el cliente.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx prisma generate&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Con pnpm sería &lt;code&gt;pnpm prisma generate&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;prisma generate&lt;/code&gt; termina sin el error &lt;code&gt;EPERM&lt;/code&gt; y con el mensaje de que el cliente se generó. En un caso real tardó menos de un segundo.&lt;/li&gt;
  &lt;li&gt;No quedan archivos temporales:
&lt;pre&gt;&lt;code&gt;Get-ChildItem -Path .\node_modules -Recurse -Force -Filter &#39;query_engine-windows.dll.node.tmp*&#39;&lt;/code&gt;&lt;/pre&gt;
    El comando no debe devolver nada.&lt;/li&gt;
  &lt;li&gt;El flujo que fallaba vuelve a funcionar: haz &lt;code&gt;git push&lt;/code&gt; y comprueba que el hook &lt;code&gt;pre-push&lt;/code&gt; se ejecuta completo (lint, tests, build y generación del cliente) y que el push termina.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;No uses &lt;code&gt;taskkill /im node.exe&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Ese comando termina todos los procesos con ese nombre de imagen, no solo los de tu proyecto: también otros servidores de desarrollo, herramientas y extensiones que usan Node.js. Por eso el filtro por línea de comandos del paso 1 es mejor: solo detienes procesos de ese proyecto.&lt;/p&gt;
&lt;h3&gt;No uses &lt;code&gt;--no-verify&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Saltarte el hook con &lt;code&gt;git push --no-verify&lt;/code&gt; hace desaparecer el síntoma, pero también las comprobaciones que el hook existe para garantizar. El problema real (un proceso con el archivo en uso) sigue ahí y reaparecerá en la siguiente regeneración.&lt;/p&gt;
&lt;h3&gt;Si el error continúa&lt;/h3&gt;
&lt;p&gt;Normalmente significa que algún proceso sigue manteniendo cargado el motor. Puede ser un &lt;code&gt;node.exe&lt;/code&gt; cuya línea de comandos no menciona la ruta de tu proyecto, o una herramienta que mantiene abierto el proyecto. Cierra las ventanas de editor y terminales que lo usen y repite el paso 1 con una parte más corta de la ruta. Como último recurso, reiniciar la sesión de Windows libera todos los archivos cargados.&lt;/p&gt;
&lt;h3&gt;Cómo evitarlo&lt;/h3&gt;
&lt;p&gt;Antes de cambiar de rama, actualizar Prisma o ejecutar &lt;code&gt;prisma generate&lt;/code&gt;, detén el servidor de desarrollo del proyecto. Es un solo paso y evita todo este recorrido.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;El &lt;code&gt;EPERM&lt;/code&gt; en &lt;code&gt;rename&lt;/code&gt; de &lt;code&gt;query_engine-windows.dll.node&lt;/code&gt; significa que otro proceso &lt;code&gt;node.exe&lt;/code&gt; tiene cargado el motor y Windows no deja reemplazarlo.&lt;/li&gt;
  &lt;li&gt;Identifica los procesos de tu proyecto con &lt;code&gt;Get-CimInstance Win32_Process&lt;/code&gt;, filtrando &lt;code&gt;CommandLine&lt;/code&gt; por la ruta, y detén solo esos.&lt;/li&gt;
  &lt;li&gt;Borra el &lt;code&gt;.tmp&lt;/code&gt; huérfano y ejecuta de nuevo &lt;code&gt;prisma generate&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;No uses &lt;code&gt;taskkill /im node.exe&lt;/code&gt; ni &lt;code&gt;--no-verify&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;El caso aplica al motor en Rust (Prisma 6 y anteriores).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.prisma.io/docs/orm/v6/more/internals/engines&quot;&gt;Prisma ORM v6: motores (Engines)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.prisma.io/changelog/2025-11-19&quot;&gt;Prisma ORM v7.0.0: el cliente sin Rust pasa a ser el predeterminado&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process&quot;&gt;Microsoft Learn: clase Win32_Process&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/3078634234323405947/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/prisma-generate-eperm-queryengine.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3078634234323405947'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3078634234323405947'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/prisma-generate-eperm-queryengine.html' title='Prisma generate EPERM: query_engine-windows.dll.node'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-6515916442590703707</id><published>2026-10-15T15:00:00.000-05:00</published><updated>2026-10-15T15:00:00.185-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>UnicodeEncodeError: &#39;charmap&#39; codec can&#39;t encode: solución</title><content type='html'>&lt;p&gt;Un script de Python que funciona sin problemas en otra máquina falla en Windows al imprimir texto con emojis o símbolos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Traceback (most recent call last):
  File &quot;informe.py&quot;, line 3, in &amp;lt;module&amp;gt;
    print(&quot;\U0001F550 \u2264&quot;)
  File &quot;...\Lib\encodings\cp1252.py&quot;, line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: &#39;charmap&#39; codec can&#39;t encode character &#39;\U0001f550&#39; in position 0: character maps to &amp;lt;undefined&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El carácter puede ser otro (por ejemplo &lt;code&gt;&#39;\u2264&#39;&lt;/code&gt;, el símbolo ≤). Con la misma raíz, leer un archivo JSON con &lt;code&gt;open()&lt;/code&gt; falla en sentido contrario:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UnicodeDecodeError: &#39;charmap&#39; codec can&#39;t decode byte 0x90 in position 15: character maps to &amp;lt;undefined&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Los datos no tienen nada malo: el JSON es válido y el texto que quieres enviar a otro sistema también. Falla solo el paso local de leer del disco o escribir en la salida estándar con una codificación que no soporta esos caracteres.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Para convertir texto en bytes (y al revés) Python necesita una codificación. Si no se le indica una, usa la del sistema. En muchas instalaciones de Windows en español, esa codificación es &lt;code&gt;cp1252&lt;/code&gt;, que no contiene emojis ni símbolos como ≤. El nombre &lt;code&gt;charmap&lt;/code&gt; del error viene del códec que implementa &lt;code&gt;cp1252&lt;/code&gt;, no de tu código.&lt;/p&gt;
&lt;p&gt;Hay dos puntos donde ocurre:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Archivos.&lt;/strong&gt; En Python 3.14 y anteriores, &lt;code&gt;open()&lt;/code&gt; sin &lt;code&gt;encoding&lt;/code&gt; usa la codificación local. Un archivo escrito en UTF-8 se lee entonces como cp1252: algunos bytes (como &lt;code&gt;0x90&lt;/code&gt;) no existen en esa tabla y producen &lt;code&gt;UnicodeDecodeError&lt;/code&gt;; el resto se decodifica sin error, pero mal. Por ejemplo, &lt;code&gt;canción&lt;/code&gt; se lee como &lt;code&gt;canciÃ³n&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Salida estándar.&lt;/strong&gt; Desde Python 3.6, la consola interactiva de Windows usa UTF-8, pero la documentación aclara que ese cambio solo se aplica a la consola interactiva, no cuando la salida se redirige a un archivo o a una tubería. Si el mismo script se ejecuta desde una tarea programada, un servicio, un sistema de CI, con &lt;code&gt;&amp;gt;&lt;/code&gt; o &lt;code&gt;|&lt;/code&gt;, o desde un terminal que entrega una tubería en lugar de una consola, &lt;code&gt;print()&lt;/code&gt; vuelve a usar la codificación local.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esto cambia en Python 3.15: según la documentación, el modo UTF-8 pasa a estar activado por defecto (PEP 686) y &lt;code&gt;open()&lt;/code&gt; usa UTF-8 si no se indica &lt;code&gt;encoding&lt;/code&gt;. En 3.14 y versiones anteriores el problema sigue vigente.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Comprueba qué codificación está usando tu Python.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import locale, sys
print(sys.version.split()[0], sys.stdout.encoding, locale.getpreferredencoding(False), sys.flags.utf8_mode)&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si ejecutas esto con la salida redirigida (por ejemplo, añadiendo &lt;code&gt;| more&lt;/code&gt; al comando de Python) y ves &lt;code&gt;cp1252&lt;/code&gt; y &lt;code&gt;0&lt;/code&gt;, estás en el caso descrito.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;En archivos, indica siempre la codificación.&lt;/strong&gt; Es la solución más robusta porque no depende del equipo donde se ejecute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json

with open(&quot;datos.json&quot;, encoding=&quot;utf-8&quot;) as f:
    datos = json.load(f)

with open(&quot;salida.json&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    json.dump(datos, f, ensure_ascii=False, indent=2)&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Pasar &lt;code&gt;encoding&lt;/code&gt; de forma explícita hace que el código se comporte igual en todas las versiones de Python y en todos los equipos.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;En la salida estándar, elige una de estas opciones.&lt;/strong&gt;&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Dentro del script, al inicio (disponible desde Python 3.7):
&lt;pre&gt;&lt;code&gt;import sys
sys.stdout.reconfigure(encoding=&quot;utf-8&quot;)&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;Con una variable de entorno, solo para la salida estándar. En PowerShell, para la sesión actual:
&lt;pre&gt;&lt;code&gt;$env:PYTHONIOENCODING = &#39;utf-8&#39;
python informe.py&lt;/code&gt;&lt;/pre&gt;
      En &lt;code&gt;cmd&lt;/code&gt; es &lt;code&gt;set PYTHONIOENCODING=utf-8&lt;/code&gt;. Para dejarla definida en tu usuario puedes usar &lt;code&gt;setx PYTHONIOENCODING utf-8&lt;/code&gt;; solo afecta a los procesos que abras después.&lt;/li&gt;
      &lt;li&gt;Con el modo UTF-8 de Python (3.7 o posterior), que además hace que &lt;code&gt;open()&lt;/code&gt; use UTF-8 por defecto:
&lt;pre&gt;&lt;code&gt;python -X utf8 informe.py&lt;/code&gt;&lt;/pre&gt;
      Equivale a definir &lt;code&gt;PYTHONUTF8=1&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;En una tarea programada o un servicio, configura el entorno de esa tarea.&lt;/strong&gt; No hereda el entorno de tu terminal. La forma más simple es poner la opción en el propio comando, por ejemplo &lt;code&gt;python -X utf8 C:\proyectos\mi-app\informe.py&lt;/code&gt;, o definir la variable de entorno en la configuración de la tarea o del servicio.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Elegir entre &lt;code&gt;PYTHONIOENCODING&lt;/code&gt; y el modo UTF-8 es una cuestión de alcance. &lt;code&gt;PYTHONIOENCODING&lt;/code&gt; solo cambia &lt;code&gt;stdin&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt; y &lt;code&gt;stderr&lt;/code&gt;. El modo UTF-8 también cambia la codificación por defecto de &lt;code&gt;open()&lt;/code&gt;, por lo que un script que lea archivos antiguos en cp1252 sin indicar &lt;code&gt;encoding&lt;/code&gt; pasaría a leerlos mal. Si tienes archivos así, indícalo en cada &lt;code&gt;open()&lt;/code&gt; con &lt;code&gt;encoding=&quot;cp1252&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Prueba siempre con la salida redirigida, porque en la consola interactiva el error puede no aparecer ni siquiera antes de la corrección:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python informe.py | more
python -c &quot;import json; print(json.load(open(&#39;datos.json&#39;, encoding=&#39;utf-8&#39;)))&quot; | more&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El primer comando debe imprimir todo el texto, con emojis y símbolos, sin &lt;code&gt;Traceback&lt;/code&gt;. El segundo debe cargar el JSON. Para confirmar la codificación de la salida:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -X utf8 -c &quot;import sys; print(sys.stdout.encoding)&quot; | more&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Debe mostrar &lt;code&gt;utf-8&lt;/code&gt;. Si el script se ejecuta como tarea programada o servicio, revisa su registro después de la primera ejecución real.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Error al escribir un archivo&lt;/h3&gt;
&lt;p&gt;Un &lt;code&gt;open(&quot;salida.txt&quot;, &quot;w&quot;)&lt;/code&gt; sin &lt;code&gt;encoding&lt;/code&gt; produce el mismo &lt;code&gt;UnicodeEncodeError&lt;/code&gt; al escribir texto que cp1252 no contiene. La solución es la del paso 2.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;UnicodeDecodeError: &#39;utf-8&#39; codec can&#39;t decode byte ...&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Es el caso inverso: el archivo no está en UTF-8, sino en cp1252 o en otra codificación, y lo abres como UTF-8. Indica la codificación real (&lt;code&gt;encoding=&quot;cp1252&quot;&lt;/code&gt;) o convierte el archivo a UTF-8.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;PYTHONIOENCODING&lt;/code&gt; con manejador de errores&lt;/h3&gt;
&lt;p&gt;La sintaxis es &lt;code&gt;codificación:manejador&lt;/code&gt;. Por ejemplo, &lt;code&gt;PYTHONIOENCODING=cp1252:replace&lt;/code&gt; evita la excepción sustituyendo cada carácter no representable por &lt;code&gt;?&lt;/code&gt;. Es útil para que un script no se detenga, pero pierde información: no lo uses si necesitas conservar el texto.&lt;/p&gt;
&lt;h3&gt;La variable no cambia nada en la consola interactiva&lt;/h3&gt;
&lt;p&gt;En Windows, &lt;code&gt;PYTHONIOENCODING&lt;/code&gt; se ignora en los búferes de la consola interactiva (salvo que también se defina &lt;code&gt;PYTHONLEGACYWINDOWSSTDIO&lt;/code&gt;); sí afecta a archivos y tuberías redirigidos. Por eso conviene probar con la salida redirigida, como se explica arriba.&lt;/p&gt;
&lt;h3&gt;Python 3.15 o posterior&lt;/h3&gt;
&lt;p&gt;Con el modo UTF-8 activado por defecto, el código sin &lt;code&gt;encoding&lt;/code&gt; lee y escribe UTF-8. Si necesitas el comportamiento anterior, puedes desactivarlo con &lt;code&gt;PYTHONUTF8=0&lt;/code&gt; o &lt;code&gt;-X utf8=0&lt;/code&gt;, o pedir la codificación local de forma explícita con &lt;code&gt;encoding=&quot;locale&quot;&lt;/code&gt; (disponible desde Python 3.10).&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;&#39;charmap&#39; codec can&#39;t encode/decode&lt;/code&gt; en Windows suele significar que Python usó cp1252 por defecto; los datos no están dañados.&lt;/li&gt;
  &lt;li&gt;Al leer o escribir archivos, pasa siempre &lt;code&gt;encoding=&quot;utf-8&quot;&lt;/code&gt; a &lt;code&gt;open()&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Para la salida, usa &lt;code&gt;sys.stdout.reconfigure(encoding=&quot;utf-8&quot;)&lt;/code&gt;, &lt;code&gt;PYTHONIOENCODING=utf-8&lt;/code&gt; o &lt;code&gt;python -X utf8&lt;/code&gt; (equivale a &lt;code&gt;PYTHONUTF8=1&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;El fallo aparece sobre todo con la salida redirigida (tareas, servicios, CI, tuberías); en tareas y servicios configura la opción en su propio entorno.&lt;/li&gt;
  &lt;li&gt;Desde Python 3.15 el modo UTF-8 es el predeterminado; en 3.14 y anteriores sigue haciendo falta lo anterior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/using/cmdline.html&quot;&gt;Python: Command line and environment (&lt;code&gt;PYTHONIOENCODING&lt;/code&gt;, &lt;code&gt;PYTHONUTF8&lt;/code&gt;, &lt;code&gt;-X utf8&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/functions.html#open&quot;&gt;Python: función integrada &lt;code&gt;open()&lt;/code&gt;, parámetro &lt;code&gt;encoding&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/whatsnew/3.6.html&quot;&gt;Python 3.6, novedades: PEP 528, codificación de la consola de Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/6515916442590703707/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/unicodeencodeerror-charmap-codec-cant.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/6515916442590703707'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/6515916442590703707'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/unicodeencodeerror-charmap-codec-cant.html' title='UnicodeEncodeError: &#39;charmap&#39; codec can&#39;t encode: solución'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-6023228651597914041</id><published>2026-10-15T09:00:00.000-05:00</published><updated>2026-10-15T09:00:00.234-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Git"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>fatal: detected dubious ownership in repository: solución</title><content type='html'>&lt;p&gt;Abres una carpeta con un repositorio, ejecutas cualquier comando de Git y todos fallan con este mensaje:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fatal: detected dubious ownership in repository at &#39;D:/proyectos/mi-repo&#39;
To add an exception for this directory, call:

	git config --global --add safe.directory D:/proyectos/mi-repo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Según la versión de Git, entre ambas líneas puede aparecer información sobre el propietario de la carpeta y el usuario actual. Este artículo explica qué está protegiendo Git, cómo agregar la excepción correcta en Windows y por qué conviene evitar el comodín &lt;code&gt;*&lt;/code&gt; que aparece en muchas respuestas.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;La documentación de Git explica que, por defecto, se niega a leer siquiera la configuración de un repositorio cuyo propietario es otro usuario, y mucho menos a ejecutar sus hooks. La razón es de seguridad: la configuración y los hooks de un repositorio pueden ejecutar comandos, así que Git no los acepta de una carpeta que controla otra persona.&lt;/p&gt;
&lt;p&gt;En Windows, Git compara el propietario de la carpeta (un identificador de seguridad, o SID) con el usuario que ejecuta el comando. Si no coinciden, aparece el error. Se ha visto en estos casos:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;La carpeta la creó otro usuario.&lt;/li&gt;
  &lt;li&gt;El repositorio se copió desde otra máquina.&lt;/li&gt;
  &lt;li&gt;El repositorio está en un disco sincronizado.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Las versiones iniciales de esta protección mostraban otro texto: &lt;code&gt;fatal: unsafe repository (&#39;...&#39; is owned by someone else)&lt;/code&gt;. Es el mismo problema con el mismo arreglo.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Confirma que el propietario no coincide.&lt;/strong&gt; En PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;$env:USERDOMAIN\$env:USERNAME&quot;
(Get-Acl -LiteralPath &#39;D:\proyectos\mi-repo&#39;).Owner&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si el segundo valor es distinto del primero (por ejemplo &lt;code&gt;EQUIPO\otro-usuario&lt;/code&gt; frente a &lt;code&gt;EQUIPO\tu-usuario&lt;/code&gt;), esa es la causa. Antes de continuar, asegúrate de que confías en el contenido de esa carpeta: la excepción hace que Git vuelva a aceptar su configuración y sus hooks.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Agrega la excepción de forma permanente, solo para ese repositorio.&lt;/strong&gt; Usa la ruta exacta que muestra el mensaje, con barras normales:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global --add safe.directory D:/proyectos/mi-repo&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;safe.directory&lt;/code&gt; admite varios valores, y &lt;code&gt;--add&lt;/code&gt; agrega uno nuevo sin reemplazar los anteriores. La excepción vale para esa ruta; si mueves la carpeta, debes agregar la nueva ruta.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Si solo necesitas ejecutar un comando una vez&lt;/strong&gt;, pasa la opción por línea de comandos en lugar de guardarla:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git -c safe.directory=D:/proyectos/mi-repo status&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La documentación actual de Git incluye la opción &lt;code&gt;-c&lt;/code&gt; entre las configuraciones &quot;protegidas&quot; que sí se respetan. En versiones antiguas, la documentación de &lt;code&gt;safe.directory&lt;/code&gt; decía que se ignoraba cuando se pasaba con &lt;code&gt;-c&lt;/code&gt;; si en tu versión no funciona, actualiza Git o usa el paso 2.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git status
git config --global --get-all safe.directory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El primer comando debe mostrar el estado del repositorio sin el &lt;code&gt;fatal&lt;/code&gt;. El segundo debe listar la ruta que agregaste (una línea por cada excepción). Para confirmar que puedes trabajar con normalidad, crea y borra una rama de prueba:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch -c prueba-propietario
git switch -
git branch -d prueba-propietario&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;No uses &lt;code&gt;safe.directory &#39;*&#39;&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git config --global --add safe.directory &#39;*&#39;   # evítalo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Funciona, pero según la documentación de Git, el valor &lt;code&gt;*&lt;/code&gt; desactiva por completo esta comprobación de seguridad. Dejas sin protección todos los repositorios del equipo, incluidos los que descargues o recibas de terceros en el futuro. Agrega solo las rutas en las que confías.&lt;/p&gt;
&lt;h3&gt;Agregarlo en el &lt;code&gt;.git/config&lt;/code&gt; del repositorio no sirve&lt;/h3&gt;
&lt;p&gt;Git solo respeta &lt;code&gt;safe.directory&lt;/code&gt; en la configuración protegida: la del sistema, la global y la de línea de comandos. Si lo escribes en el archivo de configuración del propio repositorio, se ignora, porque de lo contrario cualquier repositorio podría declararse seguro a sí mismo.&lt;/p&gt;
&lt;h3&gt;Revisa la carpeta antes de confiar en ella&lt;/h3&gt;
&lt;p&gt;Si el repositorio viene de una persona o de una máquina que no controlas, abre con un editor de texto el archivo &lt;code&gt;.git/config&lt;/code&gt; y la carpeta &lt;code&gt;.git/hooks&lt;/code&gt; antes de agregar la excepción. Son justo los elementos que Git no quiere ejecutar sin tu confirmación.&lt;/p&gt;
&lt;h3&gt;Quitar o revisar excepciones&lt;/h3&gt;
&lt;p&gt;Para revisar todas las excepciones usa &lt;code&gt;git config --global --get-all safe.directory&lt;/code&gt;; con &lt;code&gt;git config --show-origin --get-all safe.directory&lt;/code&gt; verás además de qué archivo viene cada valor. Estas excepciones se guardan en tu configuración global (por ejemplo, el archivo &lt;code&gt;.gitconfig&lt;/code&gt; de tu carpeta de usuario). Para borrar alguna, abre ese archivo con &lt;code&gt;git config --global --edit&lt;/code&gt; y elimina la línea correspondiente.&lt;/p&gt;
&lt;h3&gt;Alternativa: corregir el propietario&lt;/h3&gt;
&lt;p&gt;Si la carpeta debería pertenecerte, otra opción es cambiar su propietario al usuario actual con las herramientas de permisos de Windows (esto suele requerir permisos de administrador). A diferencia de &lt;code&gt;safe.directory&lt;/code&gt;, resuelve la causa en lugar de aceptar la diferencia.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;El error significa que el propietario de la carpeta no es el usuario que ejecuta Git; es una protección, no un fallo del repositorio.&lt;/li&gt;
  &lt;li&gt;La solución permanente es &lt;code&gt;git config --global --add safe.directory D:/ruta/exacta&lt;/code&gt;, una vez por repositorio y con la ruta tal como la muestra el mensaje.&lt;/li&gt;
  &lt;li&gt;Para un único comando puedes usar &lt;code&gt;git -c safe.directory=... status&lt;/code&gt;, si tu versión de Git lo admite.&lt;/li&gt;
  &lt;li&gt;Evita el valor &lt;code&gt;*&lt;/code&gt;: desactiva la protección para todos los repositorios.&lt;/li&gt;
  &lt;li&gt;Verifica con &lt;code&gt;git status&lt;/code&gt; y &lt;code&gt;git config --global --get-all safe.directory&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-config&quot;&gt;Git: &lt;code&gt;git-config&lt;/code&gt;, entrada &lt;code&gt;safe.directory&lt;/code&gt; y sección SCOPES (configuración protegida)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/6023228651597914041/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/fatal-detected-dubious-ownership-in.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/6023228651597914041'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/6023228651597914041'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/fatal-detected-dubious-ownership-in.html' title='fatal: detected dubious ownership in repository: solución'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-8922059441909911309</id><published>2026-10-14T15:00:00.000-05:00</published><updated>2026-10-14T15:00:00.211-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="PowerShell"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>curl no funciona en PowerShell: SessionVariable y Headers</title><content type='html'>&lt;p&gt;Copias un comando &lt;code&gt;curl&lt;/code&gt; de la documentación de una API, lo pegas en PowerShell y en lugar de una respuesta HTTP obtienes errores como estos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invoke-WebRequest : Falta un argumento para el parámetro &#39;SessionVariable&#39;. Especifique un parámetro del tipo &#39;System.String&#39; e inténtelo de nuevo.&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Invoke-WebRequest : No se puede enlazar el parámetro &#39;Headers&#39;. No se puede convertir el valor &quot;Accept: application/json&quot; de tipo &quot;System.String&quot; al tipo &quot;System.Collections.IDictionary&quot;.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le pasa a quien usa Windows PowerShell 5.1, el que viene instalado con Windows, y ejecuta comandos pensados para el &lt;code&gt;curl&lt;/code&gt; de Linux o macOS. Aquí verás por qué ocurre y tres formas de resolverlo.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;En Windows PowerShell 5.1, &lt;code&gt;curl&lt;/code&gt; no es el programa curl: es un alias del cmdlet &lt;code&gt;Invoke-WebRequest&lt;/code&gt;. La documentación de Microsoft para la versión 5.1 lo indica de forma explícita: los alias de ese cmdlet son &lt;code&gt;iwr&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt; y &lt;code&gt;wget&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Como el alias ejecuta un cmdlet, las opciones de curl se interpretan como parámetros de PowerShell:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;-s&lt;/code&gt; es un prefijo único, así que PowerShell lo completa como &lt;code&gt;-SessionVariable&lt;/code&gt;, que exige un valor. De ahí el primer error.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;-H&lt;/code&gt; se completa como &lt;code&gt;-Headers&lt;/code&gt;, que espera un diccionario (una tabla hash) y no el texto &lt;code&gt;&quot;Accept: application/json&quot;&lt;/code&gt;. De ahí el segundo error.&lt;/li&gt;
  &lt;li&gt;Otras opciones típicas de curl, como &lt;code&gt;-X&lt;/code&gt;, tampoco existen: PowerShell responde que no encuentra ningún parámetro con ese nombre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hay un problema adicional: &lt;code&gt;/dev/null&lt;/code&gt; no existe en Windows. Y si llamas al programa real, las comillas dobles de un JSON se pierden por el camino (más abajo se explica).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Una precisión sobre PowerShell 7:&lt;/strong&gt; este alias es propio de Windows PowerShell 5.1. La documentación de &lt;code&gt;Invoke-WebRequest&lt;/code&gt; para PowerShell 7.5 solo lista el alias &lt;code&gt;iwr&lt;/code&gt;: &lt;code&gt;curl&lt;/code&gt; y &lt;code&gt;wget&lt;/code&gt; ya no figuran como alias del cmdlet. Si un comando curl te falla en PowerShell 7, la causa puede ser otra, por ejemplo la forma en que se pasan las comillas a los programas nativos.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;p&gt;Windows 10 y Windows 11 incluyen curl por defecto. Según el sitio de curl, la solución para PowerShell es invocarlo como &lt;code&gt;curl.exe&lt;/code&gt; para que no se trate como alias.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Llama a &lt;code&gt;curl.exe&lt;/code&gt; explícitamente.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl.exe -s -H &quot;Accept: application/json&quot; https://example.com/api/items&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Sustituye &lt;code&gt;/dev/null&lt;/code&gt; por &lt;code&gt;NUL&lt;/code&gt;.&lt;/strong&gt; Para descartar el cuerpo y mostrar solo el código de estado:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl.exe -s -o NUL -w &quot;%{http_code}&quot; https://example.com&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Con &lt;code&gt;/dev/null&lt;/code&gt;, curl.exe termina con el código de salida 23, que la documentación de curl describe como error de escritura.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Envía el JSON desde un archivo.&lt;/strong&gt; Es la forma más portable. Guarda el cuerpo en &lt;code&gt;body.json&lt;/code&gt; (UTF-8, preferiblemente sin BOM) y pásalo con &lt;code&gt;--data-binary&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl.exe -X POST -H &quot;Content-Type: application/json&quot; --data-binary &quot;@./body.json&quot; https://example.com/api/items&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;El prefijo &lt;code&gt;@&lt;/code&gt; le indica a curl que lea el cuerpo de ese archivo (la ruta es relativa a la carpeta actual) y &lt;code&gt;--data-binary&lt;/code&gt; lo envía sin procesarlo.&lt;/p&gt;
    &lt;p&gt;Si prefieres escribir el JSON en la propia línea, en Windows PowerShell 5.1 hay que escapar las comillas dobles con una barra invertida; sin ese escape, el servidor recibe &lt;code&gt;{nombre:mi-app}&lt;/code&gt; y no un JSON válido:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl.exe -X POST -H &quot;Content-Type: application/json&quot; -d &#39;{\&quot;nombre\&quot;:\&quot;mi-app\&quot;}&#39; https://example.com/api/items&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Ese escape manual depende de la versión. PowerShell 7.3 cambió la forma de pasar argumentos a programas nativos (variable &lt;code&gt;$PSNativeCommandArgumentPassing&lt;/code&gt;), y Microsoft lo documenta como un cambio que rompe compatibilidad. Si tu script debe funcionar en ambas versiones, usa el archivo.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Alternativa nativa: &lt;code&gt;Invoke-RestMethod&lt;/code&gt;.&lt;/strong&gt; Recibe los encabezados como tabla hash y convierte la respuesta JSON en objetos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$headers = @{ Accept = &#39;application/json&#39; }
$body = @{ nombre = &#39;mi-app&#39; } | ConvertTo-Json -Compress

Invoke-RestMethod -Uri &#39;https://example.com/api/items&#39; `
  -Method Post `
  -Headers $headers `
  -ContentType &#39;application/json&#39; `
  -Body $body&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Opcional: quitar el alias.&lt;/strong&gt; Si usas curl a diario en PowerShell 5.1, puedes eliminarlo de la sesión con &lt;code&gt;Remove-Item Alias:curl&lt;/code&gt; y agregar esa línea a tu perfil de PowerShell. Después, &lt;code&gt;curl&lt;/code&gt; a secas ejecutará el programa real. Ten en cuenta que cualquier script tuyo que dependa del alias dejará de funcionar.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Comprueba a qué resuelve cada nombre:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-Command curl        # CommandType: Alias, Definition: Invoke-WebRequest
Get-Command curl.exe    # CommandType: Application&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luego ejecuta una petición real y revisa el código de estado:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl.exe -s -o NUL -w &quot;%{http_code}&quot; https://example.com
200&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El número final es el código de estado que devuelva tu servidor (200 en el caso normal).&lt;/p&gt;
&lt;p&gt;Para comprobar que el JSON viaja intacto, envíalo a tu API local o a un servicio de eco y verifica que el cuerpo recibido conserva las comillas dobles. Con &lt;code&gt;Invoke-RestMethod&lt;/code&gt;, la respuesta llega ya convertida en objetos, por lo que puedes leer sus propiedades directamente.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Aviso de seguridad al usar el alias en 5.1&lt;/h3&gt;
&lt;p&gt;La documentación de Microsoft indica que, tras la actualización de seguridad del 9 de diciembre de 2025 (CVE-2025-54100), &lt;code&gt;Invoke-WebRequest&lt;/code&gt; en Windows PowerShell 5.1 pide confirmación antes de analizar el contenido de la página, con el mensaje &quot;Security Warning: Script Execution Risk&quot;. Se evita con el parámetro &lt;code&gt;-UseBasicParsing&lt;/code&gt;. Es otra razón para usar &lt;code&gt;curl.exe&lt;/code&gt; cuando solo necesitas la respuesta cruda.&lt;/p&gt;
&lt;h3&gt;Archivos con BOM&lt;/h3&gt;
&lt;p&gt;En Windows PowerShell 5.1, &lt;code&gt;Set-Content -Encoding UTF8&lt;/code&gt; escribe el archivo con BOM (en una prueba, los primeros bytes fueron EF BB BF). Algunos servidores no aceptan un BOM al inicio de un JSON; guarda &lt;code&gt;body.json&lt;/code&gt; desde un editor que permita UTF-8 sin BOM.&lt;/p&gt;
&lt;h3&gt;Mismo comando, otro resultado según el terminal&lt;/h3&gt;
&lt;p&gt;En Git Bash (que incluye curl) y en Linux o macOS con curl instalado, &lt;code&gt;curl&lt;/code&gt; ejecuta el programa real y los ejemplos de la documentación funcionan tal cual. El problema aparece cuando el terminal es Windows PowerShell 5.1.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;En Windows PowerShell 5.1, &lt;code&gt;curl&lt;/code&gt; es un alias de &lt;code&gt;Invoke-WebRequest&lt;/code&gt;; por eso &lt;code&gt;-s&lt;/code&gt; y &lt;code&gt;-H&lt;/code&gt; se interpretan como &lt;code&gt;-SessionVariable&lt;/code&gt; y &lt;code&gt;-Headers&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;En PowerShell 7.x la documentación solo lista el alias &lt;code&gt;iwr&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Usa &lt;code&gt;curl.exe&lt;/code&gt;, &lt;code&gt;NUL&lt;/code&gt; en lugar de &lt;code&gt;/dev/null&lt;/code&gt; y un archivo con &lt;code&gt;--data-binary&lt;/code&gt; para el JSON.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Invoke-RestMethod -Headers @{...} -ContentType ... -Body ...&lt;/code&gt; es la alternativa nativa.&lt;/li&gt;
  &lt;li&gt;Verifica con &lt;code&gt;Get-Command curl&lt;/code&gt; y &lt;code&gt;Get-Command curl.exe&lt;/code&gt; y con una petición real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-5.1&quot;&gt;Microsoft Learn: Invoke-WebRequest (Windows PowerShell 5.1)&lt;/a&gt; y la &lt;a href=&quot;https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-7.5&quot;&gt;versión para PowerShell 7.5&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://curl.se/windows/microsoft.html&quot;&gt;curl: curl en Windows de Microsoft (uso de curl.exe en PowerShell)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.5&quot;&gt;Microsoft Learn: about_Preference_Variables (&lt;code&gt;$PSNativeCommandArgumentPassing&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/8922059441909911309/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/curl-no-funciona-en-powershell.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/8922059441909911309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/8922059441909911309'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/curl-no-funciona-en-powershell.html' title='curl no funciona en PowerShell: SessionVariable y Headers'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-503886016612682317</id><published>2026-10-14T09:00:00.000-05:00</published><updated>2026-10-14T09:00:00.204-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Git"/><category scheme="http://www.blogger.com/atom/ns#" term="Linux"/><title type='text'>husky pre-commit script failed (code 127): causa CRLF</title><content type='html'>&lt;p&gt;Haces &lt;code&gt;git commit&lt;/code&gt; y Husky aborta el commit con un mensaje que parece un problema de &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.husky/pre-commit: 6: : not found
husky - pre-commit script failed (code 127)
husky - command not found in PATH=node_modules/.bin:/usr/local/bin:/usr/bin:/bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Las líneas que añade Husky pueden variar según la versión, pero el detalle importante es el mismo: el comando que &quot;no se encuentra&quot; tiene el nombre vacío (&lt;code&gt;6: : not found&lt;/code&gt;). Pasa al usar Git en WSL2 (Ubuntu), Linux o macOS con hooks que se editaron o se descargaron en Windows. Este artículo explica por qué no es un problema de &lt;code&gt;PATH&lt;/code&gt;, cómo confirmarlo y cómo evitar que vuelva a ocurrir.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Un archivo de texto termina cada línea con LF (&lt;code&gt;\n&lt;/code&gt;, Linux y macOS) o con CRLF (&lt;code&gt;\r\n&lt;/code&gt;, Windows). El intérprete &lt;code&gt;sh&lt;/code&gt; solo reconoce &lt;code&gt;\n&lt;/code&gt; como fin de línea, así que el &lt;code&gt;\r&lt;/code&gt; sobrante se queda dentro de la línea. En una línea en blanco guardada con CRLF, ese carácter es lo único que hay: &lt;code&gt;sh&lt;/code&gt; lo toma como un comando llamado &lt;code&gt;\r&lt;/code&gt;, no lo encuentra y responde &quot;not found&quot; con estado de salida 127. El estándar POSIX define 127 como el estado de un comando que no se encuentra.&lt;/p&gt;
&lt;p&gt;Por eso el mensaje tiene un nombre vacío entre los dos puntos: el carácter &lt;code&gt;\r&lt;/code&gt; no se ve. Según el terminal, incluso puede verse desordenado, porque el retorno de carro mueve el cursor al inicio de la línea. El número (&lt;code&gt;6&lt;/code&gt;) es la línea del hook donde &lt;code&gt;sh&lt;/code&gt; tropezó con el comando vacío.&lt;/p&gt;
&lt;p&gt;Husky ejecuta cada hook con &lt;code&gt;sh -e&lt;/code&gt; (el script se detiene en el primer comando que falla) y solo informa del código de salida. En las versiones recientes, cuando ese código es 127 añade además el aviso &lt;code&gt;command not found in PATH&lt;/code&gt;. Ese aviso es genérico y desvía el diagnóstico hacia el &lt;code&gt;PATH&lt;/code&gt;, aunque la causa sea el formato del archivo.&lt;/p&gt;
&lt;p&gt;El CRLF llega al hook por varias vías: un editor en Windows configurado para guardar con CRLF, una herramienta que genera el archivo, o &lt;code&gt;core.autocrlf=true&lt;/code&gt; al clonar el repositorio en Windows y luego usarlo desde WSL. Si el comando faltante fuera real, el mensaje mostraría su nombre (por ejemplo &lt;code&gt;pnpm: not found&lt;/code&gt;). El nombre vacío apunta al fin de línea.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Confirma que el hook tiene CRLF.&lt;/strong&gt; Desde la raíz del repositorio, en WSL o Linux:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat -A .husky/pre-commit&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Con CRLF, cada línea termina en &lt;code&gt;^M$&lt;/code&gt;; con LF solo aparece &lt;code&gt;$&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm lint-staged^M$
^M$
pnpm exec tsc --noEmit^M$&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La opción &lt;code&gt;-A&lt;/code&gt; es de GNU coreutils. En macOS puedes usar &lt;code&gt;file .husky/pre-commit&lt;/code&gt; (muestra &quot;with CRLF line terminators&quot;) o &lt;code&gt;git ls-files --eol .husky&lt;/code&gt; (el valor &lt;code&gt;w/crlf&lt;/code&gt; indica CRLF en el directorio de trabajo).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Convierte los hooks a LF.&lt;/strong&gt; Hazlo con todos los hooks que tengas (&lt;code&gt;pre-commit&lt;/code&gt;, &lt;code&gt;pre-push&lt;/code&gt;, &lt;code&gt;commit-msg&lt;/code&gt;, etc.):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sed -i &#39;s/\r$//&#39; .husky/pre-commit .husky/pre-push&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Esa sintaxis de &lt;code&gt;sed -i&lt;/code&gt; es la de GNU (Linux y WSL). En macOS funciona &lt;code&gt;perl -pi -e &#39;s/\r$//&#39; .husky/pre-commit .husky/pre-push&lt;/code&gt;, y en cualquier sistema puedes usar &lt;code&gt;dos2unix&lt;/code&gt; si lo tienes instalado. También puedes cambiar el fin de línea desde el editor: en VS Code, haz clic en el indicador CRLF de la barra de estado y elige LF.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Fija LF con &lt;code&gt;.gitattributes&lt;/code&gt;.&lt;/strong&gt; Crea o edita el archivo en la raíz del repositorio:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.husky/* text eol=lf
*.sh text eol=lf&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Según la documentación de Git, &lt;code&gt;eol=lf&lt;/code&gt; indica que el archivo se escribe con LF en el directorio de trabajo al hacer checkout, y el atributo &lt;code&gt;eol&lt;/code&gt; activa &lt;code&gt;text&lt;/code&gt; si no estaba definido, de modo que Git también normaliza a LF al guardar en el índice. Solo cuando &lt;code&gt;eol&lt;/code&gt; no está definido Git recurre a &lt;code&gt;core.autocrlf&lt;/code&gt; o &lt;code&gt;core.eol&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Renormaliza lo que ya estaba guardado con CRLF.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add --renormalize .
git status
git commit -m &quot;chore: normalize hook line endings to LF&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;--renormalize&lt;/code&gt; vuelve a aplicar el filtro de normalización a todos los archivos rastreados, algo pensado justo para cuando cambias atributos de fin de línea. Actualiza el índice, no el directorio de trabajo: por eso el paso 2 va antes. Revisa &lt;code&gt;git status&lt;/code&gt; antes de confirmar; solo deberían aparecer archivos que coincidan con esos patrones. Si el propio commit falla porque el hook aún tiene CRLF, usa &lt;code&gt;git commit --no-verify&lt;/code&gt; solo para este commit de corrección.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cat -A .husky/pre-commit
git ls-files --eol .husky
sh -e .husky/pre-commit; echo $?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El primer comando no debe mostrar &lt;code&gt;^M&lt;/code&gt;. El segundo debe mostrar &lt;code&gt;i/lf&lt;/code&gt; y &lt;code&gt;w/lf&lt;/code&gt; con el atributo &lt;code&gt;text eol=lf&lt;/code&gt; para cada hook. El tercero ejecuta el hook como lo hace Husky y debe terminar con &lt;code&gt;0&lt;/code&gt; si las comprobaciones del hook pasan. Para cerrar, haz un commit y un push de prueba: ya no debe aparecer el código 127.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;El mismo error en &lt;code&gt;pre-push&lt;/code&gt; y otros hooks&lt;/h3&gt;
&lt;p&gt;La causa es idéntica en cualquier hook. Convierte todos los archivos de &lt;code&gt;.husky/&lt;/code&gt; que hayas creado, no solo el que falló primero.&lt;/p&gt;
&lt;h3&gt;Mensajes distintos según el intérprete&lt;/h3&gt;
&lt;p&gt;Con Bash, el mismo problema se ve como &lt;code&gt;line 6: $&#39;\r&#39;: command not found&lt;/code&gt;. Si el hook tiene un shebang y se ejecuta directamente, el sistema busca un intérprete llamado &lt;code&gt;sh\r&lt;/code&gt; y el error es de este estilo: &lt;code&gt;env: $&#39;sh\r&#39;: No such file or directory&lt;/code&gt;. También termina con estado 127. En todos los casos la solución es la misma.&lt;/p&gt;
&lt;h3&gt;Cuando sí es un problema de &lt;code&gt;PATH&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Si el mensaje incluye el nombre de un comando real (&lt;code&gt;pnpm: not found&lt;/code&gt;, &lt;code&gt;node: not found&lt;/code&gt;), el fallo es de entorno. Es habitual con gestores de versiones de Node o con clientes gráficos de Git que no cargan tu configuración de shell. La documentación de Husky tiene una sección sobre gestores de versiones y &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Otros scripts del repositorio&lt;/h3&gt;
&lt;p&gt;Los archivos &lt;code&gt;.sh&lt;/code&gt;, los scripts de entrada de un Dockerfile y cualquier script que ejecute Linux fallan igual con CRLF. La segunda línea de &lt;code&gt;.gitattributes&lt;/code&gt; (&lt;code&gt;*.sh text eol=lf&lt;/code&gt;) los cubre.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;.husky/pre-commit: 6: : not found&lt;/code&gt; con el nombre vacío no es un problema de &lt;code&gt;PATH&lt;/code&gt;: es un &lt;code&gt;\r&lt;/code&gt; de CRLF que &lt;code&gt;sh&lt;/code&gt; interpreta como un comando.&lt;/li&gt;
  &lt;li&gt;El código 127 significa &quot;comando no encontrado&quot; y el aviso de &lt;code&gt;PATH&lt;/code&gt; que añade Husky es genérico.&lt;/li&gt;
  &lt;li&gt;Confirma con &lt;code&gt;cat -A&lt;/code&gt; (busca &lt;code&gt;^M$&lt;/code&gt;) y corrige con &lt;code&gt;sed -i &#39;s/\r$//&#39;&lt;/code&gt; o con el selector de fin de línea del editor.&lt;/li&gt;
  &lt;li&gt;Evita que vuelva con &lt;code&gt;.husky/* text eol=lf&lt;/code&gt; y &lt;code&gt;*.sh text eol=lf&lt;/code&gt; en &lt;code&gt;.gitattributes&lt;/code&gt;, más &lt;code&gt;git add --renormalize .&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Verifica con &lt;code&gt;git ls-files --eol .husky&lt;/code&gt; y un commit y un push reales.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/gitattributes&quot;&gt;Git: &lt;code&gt;gitattributes&lt;/code&gt;, atributos &lt;code&gt;text&lt;/code&gt; y &lt;code&gt;eol&lt;/code&gt; y sección &quot;End-of-line conversion&quot;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://typicode.github.io/husky/how-to.html&quot;&gt;Husky: How To (hooks compatibles con POSIX, &lt;code&gt;PATH&lt;/code&gt; y gestores de versiones de Node)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html&quot;&gt;POSIX: Shell Command Language, sección 2.8.2 &quot;Exit Status for Commands&quot;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/503886016612682317/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/husky-pre-commit-script-failed-code-127.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/503886016612682317'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/503886016612682317'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/husky-pre-commit-script-failed-code-127.html' title='husky pre-commit script failed (code 127): causa CRLF'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-7774545644624523547</id><published>2026-10-13T15:00:00.000-05:00</published><updated>2026-10-13T15:00:00.193-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><category scheme="http://www.blogger.com/atom/ns#" term="TypeScript"/><title type='text'>La fecha sale un día antes en JavaScript: toISOString</title><content type='html'>&lt;p&gt;Guardas o muestras una fecha en JavaScript y el día sale equivocado: una venta registrada a las 19:17 aparece con la fecha de mañana, o un &lt;code&gt;&amp;lt;input type=&quot;date&quot;&amp;gt;&lt;/code&gt; guarda el día anterior al que elegiste. En una máquina con zona horaria UTC-5 puedes reproducirlo así:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Date(&#39;2026-08-18T19:17:00-05:00&#39;).toISOString().slice(0, 10)
// &#39;2026-08-19&#39;  (se esperaba &#39;2026-08-18&#39;)

new Date(&#39;2026-07-09&#39;).getDate()
// 8             (se esperaba 9)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le pasa a quien tiene usuarios en una zona horaria distinta de UTC y mezcla &lt;code&gt;toISOString()&lt;/code&gt;, &lt;code&gt;new Date(&#39;YYYY-MM-DD&#39;)&lt;/code&gt; y getters locales como &lt;code&gt;getDate()&lt;/code&gt;. Aquí verás la causa exacta y una forma de manejar fechas que no depende de la zona horaria del servidor.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Intervienen dos reglas de JavaScript que parecen inofensivas por separado:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;toISOString()&lt;/code&gt; siempre devuelve la hora en UTC.&lt;/strong&gt; MDN lo resume así: la zona horaria es siempre UTC, indicada por el sufijo &lt;code&gt;Z&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Un string de solo fecha (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;) se interpreta como medianoche UTC&lt;/strong&gt;, mientras que un string con fecha y hora sin desfase (&lt;code&gt;2019-01-01T00:00:00&lt;/code&gt;) se interpreta en la zona local. Esa diferencia está documentada en la página de &lt;code&gt;Date.parse()&lt;/code&gt; de MDN.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En UTC-5, las 19:00 locales son las 00:00 UTC del día siguiente. Por eso una venta de las 19:17 sale con la fecha de mañana si la formateas con &lt;code&gt;toISOString()&lt;/code&gt;. Y al revés: &lt;code&gt;new Date(&#39;2026-07-09&#39;)&lt;/code&gt; es el 9 de julio a las 00:00 UTC, que en tu zona local es el 8 de julio a las 19:00. Si luego lees el día con &lt;code&gt;getDate()&lt;/code&gt; o &lt;code&gt;toLocaleDateString()&lt;/code&gt;, obtienes el 8.&lt;/p&gt;
&lt;p&gt;En zonas al este de UTC ocurre lo contrario: las horas de la madrugada local caen en el día anterior de UTC. Y en un servidor configurado en UTC no verás el problema, que es justo lo que lo hace tan fácil de pasar por alto: funciona en el servidor y falla en el navegador del usuario (o al revés).&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;p&gt;Primero distingue qué tipo de valor tienes, porque se tratan distinto:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Instante:&lt;/strong&gt; un momento exacto (la hora de una venta, un &lt;code&gt;createdAt&lt;/code&gt;). Se debe formatear en la zona horaria del negocio o del usuario.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fecha pura:&lt;/strong&gt; un día de calendario sin hora (un cumpleaños, una fecha de vencimiento, el valor de un &lt;code&gt;&amp;lt;input type=&quot;date&quot;&amp;gt;&lt;/code&gt;). No debería pasar por UTC.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Instantes: formatea en una zona explícita.&lt;/strong&gt; Con &lt;code&gt;date-fns-tz&lt;/code&gt; (instala &lt;code&gt;date-fns&lt;/code&gt; y &lt;code&gt;date-fns-tz&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { formatInTimeZone } from &#39;date-fns-tz&#39;;

const TZ = &#39;America/Bogota&#39;; // zona de ejemplo con UTC-5

export function fechaLocal(instante: Date, tz: string = TZ): string {
  return formatInTimeZone(instante, tz, &#39;yyyy-MM-dd&#39;);
}

fechaLocal(new Date(&#39;2026-08-19T00:17:00Z&#39;)); // &#39;2026-08-18&#39;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Sin dependencias, &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; con la configuración regional &lt;code&gt;en-CA&lt;/code&gt; produce el formato año-mes-día:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Intl.DateTimeFormat(&#39;en-CA&#39;, { timeZone: &#39;America/Bogota&#39; })
  .format(new Date(&#39;2026-08-19T00:17:00Z&#39;)); // &#39;2026-08-18&#39;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Ese formato depende de los datos de localización del entorno, así que protege esta función con un test (paso 4).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Fechas puras: no las conviertas a UTC.&lt;/strong&gt; Lo más seguro es conservar el string &lt;code&gt;YYYY-MM-DD&lt;/code&gt; tal cual, tanto en el formulario como al enviarlo a la API. Si necesitas un objeto &lt;code&gt;Date&lt;/code&gt;, créalo con los componentes, que se interpretan en hora local (y el mes empieza en 0):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [y, m, d] = &#39;2026-07-09&#39;.split(&#39;-&#39;).map(Number);
const fecha = new Date(y, m - 1, d); // 9 de julio, medianoche local
fecha.getDate(); // 9&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si ya tienes fechas puras guardadas como medianoche UTC, no las recalcules: muéstralas indicando la zona UTC.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;formatInTimeZone(new Date(&#39;2026-07-09&#39;), &#39;UTC&#39;, &#39;yyyy-MM-dd&#39;); // &#39;2026-07-09&#39;&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Rangos por día: convierte los extremos desde la zona del negocio.&lt;/strong&gt; &lt;code&gt;fromZonedTime&lt;/code&gt; interpreta un string sin zona como hora local de la zona indicada y devuelve el instante UTC equivalente:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { fromZonedTime } from &#39;date-fns-tz&#39;;

export function rangoDelDia(fecha: string, tz: string = TZ) {
  return {
    desde: fromZonedTime(`${fecha}T00:00:00`, tz),
    hasta: fromZonedTime(`${fecha}T23:59:59.999`, tz),
  };
}

rangoDelDia(&#39;2026-08-18&#39;);
// desde: 2026-08-18T05:00:00.000Z
// hasta: 2026-08-19T04:59:59.999Z&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Con ese rango, una consulta por día incluye las ventas de las 19:17 en lugar de moverlas al día siguiente.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Agrega un test con una hora entre las 19:00 y las 23:59.&lt;/strong&gt; Es el caso que rompe el código ingenuo. Como la función recibe la zona de forma explícita, el test no depende de la configuración de la máquina:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { describe, expect, it } from &#39;vitest&#39;;
import { fechaLocal } from &#39;./fecha&#39;;

describe(&#39;fechaLocal&#39;, () =&amp;gt; {
  it(&#39;no adelanta el día entre las 19:00 y las 23:59 (UTC-5)&#39;, () =&amp;gt; {
    expect(fechaLocal(new Date(&#39;2026-08-19T00:17:00Z&#39;))).toBe(&#39;2026-08-18&#39;);
  });

  it(&#39;a la medianoche local ya es el día siguiente&#39;, () =&amp;gt; {
    expect(fechaLocal(new Date(&#39;2026-08-19T05:00:00Z&#39;))).toBe(&#39;2026-08-19&#39;);
  });
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Ejecuta la suite de tests: los dos casos de arriba deben pasar (&lt;code&gt;2026-08-19T00:17Z&lt;/code&gt; debe dar &lt;code&gt;2026-08-18&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Compara ambos enfoques en una consola Node.js. En una máquina en UTC-5 verás: &lt;code&gt;toISOString().slice(0, 10)&lt;/code&gt; da &lt;code&gt;2026-08-19&lt;/code&gt;, mientras que &lt;code&gt;fechaLocal(...)&lt;/code&gt; da &lt;code&gt;2026-08-18&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Prueba el flujo completo con una venta creada entre las 19:00 y las 23:59: la fecha en pantalla, en la exportación a Excel o PDF y en el filtro por día debe ser la misma.&lt;/li&gt;
  &lt;li&gt;Elige una fecha en el &lt;code&gt;&amp;lt;input type=&quot;date&quot;&amp;gt;&lt;/code&gt;, guárdala, recarga y comprueba que sigue siendo el mismo día.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Agregar &quot;T00:00:00&quot; cambia el significado del string&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;new Date(&#39;2026-07-09&#39;)&lt;/code&gt; es UTC, pero &lt;code&gt;new Date(&#39;2026-07-09T00:00:00&#39;)&lt;/code&gt; es hora local. Según la zona, la diferencia entre ambos es de varias horas (5 horas en UTC-5). No lo uses como parche sin saber cuál de los dos necesitas.&lt;/p&gt;
&lt;h3&gt;Funciones del paquete con nombres antiguos&lt;/h3&gt;
&lt;p&gt;Si encuentras ejemplos con &lt;code&gt;zonedTimeToUtc&lt;/code&gt; o &lt;code&gt;utcToZonedTime&lt;/code&gt;, son los nombres anteriores a la versión 3 de &lt;code&gt;date-fns-tz&lt;/code&gt;: pasaron a llamarse &lt;code&gt;fromZonedTime&lt;/code&gt; y &lt;code&gt;toZonedTime&lt;/code&gt;, según las notas de la versión 3.0.0.&lt;/p&gt;
&lt;h3&gt;Si prefieres no depender de &lt;code&gt;en-CA&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function fechaISO(instante: Date, timeZone: string): string {
  const partes = new Intl.DateTimeFormat(&#39;en-US&#39;, {
    timeZone, year: &#39;numeric&#39;, month: &#39;2-digit&#39;, day: &#39;2-digit&#39;,
  }).formatToParts(instante);
  const valor = (tipo: string) =&amp;gt; partes.find((p) =&amp;gt; p.type === tipo)!.value;
  return `${valor(&#39;year&#39;)}-${valor(&#39;month&#39;)}-${valor(&#39;day&#39;)}`;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;No mezcles los dos tipos de valor&lt;/h3&gt;
&lt;p&gt;Una fecha pura que se guardó como medianoche UTC y luego se convierte a la zona local retrocede un día. Decide desde el modelo de datos si cada campo es un instante o una fecha pura, y mantén esa decisión de punta a punta: formulario, API, base de datos y exportaciones.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;toISOString()&lt;/code&gt; formatea siempre en UTC, y &lt;code&gt;new Date(&#39;YYYY-MM-DD&#39;)&lt;/code&gt; es medianoche UTC; en UTC-5 eso cambia el día a las 19:00 locales.&lt;/li&gt;
  &lt;li&gt;Para instantes, formatea con una zona explícita: &lt;code&gt;formatInTimeZone&lt;/code&gt; o &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; con &lt;code&gt;timeZone&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Para fechas puras, conserva el string &lt;code&gt;YYYY-MM-DD&lt;/code&gt; o crea el &lt;code&gt;Date&lt;/code&gt; con &lt;code&gt;new Date(y, m - 1, d)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Para rangos por día, usa &lt;code&gt;fromZonedTime&lt;/code&gt; desde &lt;code&gt;T00:00:00&lt;/code&gt; hasta &lt;code&gt;T23:59:59.999&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Cubre el caso con un test entre las 19:00 y las 23:59.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString&quot;&gt;MDN: &lt;code&gt;Date.prototype.toISOString()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse&quot;&gt;MDN: &lt;code&gt;Date.parse()&lt;/code&gt; (strings de solo fecha y de fecha con hora)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/marnusw/date-fns-tz&quot;&gt;date-fns-tz: &lt;code&gt;formatInTimeZone&lt;/code&gt;, &lt;code&gt;fromZonedTime&lt;/code&gt; y &lt;code&gt;toZonedTime&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/7774545644624523547/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/la-fecha-sale-un-dia-antes-en.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/7774545644624523547'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/7774545644624523547'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/la-fecha-sale-un-dia-antes-en.html' title='La fecha sale un día antes en JavaScript: toISOString'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-595603453055308077</id><published>2026-10-13T09:00:00.000-05:00</published><updated>2026-10-13T09:00:00.204-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Next.js"/><category scheme="http://www.blogger.com/atom/ns#" term="Node.js"/><title type='text'>JavaScript heap out of memory en next build: solución</title><content type='html'>&lt;p&gt;Tu proyecto compila sin problemas en tu máquina, pero en Docker, en GitHub Actions o en un hook de Git el proceso se corta con este mensaje:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suele aparecer durante el paso &quot;Running TypeScript ...&quot; de &lt;code&gt;next build&lt;/code&gt; o al ejecutar &lt;code&gt;tsc --noEmit&lt;/code&gt;. Este artículo explica por qué ocurre y cómo subir el límite de memoria de Node.js en los tres lugares donde normalmente falta: el &lt;code&gt;Dockerfile&lt;/code&gt;, el pipeline de CI y el hook &lt;code&gt;pre-push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Node.js ejecuta tu código sobre el motor V8, que limita la memoria que puede usar el heap de JavaScript. Según la documentación de Node.js, ese límite es el que determinan los recursos del sistema o, si lo indicas, el valor de &lt;code&gt;--max-old-space-size&lt;/code&gt;. Dicho de otra forma: el límite por defecto no es el mismo en todas partes.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;En tu equipo, con bastante RAM, el límite por defecto es mayor y el build pasa.&lt;/li&gt;
  &lt;li&gt;En un contenedor o en un runner de CI con menos memoria, el límite por defecto es menor.&lt;/li&gt;
  &lt;li&gt;Cuando el proyecto crece, la comprobación de tipos y el empaquetado necesitan más memoria. La guía de Next.js indica que la verificación de tipos puede requerir mucha memoria en proyectos grandes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mientras el proyecto y el entorno no cambien, el resultado se repite, así que reintentar el job normalmente no sirve. Tampoco suele ser un error de tu código: el proyecto simplemente superó el límite. Si el consumo de memoria crece sin control entre versiones, entonces conviene perfilarlo, pero eso es un problema distinto.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;p&gt;La opción de V8 que lo resuelve es &lt;code&gt;--max-old-space-size&lt;/code&gt; (en MiB). Node.js permite pasarla en la variable de entorno &lt;code&gt;NODE_OPTIONS&lt;/code&gt;, y como las variables de entorno las heredan los procesos hijos, el valor llega también a los workers de &lt;code&gt;next build&lt;/code&gt; y a &lt;code&gt;tsc&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Elige un valor acorde con la RAM real.&lt;/strong&gt; Empieza con &lt;code&gt;4096&lt;/code&gt; (4 GiB) y sube a &lt;code&gt;6144&lt;/code&gt; solo si hace falta y la máquina lo soporta. Deja margen para el sistema y otros procesos; la propia documentación de Node.js sugiere, para una máquina de 2 GiB, usar 1536 para evitar el swap.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Dockerfile (etapa de build).&lt;/strong&gt; Define la variable justo antes del comando de build con &lt;a href=&quot;https://docs.docker.com/reference/dockerfile/&quot;&gt;la instrucción &lt;code&gt;ENV&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NODE_OPTIONS=--max-old-space-size=4096
RUN npm run build&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Como cada &lt;code&gt;FROM&lt;/code&gt; empieza desde cero, la variable no pasa a la etapa final a menos que esa etapa parta de &lt;code&gt;builder&lt;/code&gt;. Para un build no hace falta que esté en la imagen final.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;GitHub Actions.&lt;/strong&gt; Declara la variable en el &lt;code&gt;env&lt;/code&gt; del job para que la reciban todos los pasos (lint, tipos, tests y build):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  build:
    runs-on: ubuntu-latest
    env:
      NODE_OPTIONS: --max-old-space-size=4096
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run lint
      - run: npx tsc --noEmit
      - run: npm test
      - run: npm run build&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Hook &lt;code&gt;pre-push&lt;/code&gt; (por ejemplo con Husky).&lt;/strong&gt; Exporta la variable con un valor por defecto, de modo que cada persona pueda sobrescribirlo desde su entorno:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# .husky/pre-push
export NODE_OPTIONS=&quot;${NODE_OPTIONS:---max-old-space-size=4096}&quot;
npm run lint &amp;amp;&amp;amp; npx tsc --noEmit &amp;amp;&amp;amp; npm run build&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;La expresión &lt;code&gt;${NODE_OPTIONS:-valor}&lt;/code&gt; usa el valor solo si la variable no está definida o está vacía. Si ya tienes &lt;code&gt;NODE_OPTIONS&lt;/code&gt; con otras opciones, el hook no añade nada; en ese caso incluye la opción en tu propio valor.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Prueba local en PowerShell&lt;/strong&gt; para confirmar que el cambio hace efecto antes de subirlo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$env:NODE_OPTIONS = &quot;--max-old-space-size=4096&quot;
npm run build&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Primero comprueba que el límite realmente cambió. Este comando imprime el límite del heap en MiB:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NODE_OPTIONS=--max-old-space-size=4096 node -e &quot;console.log(Math.round(require(&#39;v8&#39;).getHeapStatistics().heap_size_limit / 1048576))&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La documentación del módulo &lt;code&gt;v8&lt;/code&gt; define &lt;code&gt;heap_size_limit&lt;/code&gt; como el tamaño máximo del heap, ya sea el límite por defecto o el valor de &lt;code&gt;--max_old_space_size&lt;/code&gt;. El número impreso será algo mayor que 4096 (en una prueba con Node.js 24 salió 4288), así que lo importante es que cambie respecto al valor sin la variable.&lt;/p&gt;
&lt;p&gt;Para la imagen de Docker, construye solo la etapa de build y revisa la variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build --target builder -t mi-app-builder .
docker run --rm mi-app-builder printenv NODE_OPTIONS&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Debe imprimir &lt;code&gt;--max-old-space-size=4096&lt;/code&gt;. Después comprueba lo importante: que el job de CI termine en verde con todos los pasos (lint, tipos, tests y build), que &lt;code&gt;git push&lt;/code&gt; pase el hook completo y que el build ya no muestre el &lt;code&gt;FATAL ERROR&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Un valor demasiado alto empeora todo&lt;/h3&gt;
&lt;p&gt;El valor de &lt;code&gt;--max-old-space-size&lt;/code&gt; es solo un tope: no crea memoria que la máquina no tenga. Si lo subes por encima de la RAM disponible, el sistema empieza a usar swap y el build puede quedarse prácticamente colgado. En un caso real, usar 8192 en una máquina de 7,4 GB de RAM llevó a swap y a un cuelgue de unos 25 minutos. En contenedores con límite de memoria, el kernel de Linux puede terminar procesos cuando falta memoria (lo describe la documentación de Docker sobre OOME), y entonces el síntoma es otro: el proceso desaparece sin el mensaje de V8.&lt;/p&gt;
&lt;h3&gt;No desactives la comprobación de tipos para &quot;arreglarlo&quot;&lt;/h3&gt;
&lt;p&gt;La guía de Next.js menciona que puedes saltarte el chequeo de tipos del build con &lt;code&gt;typescript.ignoreBuildErrors&lt;/code&gt;, pero advierte que puede producir despliegues defectuosos. Es mejor dar más memoria y mantener el chequeo.&lt;/p&gt;
&lt;h3&gt;Otras formas de reducir el consumo en Next.js&lt;/h3&gt;
&lt;p&gt;La misma guía documenta opciones como &lt;code&gt;experimental.webpackMemoryOptimizations&lt;/code&gt; (disponible desde la 15.0.0, experimental y pensada para Webpack) y desactivar la generación de source maps en producción. Úsalas como complemento, no como sustituto de un límite adecuado.&lt;/p&gt;
&lt;h3&gt;El CI estaba rojo y nadie lo notó&lt;/h3&gt;
&lt;p&gt;Un patrón frecuente: el workflow de pruebas y build falla, pero el despliegue vive en otro workflow que sí pasa, así que el error pasa desapercibido durante varios merges. Revisa todos los workflows del repositorio, no solo el que despliega.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;El error indica que el proceso alcanzó el límite del heap de V8, que depende del entorno; por eso falla en CI y en Docker aunque en local pase.&lt;/li&gt;
  &lt;li&gt;Define &lt;code&gt;NODE_OPTIONS=--max-old-space-size=4096&lt;/code&gt; (o 6144) en el Dockerfile, en el &lt;code&gt;env&lt;/code&gt; del job de CI y en el hook &lt;code&gt;pre-push&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Ajusta el valor a la RAM real: un número mayor que la memoria física causa swap y cuelgues.&lt;/li&gt;
  &lt;li&gt;Verifica con &lt;code&gt;heap_size_limit&lt;/code&gt; y con un pipeline completo en verde, sin desactivar la comprobación de tipos.&lt;/li&gt;
  &lt;li&gt;Vigila todos los workflows para que un CI en rojo no pase inadvertido.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/cli.html&quot;&gt;Node.js: opciones de línea de comandos (&lt;code&gt;--max-old-space-size&lt;/code&gt; y &lt;code&gt;NODE_OPTIONS&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/v8.html&quot;&gt;Node.js: módulo &lt;code&gt;v8&lt;/code&gt; y &lt;code&gt;getHeapStatistics()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/guides/memory-usage&quot;&gt;Next.js: cómo optimizar el uso de memoria&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/595603453055308077/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/javascript-heap-out-of-memory-en-next.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/595603453055308077'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/595603453055308077'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/javascript-heap-out-of-memory-en-next.html' title='JavaScript heap out of memory en next build: solución'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-8519088198098286677</id><published>2026-10-12T15:00:00.000-05:00</published><updated>2026-10-12T15:00:00.214-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Linux"/><title type='text'>Could not get lock /var/lib/dpkg/lock-frontend: solución</title><content type='html'>&lt;p&gt;Lanzas por SSH un script de instalación largo en un servidor Ubuntu 24.04 (Docker, swap, dependencias). A mitad de camino se corta la conexión, vuelves a entrar, ejecutas de nuevo el script y falla al primer &lt;code&gt;apt-get&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;E: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 1234 (apt-get)
N: Be aware that removing the lock file is not a solution and may break your system.
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El mensaje no habla de un fallo: dice que otro proceso tiene el candado del gestor de paquetes. Este artículo explica cómo averiguar cuál es, qué hacer con él sin dañar el sistema y cómo lanzar trabajos largos para que no dependan de tu terminal. Los comandos se contrastaron con los manuales de apt, dpkg y systemd, y con la documentación de Ubuntu Server; no se ejecutaron sobre un servidor.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;El gestor de paquetes permite que un solo proceso modifique la base de datos de paquetes a la vez, y lo garantiza con archivos de bloqueo como &lt;code&gt;/var/lib/dpkg/lock-frontend&lt;/code&gt;. Cuando el candado está en uso, el segundo proceso se niega a continuar e indica el PID y el nombre del que lo tiene (aquí, &lt;code&gt;apt-get&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Si la sesión SSH se corta a mitad de una instalación, desaparece el terminal, pero eso no garantiza que terminen todos los procesos que lanzó el script. Un &lt;code&gt;apt-get&lt;/code&gt; o un &lt;code&gt;dpkg&lt;/code&gt; que ya estaba en marcha puede seguir trabajando sin que nadie lo mire, y mientras lo haga conserva el candado. Es la razón por la que el segundo intento falla: el primero todavía no terminó.&lt;/p&gt;
&lt;p&gt;También puede ser otro proceso del sistema. La documentación de Ubuntu Server explica que &lt;code&gt;unattended-upgrades&lt;/code&gt; puede ejecutarse al arrancar la máquina y, mientras trabaja, impide otras operaciones de paquetes: si necesitas instalar algo, debe esperar a que terminen las actualizaciones. Es frecuente en un servidor recién creado.&lt;/p&gt;
&lt;p&gt;La advertencia del propio mensaje lo resume: borrar el archivo de bloqueo no es la solución y puede dañar el sistema.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Identifica el proceso que tiene el candado.&lt;/strong&gt; El mensaje ya da el PID. Con él y con las herramientas del sistema:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ps -p 1234 -o pid,etime,cmd
sudo fuser -v /var/lib/dpkg/lock-frontend
sudo lsof /var/lib/dpkg/lock-frontend
ps -ef | grep -E &#39;apt|dpkg&#39;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;ps -p&lt;/code&gt; muestra el comando completo y el tiempo que lleva en marcha (&lt;code&gt;etime&lt;/code&gt;). &lt;code&gt;fuser -v&lt;/code&gt; lista los procesos que usan el archivo, en formato parecido a &lt;code&gt;ps&lt;/code&gt;; &lt;code&gt;lsof&lt;/code&gt; hace algo equivalente, usa el que tengas instalado. La última línea busca cualquier proceso de apt o dpkg (verás también la propia línea de &lt;code&gt;grep&lt;/code&gt;).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Comprueba si avanza.&lt;/strong&gt; Mira el registro de dpkg y el registro de tu script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo tail -f /var/log/dpkg.log
tail -f instalar.log&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Si aparecen líneas nuevas (paquetes que se descomprimen, se configuran), el proceso sigue trabajando.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Si avanza, espera a que termine.&lt;/strong&gt; Este bucle se detiene cuando nadie usa el archivo (&lt;code&gt;fuser&lt;/code&gt; devuelve un código distinto de cero si ningún proceso accede a él):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while sudo fuser /var/lib/dpkg/lock-frontend &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; do sleep 5; done; echo &quot;candado libre&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Después vuelve a lanzar el script. Antes, comprueba que se pueda repetir sin efectos secundarios: instalar un paquete ya instalado no cambia nada, pero crear un archivo de swap o agregar una línea a &lt;code&gt;/etc/fstab&lt;/code&gt; por segunda vez sí puede.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Si lleva mucho tiempo sin avanzar, deténlo con cuidado.&lt;/strong&gt; Pide primero una terminación normal y espera:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo kill 1234&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Evita la señal &lt;code&gt;SIGKILL&lt;/code&gt; (&lt;code&gt;kill -9&lt;/code&gt;) salvo como último recurso: puede dejar paquetes a medio instalar. Si el proceso se interrumpió, repara el estado de dpkg antes de seguir:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dpkg --configure -a
sudo apt-get install -f&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Según el manual de dpkg, &lt;code&gt;--configure -a&lt;/code&gt; configura todos los paquetes desempaquetados pero sin configurar. La opción &lt;code&gt;-f&lt;/code&gt; (&lt;code&gt;--fix-broken&lt;/code&gt;) de apt-get intenta corregir un sistema con dependencias rotas.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No borres los archivos de bloqueo&lt;/strong&gt; (&lt;code&gt;rm /var/lib/dpkg/lock-frontend&lt;/code&gt; y similares). El mensaje de apt lo desaconseja de forma explícita, y el candado lo tiene un proceso vivo, no el archivo.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Lanza los trabajos largos desacoplados de tu terminal.&lt;/strong&gt; Tres opciones, de menos a más robusta:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. En una sesión nueva, sin terminal asociado
sudo setsid -f bash ./instalar.sh &amp;gt; instalar.log 2&amp;gt;&amp;amp;1 &amp;lt; /dev/null
tail -f instalar.log

# 2. Dentro de tmux: puedes desconectarte y volver
tmux new -s instalacion
bash ./instalar.sh
# Ctrl+b d para desacoplar; después: tmux attach -t instalacion

# 3. Como servicio transitorio de systemd
sudo systemd-run --unit=instalacion /bin/bash /ruta/instalar.sh
journalctl -u instalacion -f&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;El manual de &lt;code&gt;setsid&lt;/code&gt; indica que ejecuta un programa en una sesión nueva (y &lt;code&gt;-f&lt;/code&gt; obliga a crear un proceso nuevo, de modo que el comando devuelve el control enseguida). Lanza el script con &lt;code&gt;sudo&lt;/code&gt; desde el principio: sin terminal no podrás responder a una petición de contraseña a mitad del trabajo. En &lt;code&gt;tmux&lt;/code&gt;, &lt;code&gt;new -s&lt;/code&gt; crea una sesión con nombre y &lt;code&gt;attach -t&lt;/code&gt; se conecta a ella; la tecla para desacoplar por defecto es &lt;code&gt;Ctrl+b&lt;/code&gt; seguida de &lt;code&gt;d&lt;/code&gt;. Con &lt;code&gt;systemd-run&lt;/code&gt;, el comando corre como un servicio gestionado por systemd, en un entorno limpio y separado, con el gestor de servicios como proceso padre.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Si el trabajo debe sobrevivir a un reinicio, crea un servicio.&lt;/strong&gt; Archivo &lt;code&gt;/etc/systemd/system/instalacion.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Instalacion inicial del servidor
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/bin/bash /opt/instalador/instalar.sh
RemainAfterExit=yes
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable instalacion.service
sudo systemctl start --no-block instalacion.service&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;En &lt;code&gt;Type=oneshot&lt;/code&gt;, el tiempo de espera de arranque está desactivado por defecto, así que una instalación larga no se corta por duración. &lt;code&gt;Restart=on-failure&lt;/code&gt; está permitido para este tipo (el manual rechaza &lt;code&gt;always&lt;/code&gt; y &lt;code&gt;on-success&lt;/code&gt;) y relanza el script si termina con error, por lo que el script debe poder repetirse sin efectos secundarios. &lt;code&gt;RemainAfterExit=yes&lt;/code&gt; mantiene el servicio como activo cuando el script termina bien. &lt;code&gt;enable&lt;/code&gt; lo habilita para los próximos arranques. Sin &lt;code&gt;--no-block&lt;/code&gt;, &lt;code&gt;systemctl start&lt;/code&gt; espera a que el arranque de la unidad se complete, y en un servicio &lt;code&gt;oneshot&lt;/code&gt; eso ocurre cuando el script termina; con la opción, solo encola el trabajo y te devuelve la consola.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo fuser -v /var/lib/dpkg/lock-frontend
echo $?
sudo dpkg --audit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si nadie usa el archivo, &lt;code&gt;fuser&lt;/code&gt; no imprime procesos y el código de salida es distinto de cero. &lt;code&gt;dpkg --audit&lt;/code&gt; busca paquetes instalados solo en parte o con datos de control dañados; no debe listar nada. Si lanzaste el trabajo con &lt;code&gt;setsid&lt;/code&gt;, &lt;code&gt;tmux&lt;/code&gt; o &lt;code&gt;systemd-run&lt;/code&gt;, cierra la sesión SSH, vuelve a entrar y comprueba que el registro (&lt;code&gt;instalar.log&lt;/code&gt; o &lt;code&gt;journalctl -u instalacion&lt;/code&gt;) sigue creciendo.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Otros archivos de bloqueo&lt;/h3&gt;
&lt;p&gt;El mismo error puede nombrar &lt;code&gt;/var/lib/dpkg/lock&lt;/code&gt; o &lt;code&gt;/var/lib/apt/lists/lock&lt;/code&gt;. El procedimiento es idéntico: el mensaje indica el proceso que lo tiene.&lt;/p&gt;
&lt;h3&gt;Lo tiene unattended-upgrades&lt;/h3&gt;
&lt;p&gt;Si el proceso es de actualizaciones automáticas, lo correcto es esperar. Según la documentación de Ubuntu, esas actualizaciones las lanzan los temporizadores &lt;code&gt;apt-daily.timer&lt;/code&gt; y &lt;code&gt;apt-daily-upgrade.timer&lt;/code&gt;, y sus registros están en &lt;code&gt;/var/log/unattended-upgrades&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;apt pide ejecutar &lt;code&gt;dpkg --configure -a&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Suele indicar que una instalación anterior se interrumpió. Ejecuta el comando como se indica en el paso 4 y repite después la instalación.&lt;/p&gt;
&lt;h3&gt;Dos instaladores a la vez&lt;/h3&gt;
&lt;p&gt;Si lanzaste el trabajo como servicio y además a mano, el segundo chocará con el candado del primero. Elige un solo mecanismo y revisa con &lt;code&gt;systemctl status instalacion&lt;/code&gt; si ya está en marcha.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;El error significa que un proceso vivo (por ejemplo, el &lt;code&gt;apt-get&lt;/code&gt; de la primera ejecución) tiene el candado de dpkg; no es un archivo corrupto.&lt;/li&gt;
  &lt;li&gt;Identifícalo con el PID del mensaje, &lt;code&gt;ps&lt;/code&gt; y &lt;code&gt;fuser -v&lt;/code&gt;; si avanza, espera; si está atascado, termínalo con &lt;code&gt;kill&lt;/code&gt; y ejecuta &lt;code&gt;sudo dpkg --configure -a&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;No borres los archivos de bloqueo: el propio apt advierte que puede dañar el sistema.&lt;/li&gt;
  &lt;li&gt;Lanza instalaciones largas con &lt;code&gt;setsid -f&lt;/code&gt;, &lt;code&gt;tmux&lt;/code&gt; o &lt;code&gt;systemd-run&lt;/code&gt;, con el registro en un archivo.&lt;/li&gt;
  &lt;li&gt;Si debe sobrevivir a reinicios, usa un servicio &lt;code&gt;Type=oneshot&lt;/code&gt; con &lt;code&gt;Restart=on-failure&lt;/code&gt; y un script que se pueda repetir.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/bookworm/dpkg/dpkg.1.en.html&quot;&gt;dpkg: &lt;code&gt;dpkg(1)&lt;/code&gt;, opciones &lt;code&gt;--configure&lt;/code&gt; y &lt;code&gt;--audit&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/bookworm/systemd/systemd.service.5.en.html&quot;&gt;systemd: &lt;code&gt;systemd.service(5)&lt;/code&gt;, &lt;code&gt;Type=oneshot&lt;/code&gt;, &lt;code&gt;Restart=&lt;/code&gt; y &lt;code&gt;RemainAfterExit=&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ubuntu.com/server/docs/how-to/software/automatic-updates/&quot;&gt;Ubuntu Server: actualizaciones automáticas (&lt;code&gt;unattended-upgrades&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/8519088198098286677/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/12/could-not-get-lock-varlibdpkglock.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/8519088198098286677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/8519088198098286677'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/12/could-not-get-lock-varlibdpkglock.html' title='Could not get lock /var/lib/dpkg/lock-frontend: solución'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-4575366562230634063</id><published>2026-10-12T09:00:00.000-05:00</published><updated>2026-10-12T09:00:00.126-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Errores y soluciones"/><category scheme="http://www.blogger.com/atom/ns#" term="Linux"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>tar: Cannot change ownership to uid: Invalid argument</title><content type='html'>&lt;p&gt;Subes un proyecto desde Git Bash (Windows) a un servidor Linux empaquetándolo con &lt;code&gt;tar&lt;/code&gt; y enviándolo por SSH:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar cf - . | ssh usuario@servidor &quot;tar xf - -C /destino&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La transferencia termina, pero el &lt;code&gt;tar&lt;/code&gt; del servidor imprime una avalancha de líneas como estas:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar: ./archivo: Cannot change ownership to uid 197610, gid 197121: Invalid argument
tar: ./otro-archivo: Cannot change ownership to uid 197610, gid 197121: Operation not permitted
tar: Exiting with failure status due to previous errors&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Este artículo explica de dónde salen esos números, por qué el servidor intenta usarlos y cómo evitar el error. Los pasos de creación y listado del archivo se reprodujeron con GNU tar 1.35 de Git Bash en un directorio temporal; el error en sí ocurre en el servidor y no se reprodujo (no se usó ningún servidor), así que esa parte se apoya en el manual de GNU tar y en la documentación de Linux.&lt;/p&gt;

&lt;h2&gt;Por qué pasa&lt;/h2&gt;
&lt;p&gt;Un archivo &lt;code&gt;tar&lt;/code&gt; guarda, para cada entrada, el propietario y el grupo del archivo original. Puedes verlo con &lt;code&gt;tar tvf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar tvf proyecto.tar
tar --numeric-owner -tvf proyecto.tar&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;-rw-r--r-- usuario/197121     18 2026-01-15 10:30 ./package.json
-rw-r--r-- 197610/197121      18 2026-01-15 10:30 ./package.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Salida de ejemplo: la primera línea es el listado normal, con el nombre del propietario y el número de grupo; la segunda, el de &lt;code&gt;--numeric-owner&lt;/code&gt;, con números.) En Git Bash tu cuenta de Windows se presenta con un uid y un gid numéricos altos; en la prueba, &lt;code&gt;id -u&lt;/code&gt; devolvió un número de seis cifras. Esos números no corresponden a ninguna cuenta del servidor.&lt;/p&gt;
&lt;p&gt;Al extraer, el manual de GNU tar indica que &lt;code&gt;--same-owner&lt;/code&gt; (intentar extraer con el mismo propietario que figura en el archivo) es el comportamiento por defecto del superusuario, mientras que &lt;code&gt;--no-same-owner&lt;/code&gt; (extraer como tú mismo) es el de los usuarios normales. Por eso el problema aparece cuando extraes como &lt;code&gt;root&lt;/code&gt;: &lt;code&gt;tar&lt;/code&gt; intenta ejecutar un &lt;code&gt;chown&lt;/code&gt; a un uid y un gid que el servidor no conoce o no puede asignar.&lt;/p&gt;
&lt;p&gt;Los dos mensajes del error indican cosas distintas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;Operation not permitted&lt;/code&gt;:&lt;/strong&gt; el proceso no tiene privilegio para cambiar el propietario. Según &lt;code&gt;chown(2)&lt;/code&gt;, solo un proceso privilegiado (en Linux, con la capacidad &lt;code&gt;CAP_CHOWN&lt;/code&gt;) puede cambiar el propietario de un archivo.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;Invalid argument&lt;/code&gt;:&lt;/strong&gt; el sistema rechaza el número como propietario. Suele verse cuando el destino no puede representar ese id, por ejemplo en un contenedor o un espacio de nombres de usuario con un rango limitado: &lt;code&gt;user_namespaces(7)&lt;/code&gt; explica que cada espacio de nombres solo conoce los ids que tiene asignados y que los demás se tratan como no asignados.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El error no dice que el archivo esté dañado, solo que no se pudo asignar el propietario. Pero &lt;code&gt;tar&lt;/code&gt; termina con estado de fallo (el manual define el código 2 como error fatal), lo que puede cortar un script que encadene comandos con &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; o use &lt;code&gt;set -e&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Solución&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Extrae sin conservar el propietario del archivo.&lt;/strong&gt; Añade &lt;code&gt;--no-same-owner&lt;/code&gt; al &lt;code&gt;tar&lt;/code&gt; del servidor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar cf - . | ssh usuario@servidor &quot;mkdir -p /destino &amp;amp;&amp;amp; tar xf - --no-same-owner -C /destino&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Los archivos quedan a nombre del usuario que ejecuta la extracción. Si la aplicación corre con otra cuenta, ajusta después el propietario con &lt;code&gt;chown -R cuenta:grupo /destino&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Alternativa: crea el archivo con propietario neutro.&lt;/strong&gt; Con &lt;code&gt;--owner&lt;/code&gt;, &lt;code&gt;--group&lt;/code&gt; y &lt;code&gt;--numeric-owner&lt;/code&gt; el archivo registra el uid 0 y el gid 0 en lugar de los de Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar --owner=0 --group=0 --numeric-owner -cf - . | ssh usuario@servidor &quot;mkdir -p /destino &amp;amp;&amp;amp; tar xf - -C /destino&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;En la prueba, el listado con &lt;code&gt;tar tvf&lt;/code&gt; mostró &lt;code&gt;0/0&lt;/code&gt; en todas las entradas. Si el servidor extrae como &lt;code&gt;root&lt;/code&gt;, los archivos quedarán a nombre de &lt;code&gt;root&lt;/code&gt;; si lo hace un usuario normal, a nombre de ese usuario. Elige esta opción cuando quieras que el archivo sea portable y no solo arreglar el servidor.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Si también molestan los permisos, añade &lt;code&gt;--no-same-permissions&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar xf - --no-same-owner --no-same-permissions -C /destino&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Según el manual, preservar los permisos del archivo es lo habitual para el superusuario; &lt;code&gt;--no-same-permissions&lt;/code&gt; hace que se aplique la &lt;code&gt;umask&lt;/code&gt; del usuario que extrae. Úsalo solo si lo necesitas: puede quitar bits que quieras conservar (por ejemplo, el de ejecución de un script). Revisa después con &lt;code&gt;ls -l&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Comprueba que no falte ni cambie nada.&lt;/strong&gt; Genera una lista de sumas de verificación fuera de la carpeta del proyecto, envíala al servidor y compárala:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find . -type f -exec sha256sum {} + &amp;gt; ../manifiesto.sha256
scp ../manifiesto.sha256 usuario@servidor:/tmp/manifiesto.sha256
ssh usuario@servidor &quot;cd /destino &amp;amp;&amp;amp; sha256sum -c /tmp/manifiesto.sha256&quot;&lt;/code&gt;&lt;/pre&gt;
    &lt;p&gt;Cada archivo debe mostrar &lt;code&gt;OK&lt;/code&gt;. No guardes el manifiesto dentro del proyecto (por eso va en &lt;code&gt;..&lt;/code&gt;): así no se incluye a sí mismo en la lista ni en el envío.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Cómo verificar que quedó resuelto&lt;/h2&gt;
&lt;p&gt;Ejecuta de nuevo el comando de la solución y comprueba tres cosas:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;${PIPESTATUS[@]}&quot;
ssh usuario@servidor &quot;ls -ln /destino | head&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El primer comando se ejecuta en Git Bash justo después de la tubería y debe mostrar &lt;code&gt;0 0&lt;/code&gt;: el primer valor es el de &lt;code&gt;tar&lt;/code&gt; local y el segundo el de &lt;code&gt;ssh&lt;/code&gt;, que según su manual termina con el estado del comando remoto. El segundo comando muestra los propietarios numéricos: deben ser ids que existan en el servidor, no el de Windows. Por último, &lt;code&gt;sha256sum -c&lt;/code&gt; debe dar &lt;code&gt;OK&lt;/code&gt; en todos los archivos.&lt;/p&gt;

&lt;h2&gt;Variantes y errores relacionados&lt;/h2&gt;
&lt;h3&gt;Solo fallan algunos archivos con &lt;code&gt;Operation not permitted&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Es la misma causa: &lt;code&gt;tar&lt;/code&gt; intentó cambiar el propietario sin el privilegio necesario. La solución es la misma, &lt;code&gt;--no-same-owner&lt;/code&gt;, en lugar de dar más privilegios al proceso.&lt;/p&gt;
&lt;h3&gt;En PowerShell, &lt;code&gt;tar&lt;/code&gt; no es GNU tar&lt;/h3&gt;
&lt;p&gt;Las opciones de este artículo son de GNU tar (Git Bash y el servidor Linux). En una consola de PowerShell, &lt;code&gt;tar&lt;/code&gt; suele ser el bsdtar que trae Windows (&lt;code&gt;tar --version&lt;/code&gt; muestra &lt;code&gt;bsdtar&lt;/code&gt; y la versión de libarchive), con opciones distintas; la ayuda que imprime no lista &lt;code&gt;--owner&lt;/code&gt; ni &lt;code&gt;--group&lt;/code&gt;. Ejecuta el comando de creación desde Git Bash.&lt;/p&gt;
&lt;h3&gt;El error sale en un contenedor o en un disco montado&lt;/h3&gt;
&lt;p&gt;Si extraes dentro de un contenedor o sobre un sistema de archivos que no admite propietarios arbitrarios, &lt;code&gt;--no-same-owner&lt;/code&gt; también es la salida. Si de verdad necesitas conservar los propietarios, el servidor debe tener esos ids disponibles, y ese ya es un asunto de la configuración del contenedor o del montaje.&lt;/p&gt;

&lt;h2&gt;Resumen&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;El archivo de Git Bash guarda uid y gid numéricos altos de tu cuenta de Windows; el servidor no los conoce.&lt;/li&gt;
  &lt;li&gt;Como &lt;code&gt;root&lt;/code&gt;, GNU tar conserva por defecto el propietario del archivo (&lt;code&gt;--same-owner&lt;/code&gt;), y el &lt;code&gt;chown&lt;/code&gt; falla con &lt;code&gt;Invalid argument&lt;/code&gt; o &lt;code&gt;Operation not permitted&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;La solución directa es extraer con &lt;code&gt;tar xf - --no-same-owner -C /destino&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;También puedes crear el archivo con &lt;code&gt;--owner=0 --group=0 --numeric-owner&lt;/code&gt; para que no lleve ids de Windows.&lt;/li&gt;
  &lt;li&gt;Aunque el error sea solo de propietarios, comprueba el resultado con &lt;code&gt;sha256sum -c&lt;/code&gt; y revisa el estado de salida.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Referencias&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/bookworm/tar/tar.1.en.html&quot;&gt;GNU tar: &lt;code&gt;tar(1)&lt;/code&gt;, opciones &lt;code&gt;--same-owner&lt;/code&gt;, &lt;code&gt;--no-same-owner&lt;/code&gt;, &lt;code&gt;--no-same-permissions&lt;/code&gt;, &lt;code&gt;--owner&lt;/code&gt;, &lt;code&gt;--group&lt;/code&gt; y &lt;code&gt;--numeric-owner&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/bookworm/manpages-dev/chown.2.en.html&quot;&gt;Linux: &lt;code&gt;chown(2)&lt;/code&gt;, errores y privilegios (&lt;code&gt;CAP_CHOWN&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://manpages.debian.org/bookworm/manpages/user_namespaces.7.en.html&quot;&gt;Linux: &lt;code&gt;user_namespaces(7)&lt;/code&gt;, ids sin asignación&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/4575366562230634063/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/12/tar-cannot-change-ownership-to-uid.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/4575366562230634063'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/4575366562230634063'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/12/tar-cannot-change-ownership-to-uid.html' title='tar: Cannot change ownership to uid: Invalid argument'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-1793309783817111581</id><published>2026-10-11T16:20:25.921-05:00</published><updated>2026-10-11T16:20:25.921-05:00</updated><title type='text'>Cómo las Tendencias en Twitter (Ahora X) Influencian el Desarrollo de Software y Aplicaciones</title><content type='html'>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducción:&lt;/h2&gt;&lt;p&gt;Imagina un mundo donde lo que se dice en 280 caracteres o menos puede cambiar la forma en que interactuamos con nuestros dispositivos. Bueno, no necesitas imaginarlo: ese mundo es Twitter. Más que una simple plataforma de redes sociales, Twitter se ha convertido en un gigante influenciador, capaz de mover montañas en el mundo digital. O, en nuestro caso, capaz de influenciar el desarrollo de software y aplicaciones.&lt;/p&gt;&lt;p&gt;Twitter no es solo un lugar para compartir memes o discutir el episodio más reciente de tu serie favorita; es un hervidero de opiniones, ideas y, lo más importante, tendencias que reflejan lo que la gente realmente quiere y necesita. ¿Y adivina qué? Los desarrolladores de software y aplicaciones están prestando mucha atención. No es casualidad que las últimas actualizaciones de tu app favorita coincidan sorprendentemente con los temas más candentes en Twitter.&lt;/p&gt;&lt;p&gt;Así que, si alguna vez te has preguntado &quot;¿Alguien más piensa que esta app podría mejorar?&quot;, probablemente alguien en Twitter ya lo haya expresado, y alguien en alguna parte del mundo del desarrollo de software lo esté considerando seriamente. Vamos a sumergirnos en cómo Twitter, el titán de los tweets, está marcando el compás en el baile del desarrollo de software y aplicaciones. ¿Listo para twittear el camino hacia la innovación tecnológica?&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/1793309783817111581/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/como-las-tendencias-en-twitter-ahora-x.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/1793309783817111581'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/1793309783817111581'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2026/10/como-las-tendencias-en-twitter-ahora-x.html' title='Cómo las Tendencias en Twitter (Ahora X) Influencian el Desarrollo de Software y Aplicaciones'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-262798766735707022</id><published>2023-11-30T20:24:00.001-05:00</published><updated>2023-11-30T20:24:07.371-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI entretenimiento"/><category scheme="http://www.blogger.com/atom/ns#" term="AI hogar"/><category scheme="http://www.blogger.com/atom/ns#" term="Asistentes AI"/><category scheme="http://www.blogger.com/atom/ns#" term="Futuro AI"/><category scheme="http://www.blogger.com/atom/ns#" term="IA cotidiana"/><category scheme="http://www.blogger.com/atom/ns#" term="Salud AI"/><title type='text'>La Revolución Silenciosa: Cómo la Inteligencia Artificial Está Transformando Nuestra Vida Cotidiana</title><content type='html'>&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKhzDGgGR4rlgMncozNprWNRcdIXQLfYM9ehZlEAffzi60N6SRnUV5QBMVhY25tL_yf4b5Y3W2344qDbJjfDvM91iqWaBipx4rlO66cxLX0c5vrh1BvaUqTQcorXfcAoYAnoxdSdvhZeEjU3CsBIwWTrkYt75zmI16MSa0nCyzb_-ZR3GxS9-UoXhZVR1s/s1024/DALL%C2%B7E%202023-11-30%2019.00.15%20-%20A%20futuristic%20and%20humorous%20illustration%20representing%20the%20integration%20of%20artificial%20intelligence%20in%20daily%20life.%20The%20image%20should%20feature%20various%20element.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKhzDGgGR4rlgMncozNprWNRcdIXQLfYM9ehZlEAffzi60N6SRnUV5QBMVhY25tL_yf4b5Y3W2344qDbJjfDvM91iqWaBipx4rlO66cxLX0c5vrh1BvaUqTQcorXfcAoYAnoxdSdvhZeEjU3CsBIwWTrkYt75zmI16MSa0nCyzb_-ZR3GxS9-UoXhZVR1s/w400-h400/DALL%C2%B7E%202023-11-30%2019.00.15%20-%20A%20futuristic%20and%20humorous%20illustration%20representing%20the%20integration%20of%20artificial%20intelligence%20in%20daily%20life.%20The%20image%20should%20feature%20various%20element.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducción:&lt;/h2&gt;&lt;p&gt;Hola, ¡bienvenido a la era de los susurros tecnológicos! Aquí, en el tranquilo mundo de la Inteligencia Artificial (IA), donde las máquinas hablan en código y los algoritmos deciden qué café te gusta más. Sí, has leído bien. La IA no es solo una charla de nerds en una convención de ciencia ficción; es una realidad cotidiana, tan integrada en nuestras vidas que a veces ni siquiera nos damos cuenta. En este artículo, vamos a explorar (¡sin necesidad de un mapa del tesoro!) cómo la IA está cambiando silenciosamente todo, desde cómo bostezamos por la mañana hasta cómo nos deslizamos por nuestras redes sociales por la noche.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Asistentes Personales Inteligentes:&lt;/h2&gt;&lt;p&gt;¿Alguna vez has deseado tener un asistente personal que no se queje de tus malos chistes? ¡Bueno, la IA te cubre! Siri, Alexa y Google Assistant son como los genios de las lámparas modernas (pero sin límite de tres deseos). Vamos a profundizar en cómo estos pequeños genios digitales están haciendo nuestras vidas un poco menos complicadas. &lt;a href=&quot;https://tutorialestecnicos.blogspot.com/2023/11/asistentes-personales-inteligentes-la.html&quot; target=&quot;_blank&quot;&gt;Leer más.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Recomendaciones Personalizadas en Plataformas de Streaming:&lt;/h2&gt;&lt;p&gt;¿Te has preguntado cómo Netflix sabe que te encantará esa serie rara de ciencia ficción? No, no es magia; es IA. Aquí te contaré sobre los algoritmos que conocen tus gustos mejor que tu mejor amigo. &lt;a href=&quot;https://tutorialestecnicos.blogspot.com/2023/11/recomendaciones-personalizadas-en.html&quot; target=&quot;_blank&quot;&gt;Leer más.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;3. IA en el Comercio Electrónico:&lt;/h2&gt;&lt;p&gt;De compras en pijama y aún así recibiendo recomendaciones personalizadas: exploraremos cómo la IA está transformando la forma en que compramos en línea, desde escoger el par de zapatos perfecto hasta encontrar esa oferta de último minuto. &lt;a href=&quot;https://tutorialestecnicos.blogspot.com/2023/11/ia-en-el-comercio-electronico-tu.html&quot; target=&quot;_blank&quot;&gt;Leer más.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;4. IA en Dispositivos Domésticos:&lt;/h2&gt;&lt;p&gt;Nuestros hogares están más conectados que una red de chismes de vecindario, gracias a la IA. Descubre cómo la tecnología está haciendo que nuestras casas sean más inteligentes y por qué tal vez quieras agradecer a tu refrigerador la próxima vez que haga la lista de compras. &lt;a href=&quot;https://tutorialestecnicos.blogspot.com/2023/11/ia-en-dispositivos-domesticos-hogares.html&quot; target=&quot;_blank&quot;&gt;Leer más.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;5. Impacto en la Salud y Bienestar:&lt;/h2&gt;&lt;p&gt;La IA no solo se trata de comodidad y entretenimiento; también está ayudando a mantenernos en forma y saludables. Desde aplicaciones que monitorean tu sueño hasta dispositivos que te recuerdan beber agua, la IA está aquí para cuidar de ti. &lt;a href=&quot;https://tutorialestecnicos.blogspot.com/2023/11/impacto-de-la-ia-en-la-salud-y-el.html&quot; target=&quot;_blank&quot;&gt;Leer más.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusión:&lt;/h2&gt;&lt;p&gt;Entonces, ¿es la IA el futuro? ¡No! La IA es el presente, y está moldeando nuestro mundo de maneras que ni siquiera hemos empezado a comprender completamente. Pero no te preocupes, no estamos en una película de ciencia ficción apocalíptica... aún. Por ahora, disfrutemos de las maravillas de esta revolución silenciosa, siempre recordando usar la tecnología con sabiduría y un toque de humanidad.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/262798766735707022/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/la-revolucion-silenciosa-como-la.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/262798766735707022'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/262798766735707022'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/la-revolucion-silenciosa-como-la.html' title='La Revolución Silenciosa: Cómo la Inteligencia Artificial Está Transformando Nuestra Vida Cotidiana'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKhzDGgGR4rlgMncozNprWNRcdIXQLfYM9ehZlEAffzi60N6SRnUV5QBMVhY25tL_yf4b5Y3W2344qDbJjfDvM91iqWaBipx4rlO66cxLX0c5vrh1BvaUqTQcorXfcAoYAnoxdSdvhZeEjU3CsBIwWTrkYt75zmI16MSa0nCyzb_-ZR3GxS9-UoXhZVR1s/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2019.00.15%20-%20A%20futuristic%20and%20humorous%20illustration%20representing%20the%20integration%20of%20artificial%20intelligence%20in%20daily%20life.%20The%20image%20should%20feature%20various%20element.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-3720184405069816907</id><published>2023-11-30T20:22:00.001-05:00</published><updated>2023-11-30T20:22:03.271-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="apps fitness inteligentes"/><category scheme="http://www.blogger.com/atom/ns#" term="asistentes salud digitales"/><category scheme="http://www.blogger.com/atom/ns#" term="IA salud bienestar"/><category scheme="http://www.blogger.com/atom/ns#" term="inteligencia artificial cuidado personal."/><category scheme="http://www.blogger.com/atom/ns#" term="monitor sueño IA"/><category scheme="http://www.blogger.com/atom/ns#" term="recordatorios hidratación IA"/><category scheme="http://www.blogger.com/atom/ns#" term="tecnología bienestar personalizado"/><title type='text'>Impacto de la IA en la Salud y el Bienestar: Tu Entrenador Personal en el Bolsillo</title><content type='html'>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyds2MuEt-MHiB3LTLQzU9x3qxSHEPYrJDFxCTTozR6rYxUOeqX8nmauIbX2_OydcVXXvsb5LYWOTnybF9g7oIuNhbjbJjM2P6eJS1LfbnmBc4jyCTk-aVJDq-VVBbtWQQxcnbKSLPU55UzifAG_nfpy5-iN4NJkl7SWU1sS_fLAANmWkpR6121RCtZnf4/s1024/DALL%C2%B7E%202023-11-30%2020.20.59%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20impact%20of%20AI%20on%20health%20and%20wellness.%20The%20image%20should%20feature%20a%20variety%20of%20health-related%20elemen.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyds2MuEt-MHiB3LTLQzU9x3qxSHEPYrJDFxCTTozR6rYxUOeqX8nmauIbX2_OydcVXXvsb5LYWOTnybF9g7oIuNhbjbJjM2P6eJS1LfbnmBc4jyCTk-aVJDq-VVBbtWQQxcnbKSLPU55UzifAG_nfpy5-iN4NJkl7SWU1sS_fLAANmWkpR6121RCtZnf4/w400-h400/DALL%C2%B7E%202023-11-30%2020.20.59%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20impact%20of%20AI%20on%20health%20and%20wellness.%20The%20image%20should%20feature%20a%20variety%20of%20health-related%20elemen.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducción:&lt;/h2&gt;&lt;p&gt;¿Imaginas tener un entrenador personal que vive en tu smartphone y no te juzga por ese helado extra que comiste anoche? La Inteligencia Artificial (IA) está revolucionando el cuidado de la salud y el bienestar de formas que nunca imaginamos, ¡y no necesita que le pagues una membresía de gimnasio!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Tu Salud en Tus Manos (Literalmente):&lt;/h2&gt;&lt;p&gt;Las aplicaciones impulsadas por IA están transformando nuestros teléfonos en centros de salud personalizados. ¿Quieres mejorar tu calidad de sueño? Hay una app que analiza tus patrones de sueño y te da consejos para dormir mejor. ¿Olvidas constantemente beber agua? No te preocupes, tu teléfono vibrará recordándote que es hora de hidratarte. Incluso hay aplicaciones que pueden ayudarte a meditar, reducir el estrés y hasta guiarte en ejercicios de respiración. ¡Adiós, estrés!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Un Vistazo al Futuro de la Salud:&lt;/h2&gt;&lt;p&gt;Pero la IA no se detiene ahí. Está ayudando a los médicos a diagnosticar enfermedades con mayor precisión y rapidez, desde detectar anomalías en imágenes de rayos X hasta identificar síntomas de enfermedades raras. En el futuro, podríamos ver dispositivos aún más avanzados, como sensores que monitorean nuestra salud en tiempo real y nos avisan si algo anda mal. ¡Es como tener un doctor en tu bolsillo!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusión:&lt;/h2&gt;&lt;p&gt;La IA en el ámbito de la salud y el bienestar no es solo una tendencia pasajera; es una revolución que está aquí para quedarse. Con su ayuda, estamos aprendiendo a cuidar mejor de nosotros mismos y de nuestros seres queridos. Así que la próxima vez que tu reloj inteligente te diga que te muevas, ¡hazle caso! Podría ser el empujoncito que necesitas para llevar una vida más saludable. Y recuerda, ¡la IA está de tu lado!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/3720184405069816907/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/impacto-de-la-ia-en-la-salud-y-el.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3720184405069816907'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/3720184405069816907'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/impacto-de-la-ia-en-la-salud-y-el.html' title='Impacto de la IA en la Salud y el Bienestar: Tu Entrenador Personal en el Bolsillo'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyds2MuEt-MHiB3LTLQzU9x3qxSHEPYrJDFxCTTozR6rYxUOeqX8nmauIbX2_OydcVXXvsb5LYWOTnybF9g7oIuNhbjbJjM2P6eJS1LfbnmBc4jyCTk-aVJDq-VVBbtWQQxcnbKSLPU55UzifAG_nfpy5-iN4NJkl7SWU1sS_fLAANmWkpR6121RCtZnf4/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2020.20.59%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20impact%20of%20AI%20on%20health%20and%20wellness.%20The%20image%20should%20feature%20a%20variety%20of%20health-related%20elemen.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-1483063748044576508</id><published>2023-11-30T20:18:00.001-05:00</published><updated>2023-11-30T20:18:03.132-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Automatización del Hogar"/><category scheme="http://www.blogger.com/atom/ns#" term="Dispositivos Conectados"/><category scheme="http://www.blogger.com/atom/ns#" term="Electrodomésticos Inteligentes"/><category scheme="http://www.blogger.com/atom/ns#" term="Hogar Inteligente"/><category scheme="http://www.blogger.com/atom/ns#" term="Innovación Tecnológica"/><category scheme="http://www.blogger.com/atom/ns#" term="Inteligencia Artificial"/><category scheme="http://www.blogger.com/atom/ns#" term="Tecnología Doméstica"/><category scheme="http://www.blogger.com/atom/ns#" term="Vida Cotidiana"/><title type='text'>IA en Dispositivos Domésticos: Hogares Inteligentes y sus Chismes Tecnológicos</title><content type='html'>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKB_EcCyU8YYajotUtzK1nS_IeWDeOLGF-9Aq7TWTL3KX4kmQDDTyoidFhtJzR75KJV0EOWg5JEQPI5uM4kXvVJTyu6et8oniij9lw4rcHRbqJzNE1TSHS6wsofwU-N5cNBq_yRq08izNFMvd77mT5SJR0yGfgNGkjKuO33PPwjKOxo2E0scp1jC2NZmwN/s1024/DALL%C2%B7E%202023-11-30%2020.16.54%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20concept%20of%20smart%20home%20devices%20influenced%20by%20artificial%20intelligence.%20The%20scene%20should%20show%20a%20var.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKB_EcCyU8YYajotUtzK1nS_IeWDeOLGF-9Aq7TWTL3KX4kmQDDTyoidFhtJzR75KJV0EOWg5JEQPI5uM4kXvVJTyu6et8oniij9lw4rcHRbqJzNE1TSHS6wsofwU-N5cNBq_yRq08izNFMvd77mT5SJR0yGfgNGkjKuO33PPwjKOxo2E0scp1jC2NZmwN/w400-h400/DALL%C2%B7E%202023-11-30%2020.16.54%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20concept%20of%20smart%20home%20devices%20influenced%20by%20artificial%20intelligence.%20The%20scene%20should%20show%20a%20var.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducción:&lt;/h2&gt;&lt;p&gt;Imagina un mundo donde tu refrigerador critica tus elecciones de snacks nocturnos, o tu aspiradora tiene un mapa mental de cada rincón de tu sala. Bueno, no estamos lejos de eso. Con la Inteligencia Artificial (IA) infiltrándose en nuestros hogares, estamos viviendo en una versión menos dramática de una película de ciencia ficción. Vamos a zambullirnos en este mundo donde los dispositivos hablan, piensan (un poco) y, lo más importante, nos hacen la vida más fácil.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Los Chismosos de la Casa: Dispositivos Inteligentes:&lt;/h2&gt;&lt;p&gt;Desde termostatos que aprenden tu rutina diaria hasta luces que cambian de color según tu estado de ánimo, la IA está convirtiendo nuestros hogares en algo sacado de una novela futurista. Y no es solo por diversión; hay una utilidad real aquí. Por ejemplo, esa cámara de seguridad que te avisa si un extraño ronda tu casa, o el asistente de voz que te recuerda comprar leche. ¿Quién necesita un mayordomo cuando tienes un ecosistema de gadgets inteligentes que se ocupan de todo?&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Hablando con las Paredes: La Integración de IA:&lt;/h2&gt;&lt;p&gt;La integración de IA en dispositivos domésticos no se trata solo de comodidad; es una revolución en cómo interactuamos con nuestro entorno. Ahora puedes hablarle a tu televisor para cambiar de canal o preguntarle a tu horno cómo cocinar el pollo perfecto. Y mientras que algunos podrían argumentar que nos estamos volviendo demasiado dependientes de estos dispositivos, la mayoría diría que es simplemente aprovechar lo que la tecnología tiene para ofrecer.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusión: Agradeciendo a tus Electrodomésticos:&lt;/h2&gt;&lt;p&gt;Así que la próxima vez que pases por tu nevera, tal vez quieras darle las gracias. Después de todo, está trabajando diligentemente para asegurarse de que nunca te falten tus snacks favoritos. La IA en los dispositivos domésticos no solo está haciendo nuestras casas más inteligentes, sino que también está agregando un toque de magia tecnológica a nuestras vidas cotidianas. Y quién sabe, tal vez en el futuro, incluso nuestros tostadores tendrán algo interesante que decir.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/1483063748044576508/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/ia-en-dispositivos-domesticos-hogares.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/1483063748044576508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/1483063748044576508'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/ia-en-dispositivos-domesticos-hogares.html' title='IA en Dispositivos Domésticos: Hogares Inteligentes y sus Chismes Tecnológicos'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKB_EcCyU8YYajotUtzK1nS_IeWDeOLGF-9Aq7TWTL3KX4kmQDDTyoidFhtJzR75KJV0EOWg5JEQPI5uM4kXvVJTyu6et8oniij9lw4rcHRbqJzNE1TSHS6wsofwU-N5cNBq_yRq08izNFMvd77mT5SJR0yGfgNGkjKuO33PPwjKOxo2E0scp1jC2NZmwN/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2020.16.54%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20concept%20of%20smart%20home%20devices%20influenced%20by%20artificial%20intelligence.%20The%20scene%20should%20show%20a%20var.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-7740748397075701305</id><published>2023-11-30T20:11:00.003-05:00</published><updated>2023-11-30T20:11:49.338-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Compras Online"/><category scheme="http://www.blogger.com/atom/ns#" term="Experiencia de Usuario IA"/><category scheme="http://www.blogger.com/atom/ns#" term="IA en Retail"/><category scheme="http://www.blogger.com/atom/ns#" term="Inteligencia Artificial en Comercio"/><category scheme="http://www.blogger.com/atom/ns#" term="recomendaciones personalizadas"/><category scheme="http://www.blogger.com/atom/ns#" term="Tecnología de Compras"/><category scheme="http://www.blogger.com/atom/ns#" term="Tendencias E-Commerce"/><title type='text'> IA en el Comercio Electrónico: Tu Estilista Personal en Pijama</title><content type='html'>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnraINL81ig_zLUVmE6PrCTE667AFwkBmU4zrBfGUbbondxCby0VH48LaEBb2gmifv7Tkgu_T6k4p7QapToupLH8AiD5zSM9tHdlyzbAzTo_d6f0QLpnSS6z0mRBmU8GMIXWGXGbw_0hPURc4TYgqOs0IkFQw4UZfD_3b1gXMslF3ovHb6ns5svHVJV9JO/s1024/DALL%C2%B7E%202023-11-30%2020.09.27%20-%20A%20colorful,%20humorous%20illustration%20representing%20the%20concept%20of%20AI%20in%20e-commerce.%20The%20image%20shows%20a%20person%20in%20pajamas%20shopping%20online%20on%20a%20laptop,%20surro.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnraINL81ig_zLUVmE6PrCTE667AFwkBmU4zrBfGUbbondxCby0VH48LaEBb2gmifv7Tkgu_T6k4p7QapToupLH8AiD5zSM9tHdlyzbAzTo_d6f0QLpnSS6z0mRBmU8GMIXWGXGbw_0hPURc4TYgqOs0IkFQw4UZfD_3b1gXMslF3ovHb6ns5svHVJV9JO/w400-h400/DALL%C2%B7E%202023-11-30%2020.09.27%20-%20A%20colorful,%20humorous%20illustration%20representing%20the%20concept%20of%20AI%20in%20e-commerce.%20The%20image%20shows%20a%20person%20in%20pajamas%20shopping%20online%20on%20a%20laptop,%20surro.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&amp;nbsp;Introducción:&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;¿Quién hubiera imaginado que comprar en pijama podía convertirse en una experiencia tan personalizada y sofisticada? Gracias a la inteligencia artificial (IA), el comercio electrónico ha evolucionado de ser solo una conveniencia a ser una experiencia casi mágica, donde cada clic te acerca más a lo que realmente quieres (¡o a lo que no sabías que querías hasta que lo viste!).&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;La Magia Detrás del Carrito de Compras:&lt;/h2&gt;&lt;p&gt;Piénsalo: cada vez que buscas un nuevo par de zapatillas en línea, aparecen sugerencias que parecen leer tu mente. ¿Es telepatía? No, es IA. Utilizando datos de tus búsquedas anteriores, compras y hasta los artículos que abandonaste en el carrito, la IA configura un perfil de tus gustos y preferencias. Así, cada sugerencia es como un consejo de un amigo que conoce tus gustos a la perfección.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Más Que Solo Compras: Una Experiencia Personalizada:&lt;/h2&gt;&lt;p&gt;En el mundo del e-commerce impulsado por IA, cada usuario recibe una experiencia de compra única. ¿Te gustan los estampados de leopardo? La IA lo recordará. ¿Prefieres las ofertas sobre las novedades? También lo sabrá. Esta tecnología no solo te muestra productos que podrían gustarte, sino que también adapta las ofertas y promociones según tus hábitos de compra. ¡Adiós, anuncios irrelevantes!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Un Futuro de Compras Inteligentes:&lt;/h2&gt;&lt;p&gt;La IA está transformando no solo cómo compramos, sino también cómo se gestionan los inventarios y se predice la demanda. Imagina un futuro donde nunca haya un &quot;agotado&quot; en tu talla porque la IA ya predijo la demanda y ajustó el stock. O donde los precios fluctúen inteligentemente según las tendencias de mercado, maximizando tanto la satisfacción del cliente como el beneficio de la tienda.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusión:&lt;/h2&gt;&lt;p&gt;Comprar en línea ya no es solo hacer clic en un botón de compra. Es una experiencia personalizada, casi como tener un estilista personal que te conoce mejor que nadie (¡y todo esto mientras estás cómodamente en pijama!). La IA en el comercio electrónico no es solo una moda pasajera; es el futuro brillante y emocionante del shopping online. Así que la próxima vez que recibas una recomendación perfecta, recuerda: hay una IA trabajando para hacerte feliz.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/7740748397075701305/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/ia-en-el-comercio-electronico-tu.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/7740748397075701305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/7740748397075701305'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/ia-en-el-comercio-electronico-tu.html' title=' IA en el Comercio Electrónico: Tu Estilista Personal en Pijama'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnraINL81ig_zLUVmE6PrCTE667AFwkBmU4zrBfGUbbondxCby0VH48LaEBb2gmifv7Tkgu_T6k4p7QapToupLH8AiD5zSM9tHdlyzbAzTo_d6f0QLpnSS6z0mRBmU8GMIXWGXGbw_0hPURc4TYgqOs0IkFQw4UZfD_3b1gXMslF3ovHb6ns5svHVJV9JO/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2020.09.27%20-%20A%20colorful,%20humorous%20illustration%20representing%20the%20concept%20of%20AI%20in%20e-commerce.%20The%20image%20shows%20a%20person%20in%20pajamas%20shopping%20online%20on%20a%20laptop,%20surro.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-8492217699586386505</id><published>2023-11-30T20:05:00.002-05:00</published><updated>2023-11-30T20:05:27.070-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="algoritmos de IA"/><category scheme="http://www.blogger.com/atom/ns#" term="binge-watching"/><category scheme="http://www.blogger.com/atom/ns#" term="Disney+"/><category scheme="http://www.blogger.com/atom/ns#" term="elecciones de entretenimiento"/><category scheme="http://www.blogger.com/atom/ns#" term="Hulu"/><category scheme="http://www.blogger.com/atom/ns#" term="Netflix"/><category scheme="http://www.blogger.com/atom/ns#" term="recomendaciones personalizadas"/><category scheme="http://www.blogger.com/atom/ns#" term="series de ciencia ficción"/><category scheme="http://www.blogger.com/atom/ns#" term="streaming"/><category scheme="http://www.blogger.com/atom/ns#" term="tendencias de visualización"/><title type='text'>Recomendaciones Personalizadas en Plataformas de Streaming: El Genio Detrás de la Pantalla</title><content type='html'>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9y8bMBdUU3EWHWhObPlEg3ukILCQNHascYua68ODMNSm4Oyuspb4ux8k3EumcA_bg3dQHcvgiDasNjvc1UCw-bi1UCODbTwnQVWzUEwne6ahbVESRd2lwfCN19w6ZtQW6dofIAAVqJ3vca44blPEcAY_b-YUFSeveW-BZ9Mo5fTnsRpYq216ZHSu-CPY_/s1024/DALL%C2%B7E%202023-11-30%2020.03.23%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20concept%20of%20personalized%20recommendations%20on%20streaming%20platforms.%20The%20image%20shows%20a%20diverse%20group%20.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9y8bMBdUU3EWHWhObPlEg3ukILCQNHascYua68ODMNSm4Oyuspb4ux8k3EumcA_bg3dQHcvgiDasNjvc1UCw-bi1UCODbTwnQVWzUEwne6ahbVESRd2lwfCN19w6ZtQW6dofIAAVqJ3vca44blPEcAY_b-YUFSeveW-BZ9Mo5fTnsRpYq216ZHSu-CPY_/w400-h400/DALL%C2%B7E%202023-11-30%2020.03.23%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20concept%20of%20personalized%20recommendations%20on%20streaming%20platforms.%20The%20image%20shows%20a%20diverse%20group%20.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducción:&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;¿Te ha sorprendido alguna vez esa sensación de que Netflix te conoce mejor que tu propia madre? Pues no estás solo. Resulta que detrás de esas recomendaciones, tan precisas que dan miedo, hay un cerebrito trabajando incansablemente: la inteligencia artificial. Y no, no se trata de un stalker digital, sino de algoritmos sofisticados que se han vuelto expertos en descifrar tus gustos y preferencias.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;El Misterio de las Recomendaciones Personalizadas:&lt;/h2&gt;&lt;p&gt;Imagina un chef que, sin preguntarte, prepara tu plato favorito cada vez que visitas su restaurante. Así funciona la IA en las plataformas de streaming. Estos algoritmos observan y aprenden de cada elección que haces: lo que miras, lo que te gusta, incluso lo que descartas después de 10 minutos. Basándose en estos datos, te presentan una bandeja llena de opciones personalizadas que probablemente te encantarán. Y lo mejor de todo es que, ¡no tienes que compartir tus palomitas de maíz con ellos!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Más Allá del &quot;Me Gusta&quot;:&lt;/h2&gt;&lt;p&gt;La IA en las plataformas de streaming va más allá de un simple &quot;me gusta&quot;. Estos sistemas son como aquel amigo que te recomienda libros, pero a nivel experto. Analizan tendencias, calificaciones, e incluso se atreven a sugerirte algo fuera de tu zona de confort, como esa serie de documentales sobre insectos que, sorprendentemente, terminaste maratoneando un fin de semana.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;El Humor y la Inteligencia Artificial:&lt;/h2&gt;&lt;p&gt;Y lo que es más intrigante, estos sistemas aprenden con el tiempo. Empiezan entendiendo tus elecciones básicas y, poco a poco, se convierten en expertos de tus hábitos. ¿El resultado? Una experiencia de visualización tan personalizada que te sentirás como en una cita con tu alma gemela cinematográfica.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusión:&lt;/h2&gt;&lt;p&gt;En resumen, la próxima vez que te encuentres navegando por las recomendaciones de tu plataforma de streaming favorita, recuerda: no es magia, es inteligencia artificial. Estos algoritmos no solo conocen tus gustos, sino que también te desafían a explorar nuevos horizontes. Así que, ¡dale una oportunidad a esa sugerencia rara y disfruta del viaje que la IA ha preparado para ti!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/8492217699586386505/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/recomendaciones-personalizadas-en.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/8492217699586386505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/8492217699586386505'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/recomendaciones-personalizadas-en.html' title='Recomendaciones Personalizadas en Plataformas de Streaming: El Genio Detrás de la Pantalla'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9y8bMBdUU3EWHWhObPlEg3ukILCQNHascYua68ODMNSm4Oyuspb4ux8k3EumcA_bg3dQHcvgiDasNjvc1UCw-bi1UCODbTwnQVWzUEwne6ahbVESRd2lwfCN19w6ZtQW6dofIAAVqJ3vca44blPEcAY_b-YUFSeveW-BZ9Mo5fTnsRpYq216ZHSu-CPY_/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2020.03.23%20-%20A%20colorful%20and%20humorous%20illustration%20representing%20the%20concept%20of%20personalized%20recommendations%20on%20streaming%20platforms.%20The%20image%20shows%20a%20diverse%20group%20.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-231224299791073975</id><published>2023-11-30T19:32:00.001-05:00</published><updated>2023-11-30T19:32:02.463-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Alexa"/><category scheme="http://www.blogger.com/atom/ns#" term="Asistentes Inteligentes"/><category scheme="http://www.blogger.com/atom/ns#" term="Ayuda digital"/><category scheme="http://www.blogger.com/atom/ns#" term="Google Assistant"/><category scheme="http://www.blogger.com/atom/ns#" term="IA en la vida diaria"/><category scheme="http://www.blogger.com/atom/ns#" term="Siri"/><category scheme="http://www.blogger.com/atom/ns#" term="Tecnología AI"/><category scheme="http://www.blogger.com/atom/ns#" term="Vida moderna"/><title type='text'>Asistentes Personales Inteligentes: La Magia de la IA en Tu Bolsillo</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2xEjVYGYOa-hCLZ25GQ3Zs2qd6JayI2-mM1cRumNp9IaxrI_DEPQYZSfpk9JxC8Q_q5ub8LlMiWQTd0UjGQOm6Gvclm3s4YHbnZ3DE9DYgwSWnrKJj3ubwOGnH4WAn_tL_mKUSED7ujJENLynPjmZM5EmvSn98oPO0goTJZrtwOkUfepkAJzjiKJxViEg/s1024/DALL%C2%B7E%202023-11-30%2019.26.50%20-%20Una%20ilustraci%C3%B3n%20humor%C3%ADstica%20y%20colorida%20de%20asistentes%20personales%20inteligentes%20en%20la%20vida%20cotidiana.%20Muestra%20caricaturas%20de%20Siri,%20Alexa%20y%20Google%20Assista.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2xEjVYGYOa-hCLZ25GQ3Zs2qd6JayI2-mM1cRumNp9IaxrI_DEPQYZSfpk9JxC8Q_q5ub8LlMiWQTd0UjGQOm6Gvclm3s4YHbnZ3DE9DYgwSWnrKJj3ubwOGnH4WAn_tL_mKUSED7ujJENLynPjmZM5EmvSn98oPO0goTJZrtwOkUfepkAJzjiKJxViEg/w400-h400/DALL%C2%B7E%202023-11-30%2019.26.50%20-%20Una%20ilustraci%C3%B3n%20humor%C3%ADstica%20y%20colorida%20de%20asistentes%20personales%20inteligentes%20en%20la%20vida%20cotidiana.%20Muestra%20caricaturas%20de%20Siri,%20Alexa%20y%20Google%20Assista.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Introducción:&lt;/h2&gt;&lt;p&gt;Imagina un mundo donde tienes un asistente personal que no solo es infalible sino que también soporta pacientemente tus bromas a altas horas de la madrugada. Ese mundo es ahora una realidad gracias a Siri, Alexa y Google Assistant. Estos asistentes virtuales, que han ido más allá de la mera tolerancia a nuestros chistes malos, están redefiniendo nuestra interacción cotidiana con la tecnología. Olvídate de frotar lámparas mágicas; estos genios digitales están listos para cumplir tus deseos con solo un comando de voz.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;La Vida con Asistentes Personales Inteligentes:&lt;/h2&gt;&lt;p&gt;La era de los asistentes personales inteligentes es una mezcla de comodidad y magia. Imagina despertar cada mañana con el aroma de un café recién hecho porque Alexa recordó encender tu cafetera inteligente. O qué tal cuando Siri te salva de un momento incómodo recordándote el nombre de alguien justo antes de una reunión. Y cuando se trata de evitar el tráfico, Google Assistant se convierte en tu copiloto personal, encontrando la ruta más rápida y segura a tu destino.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Más que Simples Ayudantes:&lt;/h2&gt;&lt;p&gt;Pero la verdadera magia de estos asistentes va más allá de las tareas diarias. Son como amigos digitales que te conocen íntimamente. ¿Quieres relajarte? Alexa puede reproducir tu lista de reproducción de jazz favorita sin que tengas que pedírselo dos veces. ¿Necesitas un empujoncito para hacer ejercicio? Google Assistant te recordará amablemente tu rutina de ejercicios, y Siri puede incluso motivarte con un poco de humor. Son como esos amigos perfectos, siempre disponibles, nunca críticos y eternamente pacientes.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusiones:&lt;/h2&gt;&lt;p&gt;Los asistentes personales inteligentes son más que una maravilla tecnológica; son compañeros en nuestro viaje diario, facilitadores de una vida más fácil y entretenida. No solo están aquí para cumplir órdenes, sino para enriquecer nuestra vida diaria con su inteligencia y su toque personal. La próxima vez que interactúes con tu asistente de IA, recuerda que estás presenciando un milagro de la tecnología moderna: un genio en tu bolsillo, siempre listo para ayudarte con una sonrisa digital.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/231224299791073975/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/asistentes-personales-inteligentes-la.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/231224299791073975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/231224299791073975'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/11/asistentes-personales-inteligentes-la.html' title='Asistentes Personales Inteligentes: La Magia de la IA en Tu Bolsillo'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2xEjVYGYOa-hCLZ25GQ3Zs2qd6JayI2-mM1cRumNp9IaxrI_DEPQYZSfpk9JxC8Q_q5ub8LlMiWQTd0UjGQOm6Gvclm3s4YHbnZ3DE9DYgwSWnrKJj3ubwOGnH4WAn_tL_mKUSED7ujJENLynPjmZM5EmvSn98oPO0goTJZrtwOkUfepkAJzjiKJxViEg/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2019.26.50%20-%20Una%20ilustraci%C3%B3n%20humor%C3%ADstica%20y%20colorida%20de%20asistentes%20personales%20inteligentes%20en%20la%20vida%20cotidiana.%20Muestra%20caricaturas%20de%20Siri,%20Alexa%20y%20Google%20Assista.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3279603830404251843.post-4192883723394829746</id><published>2023-05-21T00:00:00.001-05:00</published><updated>2023-11-30T20:28:12.768-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Aprendizaje Online"/><category scheme="http://www.blogger.com/atom/ns#" term="Cirugía"/><category scheme="http://www.blogger.com/atom/ns#" term="Comunicación"/><category scheme="http://www.blogger.com/atom/ns#" term="Educación"/><category scheme="http://www.blogger.com/atom/ns#" term="Energías Renovables"/><category scheme="http://www.blogger.com/atom/ns#" term="Información"/><category scheme="http://www.blogger.com/atom/ns#" term="Medio Ambiente"/><category scheme="http://www.blogger.com/atom/ns#" term="Salud"/><category scheme="http://www.blogger.com/atom/ns#" term="Tecnología"/><category scheme="http://www.blogger.com/atom/ns#" term="Tecnología en el Aula"/><category scheme="http://www.blogger.com/atom/ns#" term="Tecnología Verde"/><category scheme="http://www.blogger.com/atom/ns#" term="Telemedicina"/><category scheme="http://www.blogger.com/atom/ns#" term="Transformación Digital"/><title type='text'>¿Cómo ha cambiado la tecnología nuestras vidas?</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6fn5uzL67U-rjvSJGoYET5LgNN-h9jIDagWbYQzriGYQD4ZjplOi7JG-opjUHwNbChqG6RFC_C4qxKF1j0W94kJCWrNowJIJYWY7iRE7JLu5A0LGC3a3TGtZ01Mg_-ktNPdio_Uk5KHeBW_FdyfnHiRwxf8FtSMIETxTZRDujc6WVG75foCQzDfajjiMY/s1024/DALL%C2%B7E%202023-11-30%2020.26.40%20-%20A%20futuristic%20illustration%20representing%20the%20impact%20of%20technology%20on%20various%20aspects%20of%20life,%20featuring%20a%20digital%20landscape%20with%20icons%20or%20visual%20represe.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6fn5uzL67U-rjvSJGoYET5LgNN-h9jIDagWbYQzriGYQD4ZjplOi7JG-opjUHwNbChqG6RFC_C4qxKF1j0W94kJCWrNowJIJYWY7iRE7JLu5A0LGC3a3TGtZ01Mg_-ktNPdio_Uk5KHeBW_FdyfnHiRwxf8FtSMIETxTZRDujc6WVG75foCQzDfajjiMY/w400-h400/DALL%C2%B7E%202023-11-30%2020.26.40%20-%20A%20futuristic%20illustration%20representing%20the%20impact%20of%20technology%20on%20various%20aspects%20of%20life,%20featuring%20a%20digital%20landscape%20with%20icons%20or%20visual%20represe.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;La tecnología ha cambiado nuestras vidas de formas increíbles. ¿Te has detenido alguna vez a pensar en cómo ha cambiado la forma en que nos comunicamos, cómo aprendemos o incluso cómo cuidamos nuestra salud? En este artículo, vamos a explorar todo eso y más.&lt;/p&gt;&lt;p&gt;Primero, vamos a hablar sobre cómo la &lt;b&gt;Transformación Digital&lt;/b&gt; ha cambiado la forma en que interactuamos con el mundo. Ya no necesitamos mapas de papel o enciclopedias pesadas. Todo lo que necesitamos está al alcance de nuestros dedos.&lt;/p&gt;&lt;p&gt;Luego, nos adentraremos en el mundo de la&lt;b&gt; Tecnología y Salud&lt;/b&gt;. ¿Sabías que la tecnología está ayudando a los médicos a salvar vidas y a hacer que la atención médica sea más accesible para todos?&lt;/p&gt;&lt;p&gt;Después, hablaremos sobre la &lt;b&gt;Tecnología en la Educación&lt;/b&gt;. Las aulas ya no están limitadas a cuatro paredes. Ahora, podemos aprender desde cualquier lugar, en cualquier momento.&lt;/p&gt;&lt;p&gt;Por último, pero no menos importante, discutiremos la&lt;b&gt; Tecnología y el Medio Ambiente&lt;/b&gt;. Aunque la tecnología puede tener un impacto en nuestro planeta, también está ayudando a encontrar soluciones para un futuro más verde y sostenible.&lt;/p&gt;&lt;p&gt;Así que, si estás listo para un viaje emocionante a través de cómo la tecnología ha cambiado nuestras vidas, ¡sigue leyendo!&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Transformación Digital: Un salto hacia el futuro&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;La revolución de la comunicación: De cartas a mensajes instantáneos&lt;/h3&gt;&lt;p&gt;La Transformación Digital ha marcado un antes y un después en nuestra forma de interactuar con el mundo. Este salto hacia el futuro ha sido impulsado por la rápida evolución de la tecnología, que ha cambiado la forma en que trabajamos, aprendemos, nos comunicamos y, en general, vivimos nuestras vidas. La digitalización ha abierto un mundo de posibilidades, permitiéndonos acceder a información y servicios con solo unos pocos clics o toques en una pantalla. Ya no estamos limitados por la geografía o el tiempo. Podemos trabajar desde casa, aprender nuevas habilidades en línea, comprar productos de todo el mundo y conectarnos con personas en diferentes continentes.&lt;/p&gt;&lt;p&gt;Uno de los cambios más significativos ha sido la revolución en la comunicación. Piensa en cómo solíamos comunicarnos hace solo unas décadas. Las cartas escritas a mano eran la norma, y esperar días o incluso semanas para recibir una respuesta era común. Ahora, con la llegada de los mensajes instantáneos, podemos comunicarnos con cualquier persona, en cualquier lugar del mundo, en tiempo real. Las barreras de tiempo y espacio se han desvanecido, permitiéndonos mantenernos conectados con nuestros seres queridos, hacer negocios y compartir ideas de formas que nunca antes habíamos imaginado. La comunicación instantánea ha cambiado no solo cómo interactuamos, sino también nuestras expectativas de inmediatez y accesibilidad.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;La era de la información: Conocimiento al alcance de un clic&lt;/h3&gt;&lt;div&gt;&lt;div&gt;La Transformación Digital ha propulsado a la humanidad hacia una nueva era: la era de la información. En este futuro digital, el conocimiento ya no está confinado a las bibliotecas o a las aulas de clase. Ahora, está literalmente al alcance de un clic. Internet, con su vasto océano de información, ha democratizado el acceso al conocimiento. Ya sea que quieras aprender una nueva habilidad, entender un concepto complejo, o simplemente satisfacer tu curiosidad, todo lo que necesitas es una conexión a internet y la voluntad de aprender.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Esta accesibilidad al conocimiento ha cambiado la forma en que aprendemos y nos desarrollamos. Las plataformas de aprendizaje en línea, los tutoriales en video, los blogs educativos y las aplicaciones de aprendizaje han hecho que la educación sea más flexible y personalizada. Ahora, puedes aprender a tu propio ritmo, en tu propio tiempo, y de la manera que mejor se adapte a tu estilo de aprendizaje. Además, la era de la información ha facilitado la colaboración y el intercambio de ideas, permitiendo a las personas de todo el mundo trabajar juntas para resolver problemas y generar nuevas ideas. En resumen, la Transformación Digital ha hecho que el conocimiento sea más accesible y valioso que nunca.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Tecnología y Salud: Salvando vidas con un chip&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Telemedicina: Consultas médicas desde la comodidad de tu hogar&lt;/h3&gt;&lt;div&gt;La intersección de la tecnología y la salud ha dado lugar a avances asombrosos que están cambiando la forma en que cuidamos nuestro bienestar. Uno de estos avances es la telemedicina, que ha transformado la forma en que interactuamos con los profesionales de la salud. Ya no necesitamos hacer largas colas en la sala de espera del consultorio del médico. Ahora, podemos tener consultas médicas desde la comodidad de nuestro hogar.&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;La telemedicina utiliza tecnologías de comunicación digital para facilitar las consultas médicas a distancia. Ya sea a través de una videollamada, un chat en línea o una aplicación móvil, los pacientes pueden comunicarse con los médicos, describir sus síntomas, recibir diagnósticos y obtener recetas médicas sin tener que salir de casa. Esto no solo es conveniente, sino que también es una bendición para aquellos que viven en áreas rurales o aisladas, donde el acceso a la atención médica puede ser limitado. Además, la telemedicina permite a los pacientes mantenerse en contacto con sus médicos para el seguimiento y la gestión de enfermedades crónicas. En resumen, la tecnología está haciendo que la atención médica sea más accesible y centrada en el paciente que nunca antes.&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Innovaciones en cirugía: Robots en el quirófano&lt;/h3&gt;&lt;div&gt;&lt;div&gt;La tecnología ha revolucionado la forma en que se realizan las cirugías, y una de las innovaciones más emocionantes en este campo es el uso de robots en el quirófano. Aunque pueda sonar como ciencia ficción, los robots quirúrgicos son una realidad y están cambiando la forma en que se realizan las operaciones, mejorando la precisión y reduciendo los riesgos para los pacientes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Los robots quirúrgicos, como el famoso sistema Da Vinci, permiten a los cirujanos realizar procedimientos complejos con una precisión y un control sin precedentes. Estos robots no realizan la cirugía por sí mismos, sino que son controlados por un cirujano que utiliza una consola para manipular los brazos del robot. Los robots pueden realizar movimientos más precisos y estables que la mano humana, lo que reduce el riesgo de errores. Además, los robots pueden acceder a áreas del cuerpo que serían difíciles o imposibles de alcanzar con las técnicas quirúrgicas tradicionales. Esto significa que las cirugías pueden ser menos invasivas, lo que a su vez puede llevar a una recuperación más rápida para los pacientes. En resumen, la tecnología está abriendo nuevas posibilidades en el campo de la cirugía, mejorando los resultados para los pacientes y facilitando el trabajo de los cirujanos.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;El impacto de la tecnología en la educación&lt;/h2&gt;&lt;div&gt;La tecnología ha revolucionado la educación, cambiando la forma en que los estudiantes interactúan con el conocimiento y cómo los maestros facilitan ese proceso. El aprendizaje ya no está limitado a las aulas físicas. Con la ayuda de la tecnología, la educación se ha vuelto más accesible y personalizada, adaptándose a las necesidades individuales de cada estudiante.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Aprendizaje online: La escuela en tu sala de estar&lt;/h3&gt;&lt;div&gt;&lt;div&gt;El aprendizaje online ha sido una de las transformaciones más significativas en el campo educativo. Esta modalidad ha permitido que la educación trascienda las barreras geográficas y temporales. Ahora, cualquier persona con acceso a internet puede aprender desde la comodidad de su hogar, a su propio ritmo. Los estudiantes pueden acceder a una amplia variedad de cursos y programas ofrecidos por instituciones de todo el mundo.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Además, el aprendizaje online fomenta la autodisciplina y la autogestión, habilidades esenciales en el mundo actual. Ya sea que estés buscando adquirir una nueva habilidad, mejorar tu conocimiento en un área específica o incluso obtener un título, el aprendizaje online ofrece una solución flexible y accesible.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;En resumen, la tecnología ha convertido nuestras salas de estar en aulas, abriendo un mundo de oportunidades para aprender y crecer.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Tecnología en el aula: Mejorando la experiencia de aprendizaje&lt;/span&gt;&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;La incorporación de la tecnología en el aula ha mejorado significativamente la experiencia de aprendizaje.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Las herramientas digitales como las pizarras interactivas, las tabletas y los programas educativos han hecho que las lecciones sean más atractivas y participativas. Los estudiantes ya no son meros receptores de información, sino participantes activos en su propio proceso de aprendizaje. La tecnología también ha permitido la personalización del aprendizaje, donde los maestros pueden adaptar las lecciones a las necesidades y habilidades individuales de cada estudiante.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Además, la tecnología ha facilitado la evaluación y el seguimiento del progreso del estudiante, proporcionando a los maestros información valiosa para mejorar su enseñanza.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;En resumen, la tecnología en el aula ha mejorado la eficacia de la enseñanza y ha enriquecido la experiencia de aprendizaje, preparando a los estudiantes para un mundo cada vez más digital.&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;La tecnología y el medio ambiente: Un arma de doble filo&lt;/h2&gt;&lt;div&gt;La tecnología tiene un impacto significativo en nuestro medio ambiente, y no siempre es positivo. Por un lado, la tecnología ha permitido avances increíbles en áreas como la eficiencia energética y la conservación de recursos. Pero por otro lado, también ha contribuido a problemas ambientales serios, como la contaminación y el agotamiento de recursos naturales. Sin embargo, hay un área en la que la tecnología está haciendo una diferencia positiva y esa es la energía renovable.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Energías renovables: El poder del sol y el viento&lt;/h3&gt;&lt;div&gt;&lt;div&gt;Las energías renovables, como la solar y la eólica, están cambiando la forma en que generamos y consumimos energía. Gracias a la tecnología, ahora podemos aprovechar el poder del sol y el viento para generar electricidad de una manera que es amigable con el medio ambiente y sostenible a largo plazo.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Los paneles solares y las turbinas eólicas son cada vez más eficientes y asequibles, lo que hace que la energía renovable sea una opción viable para más y más personas. Además, la tecnología también está ayudando a mejorar la forma en que almacenamos y distribuimos esta energía, lo que nos acerca cada vez más a un futuro de energía limpia y renovable.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;En resumen, la tecnología está jugando un papel crucial en la lucha contra el cambio climático y la protección de nuestro planeta para las generaciones futuras.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Tecnología verde: Innovaciones para un planeta más limpio&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;La tecnología verde es un término amplio que abarca una variedad de innovaciones y prácticas diseñadas para reducir nuestro impacto en el medio ambiente. Estas tecnologías buscan minimizar la contaminación, reducir el desperdicio, y promover la eficiencia energética. Desde vehículos eléctricos hasta edificios ecológicos, la tecnología verde está cambiando la forma en que vivimos y trabajamos.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Los vehículos eléctricos, por ejemplo, están revolucionando la industria del transporte. Al eliminar la necesidad de combustibles fósiles, estos vehículos reducen significativamente las emisiones de gases de efecto invernadero. Además, la tecnología de las baterías está avanzando rápidamente, lo que significa que los vehículos eléctricos son cada vez más eficientes y asequibles.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;En el ámbito de la construcción, los edificios ecológicos están cambiando la forma en que pensamos sobre el diseño y la construcción. Estos edificios utilizan materiales sostenibles, maximizan la eficiencia energética, y a menudo incorporan sistemas de energía renovable. Además, muchos edificios ecológicos están diseñados para minimizar el desperdicio de agua y promover la calidad del aire interior.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;En resumen, la tecnología verde está ayudando a crear un futuro más sostenible y ecológico. A través de la innovación y la adopción de estas tecnologías, podemos reducir nuestro impacto en el medio ambiente y crear un planeta más limpio para las generaciones futuras.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Conclusiones&lt;/h2&gt;&lt;div&gt;&lt;div&gt;En conclusión, la tecnología ha tenido un impacto profundo y transformador en nuestras vidas, cambiando la forma en que nos comunicamos, accedemos a la información, cuidamos nuestra salud, aprendemos y cuidamos nuestro planeta.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;La transformación digital ha revolucionado la comunicación, permitiéndonos conectar con personas de todo el mundo en tiempo real. La era de la información ha democratizado el acceso al conocimiento, poniendo una cantidad casi infinita de información al alcance de un clic. En el ámbito de la salud, la tecnología ha permitido avances asombrosos, desde la telemedicina hasta la cirugía robótica. En educación, la tecnología ha abierto nuevas vías de aprendizaje y ha mejorado la experiencia en el aula. Y en términos de medio ambiente, la tecnología verde está ayudando a crear un futuro más sostenible.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sin embargo, es importante recordar que la tecnología es una herramienta, y su impacto depende de cómo la usemos. Debemos esforzarnos por utilizar la tecnología de manera responsable y ética, y trabajar para garantizar que sus beneficios sean accesibles para todos. En última instancia, la tecnología tiene el potencial de mejorar nuestras vidas y nuestro mundo, pero depende de nosotros hacer que ese potencial se convierta en realidad.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;blockquote&gt;Nos encantaría saber tu opinión sobre este artículo en los comentarios. ¿Te ha parecido útil? ¿Hay algún otro tema relacionado con la tecnología que te gustaría que exploráramos en Tutoriales Técnicos? Tu feedback es muy valioso para nosotros y nos ayuda a crear contenido que sea relevante y útil para ti. Así que no dudes en compartir tus pensamientos y sugerencias para futuros artículos. ¡Estamos ansiosos por leer tus comentarios!&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;i&gt;PD: Comparte este artículo con todos tus contactos.&lt;/i&gt;&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tutorialestecnicos.blogspot.com/feeds/4192883723394829746/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/05/como-ha-cambiado-la-tecnologia-nuestras.html#comment-form' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/4192883723394829746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3279603830404251843/posts/default/4192883723394829746'/><link rel='alternate' type='text/html' href='http://tutorialestecnicos.blogspot.com/2023/05/como-ha-cambiado-la-tecnologia-nuestras.html' title='¿Cómo ha cambiado la tecnología nuestras vidas?'/><author><name>Willian Diaz</name><uri>http://www.blogger.com/profile/12030062158428083614</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnRio_JU5GHfySNIqpCcLO4y39mwgQIKFiBFgFSEQHIpWJYxrNWwTtCRsCWKvSrU8_dQZpSm47fY5wy_IaRQhTFv8Slg09OkhVgVSCrIXBTem182d5Y7oG4kLjErTovag/s113/1676985165043%3Fe%3D1683158400%26v%3Dbeta%26t%3DnDgfb7d630JSwVrA7iVRo0peOk3OtnsWUx7kcBUFUIg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6fn5uzL67U-rjvSJGoYET5LgNN-h9jIDagWbYQzriGYQD4ZjplOi7JG-opjUHwNbChqG6RFC_C4qxKF1j0W94kJCWrNowJIJYWY7iRE7JLu5A0LGC3a3TGtZ01Mg_-ktNPdio_Uk5KHeBW_FdyfnHiRwxf8FtSMIETxTZRDujc6WVG75foCQzDfajjiMY/s72-w400-h400-c/DALL%C2%B7E%202023-11-30%2020.26.40%20-%20A%20futuristic%20illustration%20representing%20the%20impact%20of%20technology%20on%20various%20aspects%20of%20life,%20featuring%20a%20digital%20landscape%20with%20icons%20or%20visual%20represe.png" height="72" width="72"/><thr:total>0</thr:total></entry></feed>